summaryrefslogtreecommitdiff
path: root/archived/projt-launcher/launcher/minecraft/mod/Resource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'archived/projt-launcher/launcher/minecraft/mod/Resource.cpp')
-rw-r--r--archived/projt-launcher/launcher/minecraft/mod/Resource.cpp343
1 files changed, 343 insertions, 0 deletions
diff --git a/archived/projt-launcher/launcher/minecraft/mod/Resource.cpp b/archived/projt-launcher/launcher/minecraft/mod/Resource.cpp
new file mode 100644
index 0000000000..3f70a24f41
--- /dev/null
+++ b/archived/projt-launcher/launcher/minecraft/mod/Resource.cpp
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2026 Project Tick
+// SPDX-FileContributor: Project Tick Team
+/*
+ * ProjT Launcher - Minecraft Launcher
+ * Copyright (C) 2026 Project Tick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "Resource.hpp"
+
+#include <QDirIterator>
+#include <QFileInfo>
+#include <QRegularExpression>
+#include <tuple>
+
+#include "FileSystem.h"
+#include "StringUtils.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+Resource::Resource(QObject* parent) : QObject(parent)
+{}
+
+Resource::Resource(QFileInfo file_info) : QObject()
+{
+ setFile(file_info);
+}
+
+void Resource::setFile(QFileInfo file_info)
+{
+ m_file_info = file_info;
+ parseFile();
+}
+
+static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
+{
+ if (file.isDir())
+ {
+ auto dir = QDir(file.absoluteFilePath());
+ dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
+ auto count = dir.count();
+ auto str = QObject::tr("item");
+ if (count != 1)
+ str = QObject::tr("items");
+ return { QString("%1 %2").arg(QString::number(count), str), count };
+ }
+ return { StringUtils::humanReadableFileSize(file.size(), true), file.size() };
+}
+
+void Resource::parseFile()
+{
+ QString file_name{ m_file_info.fileName() };
+
+ m_type = ResourceType::UNKNOWN;
+
+ m_internal_id = file_name;
+
+ std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info);
+ if (m_file_info.isDir())
+ {
+ m_type = ResourceType::FOLDER;
+ m_name = file_name;
+ }
+ else if (m_file_info.isFile())
+ {
+ if (file_name.endsWith(".disabled"))
+ {
+ file_name.chop(9);
+ m_enabled = false;
+ }
+
+ if (file_name.endsWith(".zip") || file_name.endsWith(".jar"))
+ {
+ m_type = ResourceType::ZIPFILE;
+ file_name.chop(4);
+ }
+ else if (file_name.endsWith(".nilmod"))
+ {
+ m_type = ResourceType::ZIPFILE;
+ file_name.chop(7);
+ }
+ else if (file_name.endsWith(".litemod"))
+ {
+ m_type = ResourceType::LITEMOD;
+ file_name.chop(8);
+ }
+ else
+ {
+ m_type = ResourceType::SINGLEFILE;
+ }
+
+ m_name = file_name;
+ }
+
+ m_changed_date_time = m_file_info.lastModified();
+}
+
+auto Resource::name() const -> QString
+{
+ if (metadata())
+ return metadata()->name;
+
+ return m_name;
+}
+
+static void removeThePrefix(QString& string)
+{
+ static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"),
+ QRegularExpression::CaseInsensitiveOption);
+ string.remove(s_regex);
+ string = string.trimmed();
+}
+
+auto Resource::provider() const -> QString
+{
+ if (metadata())
+ return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
+
+ return tr("Unknown");
+}
+
+auto Resource::homepage() const -> QString
+{
+ if (metadata())
+ return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
+
+ return {};
+}
+
+void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
+{
+ if (status() == ResourceStatus::NO_METADATA)
+ setStatus(ResourceStatus::INSTALLED);
+
+ m_metadata = metadata;
+}
+
+QStringList Resource::issues() const
+{
+ QStringList result;
+ result.reserve(m_issues.length());
+
+ for (const char* issue : m_issues) {
+ result.append(tr(issue));
+ }
+
+ return result;
+}
+
+void Resource::updateIssues(const BaseInstance* inst)
+{
+ m_issues.clear();
+
+ if (m_metadata == nullptr) {
+ return;
+ }
+
+ auto mcInst = dynamic_cast<const MinecraftInstance*>(inst);
+ if (mcInst == nullptr) {
+ return;
+ }
+
+ auto profile = mcInst->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+
+ if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) {
+ // delay translation until issues() is called
+ m_issues.append(QT_TR_NOOP("Not marked as compatible with the instance's game version."));
+ }
+}
+
+int Resource::compare(const Resource& other, SortType type) const
+{
+ switch (type)
+ {
+ default:
+ case SortType::ENABLED:
+ if (enabled() && !other.enabled())
+ return 1;
+ if (!enabled() && other.enabled())
+ return -1;
+ break;
+ case SortType::NAME:
+ {
+ QString this_name{ name() };
+ QString other_name{ other.name() };
+
+ // Remove common prefixes like "The" for better alphabetical sorting
+ removeThePrefix(this_name);
+ removeThePrefix(other_name);
+
+ return QString::compare(this_name, other_name, Qt::CaseInsensitive);
+ }
+ case SortType::DATE:
+ if (dateTimeChanged() > other.dateTimeChanged())
+ return 1;
+ if (dateTimeChanged() < other.dateTimeChanged())
+ return -1;
+ break;
+ case SortType::SIZE:
+ {
+ if (this->type() != other.type())
+ {
+ if (this->type() == ResourceType::FOLDER)
+ return -1;
+ if (other.type() == ResourceType::FOLDER)
+ return 1;
+ }
+
+ if (sizeInfo() > other.sizeInfo())
+ return 1;
+ if (sizeInfo() < other.sizeInfo())
+ return -1;
+ break;
+ }
+ case SortType::PROVIDER:
+ {
+ auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
+ if (compare_result != 0)
+ return compare_result;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+bool Resource::applyFilter(QRegularExpression filter) const
+{
+ return filter.match(name()).hasMatch();
+}
+
+bool Resource::enable(EnableAction action)
+{
+ if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
+ return false;
+
+ QString path = m_file_info.absoluteFilePath();
+ QFile file(path);
+
+ bool enable = true;
+ switch (action)
+ {
+ case EnableAction::ENABLE: enable = true; break;
+ case EnableAction::DISABLE: enable = false; break;
+ case EnableAction::TOGGLE:
+ default: enable = !enabled(); break;
+ }
+
+ if (m_enabled == enable)
+ return false;
+
+ if (enable)
+ {
+ // m_enabled is false, but there's no '.disabled' suffix.
+ if (!path.endsWith(".disabled"))
+ {
+ qWarning() << "Cannot enable resource" << name() << ": file does not have .disabled suffix";
+ return false;
+ }
+ path.chop(9);
+ }
+ else
+ {
+ path += ".disabled";
+ if (QFile::exists(path))
+ {
+ path = FS::getUniqueResourceName(path);
+ }
+ }
+ if (!file.rename(path))
+ return false;
+
+ setFile(QFileInfo(path));
+
+ m_enabled = enable;
+ return true;
+}
+
+auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
+{
+ m_type = ResourceType::UNKNOWN;
+
+ if (!preserve_metadata)
+ {
+ qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
+ destroyMetadata(index_dir);
+ }
+
+ return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
+}
+
+auto Resource::destroyMetadata(const QDir& index_dir) -> void
+{
+ if (metadata())
+ {
+ Metadata::remove(index_dir, metadata()->slug);
+ }
+ else
+ {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_metadata = nullptr;
+}
+
+bool Resource::isSymLinkUnder(const QString& instPath) const
+{
+ if (isSymLink())
+ return true;
+
+ auto instDir = QDir(instPath);
+
+ auto relAbsPath = instDir.relativeFilePath(m_file_info.absoluteFilePath());
+ auto relCanonPath = instDir.relativeFilePath(m_file_info.canonicalFilePath());
+
+ return relAbsPath != relCanonPath;
+}
+
+bool Resource::isMoreThanOneHardLink() const
+{
+ return FS::hardLinkCount(m_file_info.absoluteFilePath()) > 1;
+}
+
+auto Resource::getOriginalFileName() const -> QString
+{
+ auto fileName = m_file_info.fileName();
+ if (!m_enabled)
+ fileName.chop(9);
+ return fileName;
+} \ No newline at end of file