diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index d50b3d5cf..bb44d2495 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -19,23 +19,52 @@ #include "ResourceDownloadTask.h" +#include + #include "Application.h" #include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "net/ApiDownload.h" #include "net/ChecksumValidator.h" +namespace { +Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason) +{ + auto* mcInstance = dynamic_cast(instance); + if (!mcInstance) { + return {}; + } + + auto* profile = mcInstance->getPackProfile(); + if (!profile) { + return {}; + } + + auto loaders = profile->getModLoadersList(); + + return { + .reason = std::move(reason), + .gameVersion = profile->getComponentVersion("net.minecraft"), + .loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "", + }; +} +} // namespace + ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed) + bool isIndexed, + QString downloadReason) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) { - if (is_indexed) { + if (isIndexed) { m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); @@ -45,7 +74,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); - auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename())); + auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()), + Net::Download::Option::NoOptions, + createModrinthMeta(m_pack_model->instance(), std::move(downloadReason))); if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) { switch (Hashing::algorithmFromString(m_pack_version.hash_type)) { case Hashing::Algorithm::Md4: @@ -82,8 +113,9 @@ void ResourceDownloadTask::downloadSucceeded() auto oldName = std::get<0>(to_delete); auto oldFilename = std::get<1>(to_delete); - if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) + if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) { return; + } m_pack_model->uninstallResource(oldFilename, true); @@ -95,8 +127,9 @@ void ResourceDownloadTask::downloadSucceeded() if (oldConfig.exists() && !newConfig.exists()) { bool success = FS::move(oldConfig.filePath(), newConfig.filePath()); - if (!success) + if (!success) { emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName())); + } } } } @@ -104,7 +137,7 @@ void ResourceDownloadTask::downloadSucceeded() void ResourceDownloadTask::downloadFailed(QString reason) { m_filesNetJob.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); } void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) @@ -114,7 +147,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) // This indirection is done so that we don't delete a mod before being sure it was // downloaded successfully! -void ResourceDownloadTask::hasOldResource(QString name, QString filename) +void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename) { to_delete = { name, filename }; } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 7a04c6f1c..84324e99a 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -33,7 +33,8 @@ class ResourceDownloadTask : public SequentialTask { explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed = true); + bool isIndexed = true, + QString downloadReason = "standalone"); const QString& getFilename() const { return m_pack_version.fileName; } const QVariant& getVersionID() const { return m_pack_version.fileId; } const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; } @@ -56,5 +57,5 @@ class ResourceDownloadTask : public SequentialTask { std::tuple to_delete{ "", "" }; private slots: - void hasOldResource(QString name, QString filename); + void hasOldResource(const QString& name, const QString& filename); }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 81bc6f5fc..124b9ea77 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -183,6 +183,7 @@ class ResourceFolderModel : public QAbstractListModel { }; QString instDirPath() const; + BaseInstance* instance() const { return m_instance; } signals: void updateFinished(); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index d3f577f44..ad68fd963 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -15,15 +15,15 @@ class CheckUpdateTask : public Task { std::vector& mcVersions, QList loadersList, ResourceFolderModel* resourceModel) - : Task(), m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) + : m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) {} struct Update { QString name; - QString old_hash; - QString old_version; - QString new_version; - std::optional new_version_type; + QString oldHash; + QString oldVersion; + QString newVersion; + std::optional newVersionType; QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; @@ -31,19 +31,19 @@ class CheckUpdateTask : public Task { public: Update(QString name, - QString old_h, - QString old_v, - QString new_v, - std::optional new_v_type, + QString oldH, + QString oldV, + QString newV, + std::optional newVType, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr t, bool enabled = true) : name(std::move(name)) - , old_hash(std::move(old_h)) - , old_version(std::move(old_v)) - , new_version(std::move(new_v)) - , new_version_type(std::move(new_v_type)) + , oldHash(std::move(oldH)) + , oldVersion(std::move(oldV)) + , newVersion(std::move(newV)) + , newVersionType(newVType) , changelog(std::move(changelog)) , provider(p) , download(std::move(t)) @@ -54,14 +54,11 @@ class CheckUpdateTask : public Task { auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } - public slots: - bool abort() override = 0; - protected slots: void executeTask() override = 0; signals: - void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); + void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {}); protected: QList& m_resources; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 37f0bcb32..5bd13220a 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -18,8 +18,6 @@ #include "net/NetJob.h" #include "tasks/Task.h" -static FlameAPI api; - bool FlameCheckUpdate::abort() { bool result = false; @@ -39,7 +37,7 @@ void FlameCheckUpdate::executeTask() { setStatus(tr("Preparing resources for CurseForge...")); - auto netJob = new NetJob("Get latest versions", APPLICATION->network()); + auto* netJob = new NetJob("Get latest versions", APPLICATION->network()); connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods); connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress); @@ -48,9 +46,10 @@ void FlameCheckUpdate::executeTask() for (auto* resource : m_resources) { auto project = std::make_shared(); project->addonId = resource->metadata()->project_id.toString(); - auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions }); - if (!versionsUrlOptional.has_value()) + auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions }); + if (!versionsUrlOptional.has_value()) { continue; + } auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value()); @@ -63,11 +62,11 @@ void FlameCheckUpdate::executeTask() void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response) { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from latest mod version at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from latest mod version at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } @@ -88,100 +87,104 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* qCritical() << e.what(); qDebug() << doc; } - auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); + auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); - if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { + if (!latestVer.has_value() || !latestVer->addonId.isValid()) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); return; } - if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { - m_blocked[resource] = latest_ver->fileId.toString(); + if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) { + m_blocked[resource] = latestVer->fileId.toString(); return; } - if (!latest_ver->hash.isEmpty() && - (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + if (!latestVer->hash.isEmpty() && + (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel); - m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, - api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); + auto downloadTask = makeShared(pack, latestVer.value(), m_resourceModel, true, "update"); + m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type, + FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, latest_ver.value())); + m_deps.append(std::make_shared(pack, latestVer.value())); } void FlameCheckUpdate::collectBlockedMods() { QStringList addonIds; QHash quickSearch; - for (auto const& resource : m_blocked.keys()) { + for (const auto& resource : m_blocked.keys()) { auto addonId = resource->metadata()->project_id.toString(); addonIds.append(addonId); quickSearch[addonId] = resource; } Task::Ptr projTask; - QByteArray* response; + QByteArray* response = nullptr; if (addonIds.isEmpty()) { emitSucceeded(); return; - } else if (addonIds.size() == 1) { - std::tie(projTask, response) = api.getProject(*addonIds.begin()); + } + if (addonIds.size() == 1) { + std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin()); } else { - std::tie(projTask, response) = api.getProjects(addonIds); + std::tie(projTask, response) = FlameAPI().getProjects(addonIds); } connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { - QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + auto doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } try { QJsonArray entries; - if (addonIds.size() == 1) + if (addonIds.size() == 1) { entries = { Json::requireObject(Json::requireObject(doc), "data") }; - else + } else { entries = Json::requireArray(Json::requireObject(doc), "data"); + } for (auto entry : entries) { - auto entry_obj = Json::requireObject(entry); + auto entryObj = Json::requireObject(entry); - auto id = QString::number(Json::requireInteger(entry_obj, "id")); + auto id = QString::number(Json::requireInteger(entryObj, "id")); - auto resource = quickSearch.find(id).value(); + auto* resource = quickSearch.find(id).value(); ModPlatform::IndexedPack pack; try { setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); - FlameMod::loadIndexedPack(pack, entry_obj); - auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); + FlameMod::loadIndexedPack(pack, entryObj); + auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), - recover_url); + recoverUrl); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index bd5a50d1b..cb2d57aba 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -53,7 +53,7 @@ void ModrinthCheckUpdate::executeTask() setStatus(tr("Preparing resources for Modrinth...")); setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1); - auto hashing_task = + auto hashingTask = makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); bool startHasing = false; for (auto* resource : m_resources) { @@ -63,10 +63,11 @@ void ModrinthCheckUpdate::executeTask() // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) if (resource->metadata()->hash_format != m_hashType) { - auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); }); - connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); - hashing_task->addTask(hash_task); + auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, + [this, resource](const QString& hash) { m_mappings.insert(hash, resource); }); + connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); + hashingTask->addTask(hashTask); startHasing = true; } else { m_mappings.insert(hash, resource); @@ -74,9 +75,9 @@ void ModrinthCheckUpdate::executeTask() } if (startHasing) { - connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); - m_job = hashing_task; - hashing_task->start(); + connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashingTask; + hashingTask->start(); } else { checkNextLoader(); } @@ -120,14 +121,14 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio setStatus(tr("Parsing the API response from Modrinth...")); setProgress(m_progress + 1, m_progressTotal); - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; - emitFailed(parse_error.errorString()); + emitFailed(parseError.errorString()); return; } @@ -138,11 +139,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio const QString hash = iter.key(); Resource* resource = iter.value(); - auto project_obj = doc[hash].toObject(); + auto projectObj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available - if (project_obj.isEmpty()) { + if (projectObj.isEmpty()) { qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash; ++iter; continue; @@ -150,11 +151,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it - QString loader_filter; + QString loaderFilter; if (loader.has_value() && loader != 0) { auto modLoaders = ModPlatform::modLoaderTypesToList(*loader); if (!modLoaders.isEmpty()) { - loader_filter = ModPlatform::getModLoaderAsString(modLoaders.first()); + loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first()); } } @@ -164,9 +165,9 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) // Such is the pain of having arbitrary files for a given version .-. - auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter); - if (project_ver.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!" << project_ver.fileName; + auto projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter); + if (projectVer.downloadUrl.isEmpty()) { + qCritical() << "Modrinth mod without download url!" << projectVer.fileName; ++iter; continue; } @@ -177,21 +178,22 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->slug = resource->metadata()->slug; pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto download_task = makeShared(pack, project_ver, m_resourceModel); + if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { + auto downloadTask = makeShared(pack, projectVer, m_resourceModel, true, "update"); - QString old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + QString oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NOT_INSTALLED) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled()); + m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type, + projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, project_ver)); + m_deps.append(std::make_shared(pack, projectVer)); iter = m_mappings.erase(iter); } @@ -211,20 +213,22 @@ void ModrinthCheckUpdate::checkNextLoader() if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize); return; - } else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader + } + if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader getUpdateModsForLoader(); return; } - for (auto resource : m_mappings) { + for (auto* resource : m_mappings) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f308c88bc..0cb2c547d 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -16,7 +16,10 @@ #include "net/ChecksumValidator.h" #include "net/ApiDownload.h" +#include "net/ApiHeaderProxy.h" #include "net/NetJob.h" + +#include "modplatform/ModIndex.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -29,114 +32,119 @@ bool ModrinthCreationTask::abort() { - if (!canAbort()) + if (!canAbort()) { return false; + } - if (m_task) + if (m_task) { m_task->abort(); + } return InstanceCreationTask::abort(); } bool ModrinthCreationTask::updateInstance() { - auto instance_list = APPLICATION->instances(); + auto* instanceList = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - BaseInstance* inst; - if (auto original_id = originalInstanceID(); !original_id.isEmpty()) { - inst = instance_list->getInstanceById(original_id); + BaseInstance* inst = nullptr; + if (auto originalId = originalInstanceID(); !originalId.isEmpty()) { + inst = instanceList->getInstanceById(originalId); Q_ASSERT(inst); } else { - inst = instance_list->getInstanceByManagedName(originalName()); + inst = instanceList->getInstanceByManagedName(originalName()); if (!inst) { - inst = instance_list->getInstanceById(originalName()); + inst = instanceList->getInstanceById(originalName()); - if (!inst) + if (!inst) { return false; + } } } - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(index_path, m_files, true, false)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(indexPath, m_files, true, false)) { return false; + } - auto version_name = inst->getManagedPackVersionName(); + auto versionName = inst->getManagedPackVersionName(); m_root_path = QFileInfo(inst->gameRoot()).fileName(); - auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; + auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : ""; if (shouldConfirmUpdate()) { - auto should_update = askIfShouldUpdate(m_parent, version_str); - if (should_update == ShouldUpdate::SkipUpdating) + auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr); + if (shouldUpdate == ShouldUpdate::SkipUpdating) { return false; - if (should_update == ShouldUpdate::Cancel) { + } + if (shouldUpdate == ShouldUpdate::Cancel) { m_abort = true; return false; } } // Remove repeated files, we don't need to download them! - QDir old_inst_dir(inst->instanceRoot()); + QDir oldInstDir(inst->instanceRoot()); - QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); + QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack")); - QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); - QFileInfo old_index_file(old_index_path); - if (old_index_file.exists()) { - std::vector old_files; - parseManifest(old_index_path, old_files, false, false); + QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json")); + QFileInfo oldIndexFile(oldIndexPath); + if (oldIndexFile.exists()) { + std::vector oldFiles; + parseManifest(oldIndexPath, oldFiles, false, false); // Let's remove all duplicated, identical resources! - auto files_iterator = m_files.begin(); + auto filesIterator = m_files.begin(); begin: - while (files_iterator != m_files.end()) { - auto const& file = *files_iterator; + while (filesIterator != m_files.end()) { + const auto& file = *filesIterator; - auto old_files_iterator = old_files.begin(); - while (old_files_iterator != old_files.end()) { - auto const& old_file = *old_files_iterator; + auto oldFilesIterator = oldFiles.begin(); + while (oldFilesIterator != oldFiles.end()) { + const auto& oldFile = *oldFilesIterator; - if (old_file.hash == file.hash) { + if (oldFile.hash == file.hash) { qDebug() << "Removed file at" << file.path << "from list of downloads"; - files_iterator = m_files.erase(files_iterator); - old_files_iterator = old_files.erase(old_files_iterator); + filesIterator = m_files.erase(filesIterator); + oldFilesIterator = oldFiles.erase(oldFilesIterator); goto begin; // Sorry :c } - old_files_iterator++; + oldFilesIterator++; } - files_iterator++; + filesIterator++; } - QDir old_minecraft_dir(inst->gameRoot()); + QDir oldMinecraftDir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! - if (!old_files.empty()) { - for (auto const& file : old_files) { - scheduleToDelete(m_parent, old_minecraft_dir, file.path, true); + if (!oldFiles.empty()) { + for (const auto& file : oldFiles) { + scheduleToDelete(m_parent, oldMinecraftDir, file.path, true); } } // We will remove all the previous overrides, to prevent duplicate files! // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. - auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (const auto& entry : old_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder); + for (const auto& entry : oldOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } - auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (const auto& entry : old_client_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder); + for (const auto& entry : oldClientOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } } else { // We don't have an old index file, so we may duplicate stuff! - auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), - tr("We couldn't find a suitable index file for the older version. This may cause some " - "of the files to be duplicated. Do you want to continue?"), - QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + auto* dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some " + "of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); if (dialog->exec() == QDialog::DialogCode::Rejected) { m_abort = true; @@ -158,39 +166,40 @@ std::unique_ptr ModrinthCreationTask::createInstance() { QEventLoop loop; - QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); + QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack")); - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) { return nullptr; + } // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); - FS::ensureFilePathExists(new_index_place); - FS::move(index_path, new_index_place); + QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json")); + FS::ensureFilePathExists(newIndexPlace); + FS::move(indexPath, newIndexPlace); auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); - auto override_path = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(override_path)) { + auto overridePath = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(overridePath)) { // Create a list of overrides in "overrides.txt" inside mrpack/ - Override::createOverrides("overrides", parent_folder, override_path); + Override::createOverrides("overrides", parentFolder, overridePath); // Apply the overrides - if (!FS::move(override_path, mcPath)) { + if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return nullptr; } } // Do client overrides - auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(client_override_path)) { + auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(clientOverridePath)) { // Create a list of overrides in "client-overrides.txt" inside mrpack/ - Override::createOverrides("client-overrides", parent_folder, client_override_path); + Override::createOverrides("client-overrides", parentFolder, clientOverridePath); // Apply the overrides - if (!FS::overrideFolder(mcPath, client_override_path)) { + if (!FS::overrideFolder(mcPath, clientOverridePath)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); return nullptr; } @@ -200,18 +209,27 @@ std::unique_ptr ModrinthCreationTask::createInstance() auto instanceSettings = std::make_unique(configPath); auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); - auto components = instance->getPackProfile(); + auto* components = instance->getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_minecraft_version, true); - if (!m_fabric_version.isEmpty()) + QString loader; + if (!m_fabric_version.isEmpty()) { components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); - if (!m_quilt_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric); + } + if (!m_quilt_version.isEmpty()) { components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); - if (!m_forge_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt); + } + if (!m_forge_version.isEmpty()) { components->setComponentVersion("net.minecraftforge", m_forge_version); - if (!m_neoForge_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge); + } + if (!m_neoForge_version.isEmpty()) { components->setComponentVersion("net.neoforged", m_neoForge_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge); + } if (m_instIcon != "default") { instance->setIconKey(m_instIcon); @@ -220,34 +238,35 @@ std::unique_ptr ModrinthCreationTask::createInstance() } // Don't add managed info to packs without an ID (most likely imported from ZIP) - if (!m_managed_id.isEmpty()) + if (!m_managed_id.isEmpty()) { instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); - else + } else { instance->setManagedPack("modrinth", "", name(), "", ""); + } instance->setName(name()); instance->saveNow(); auto downloadMods = makeShared(tr("Mod Download Modrinth"), APPLICATION->network()); - auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); - auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); + auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path); + auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath); // TODO make this work with other sorts of resource QHash resources; for (auto& file : m_files) { auto fileName = file.path; fileName = FS::RemoveInvalidPathChars(fileName); - auto file_path = FS::PathCombine(root_modpack_path, fileName); - if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) { + auto filePath = FS::PathCombine(rootModpackPath, fileName); + if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) { // This means we somehow got out of the root folder, so abort here to prevent exploits setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.") .arg(fileName)); return nullptr; } if (fileName.startsWith("mods/")) { - auto mod = new Mod(file_path); + auto* mod = new Mod(filePath); ModDetails d; - d.mod_id = file_path; + d.mod_id = filePath; mod->setDetails(d); resources[file.hash.toHex()] = mod; } @@ -255,29 +274,39 @@ std::unique_ptr ModrinthCreationTask::createInstance() setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); return nullptr; } - qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; - auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; + + Net::ModrinthDownloadMeta meta{ + .reason = m_instance.has_value() ? "update" : "modpack", + .gameVersion = m_minecraft_version, + .loader = loader, + }; + + QUrl downloadUrl = file.downloads.dequeue(); + auto dl = Net::ApiDownload::makeFile(downloadUrl, filePath, Net::Download::Option::NoOptions, meta); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(dl); if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] { - auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] { + QUrl fallbackUrl = file.downloads.dequeue(); + auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(ndl); - if (auto shared = param.lock()) + if (auto shared = param.lock()) { shared->succeeded(); + } }); } } - bool ended_well = false; + bool endedWell = false; - connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; }); - connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) { - ended_well = false; + connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; }); + connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) { + endedWell = false; setError(reason); }); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); @@ -293,8 +322,8 @@ std::unique_ptr ModrinthCreationTask::createInstance() loop.exec(); - if (!ended_well) { - for (auto resource : resources) { + if (!endedWell) { + for (auto* resource : resources) { delete resource; } return nullptr; @@ -303,7 +332,7 @@ std::unique_ptr ModrinthCreationTask::createInstance() QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance->modsRoot(), ".index"); auto ensureMetadataTask = makeShared(resources, folder, ModPlatform::ResourceProvider::MODRINTH); - connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); + connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; }); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); @@ -315,40 +344,38 @@ std::unique_ptr ModrinthCreationTask::createInstance() m_task = ensureMetadataTask; ensureMetaLoop.exec(); - for (auto resource : resources) { + for (auto* resource : resources) { delete resource; } resources.clear(); // Update information of the already installed instance, if any. - if (m_instance && ended_well) { + if (m_instance && endedWell) { setAbortable(false); - auto inst = m_instance.value(); + auto* inst = m_instance.value(); // Only change the name if it didn't use a custom name, so that the previous custom name // is preserved, but if we're using the original one, we update the version string. // NOTE: This needs to come before the copyManagedPack call! if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) { - if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) + if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) { inst->setName(instance->name()); + } } inst->copyManagedPack(*instance); } - if (ended_well) { + if (endedWell) { return instance; } return nullptr; } -bool ModrinthCreationTask::parseManifest(const QString& index_path, - std::vector& files, - bool set_internal_data, - bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector& files, bool setInternalData, bool showOptionalDialog) { try { - auto doc = Json::requireDocument(index_path); + auto doc = Json::requireDocument(indexPath); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -357,9 +384,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, throw JSONValidationError("Unknown game: " + game); } - if (set_internal_data) { - if (m_managed_version_id.isEmpty()) + if (setInternalData) { + if (m_managed_version_id.isEmpty()) { m_managed_version_id = obj["versionId"].toString(); + } m_managed_name = obj["name"].toString(); } @@ -375,7 +403,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, QString support = env["client"].toString("unsupported"); if (support == "unsupported") { continue; - } else if (support == "optional") { + } + if (support == "optional") { file.required = false; } } @@ -387,20 +416,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) - auto download_arr = modInfo["downloads"].toArray(); - for (auto download : download_arr) { + auto downloadArr = modInfo["downloads"].toArray(); + for (auto download : downloadArr) { qWarning() << download.toString(); - bool is_last = download.toString() == download_arr.last().toString(); + bool isLast = download.toString() == downloadArr.last().toString(); - auto download_url = QUrl(download.toString()); + auto downloadUrl = QUrl(download.toString()); - if (!download_url.isValid()) { + if (!downloadUrl.isValid()) { qDebug() - << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path); - if (is_last && file.downloads.isEmpty()) + << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path); + if (isLast && file.downloads.isEmpty()) { throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } } else { - file.downloads.push_back(download_url); + file.downloads.push_back(downloadUrl); } } @@ -408,10 +438,11 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } if (!optionalFiles.empty()) { - if (show_optional_dialog) { + if (showOptionalDialog) { QStringList oFiles; - for (auto file : optionalFiles) + for (const auto& file : optionalFiles) { oFiles.push_back(file.path); + } OptionalModDialog optionalModDialog(m_parent, oFiles); if (optionalModDialog.exec() == QDialog::Rejected) { emitAborted(); @@ -434,7 +465,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } } } - if (set_internal_data) { + if (setInternalData) { auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 01cc8755a..c8835e142 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -24,18 +24,18 @@ class ModrinthCreationTask final : public InstanceCreationTask { }; public: - ModrinthCreationTask(QString staging_path, - SettingsObject* global_settings, + ModrinthCreationTask(const QString& stagingPath, + SettingsObject* globalSettings, QWidget* parent, QString id, - QString version_id = {}, - QString original_instance_id = {}) - : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) + QString versionId = {}, + QString originalInstanceId = {}) + : m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId)) { - setStagingPath(staging_path); - setParentSettings(global_settings); + setStagingPath(stagingPath); + setParentSettings(globalSettings); - m_original_instance_id = std::move(original_instance_id); + m_original_instance_id = std::move(originalInstanceId); } bool abort() override; @@ -44,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::unique_ptr createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool setInternalData = true, bool showOptionalDialog = true); private: QWidget* m_parent = nullptr; diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 9a5a44104..54d3e746e 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -18,28 +18,30 @@ */ #include "net/ApiDownload.h" + +#include #include "net/ApiHeaderProxy.h" namespace Net { Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options) { - auto dl = Download::makeCached(url, entry, options); + auto dl = Download::makeCached(std::move(url), std::move(entry), options); dl->addHeaderProxy(std::make_unique()); return dl; } std::pair ApiDownload::makeByteArray(QUrl url, Download::Options options) { - auto [dl, response] = Download::makeByteArray(url, options); + auto [dl, response] = Download::makeByteArray(std::move(url), options); dl->addHeaderProxy(std::make_unique()); return { dl, response }; } -Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options) +Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) { - auto dl = Download::makeFile(url, path, options); - dl->addHeaderProxy(std::make_unique()); + auto dl = Download::makeFile(std::move(url), std::move(path), options); + dl->addHeaderProxy(std::make_unique(std::move(meta))); return dl; } diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index 01a31eb17..033dd0e12 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -20,13 +20,17 @@ #pragma once #include "Download.h" +#include "net/ApiHeaderProxy.h" namespace Net { namespace ApiDownload { Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); std::pair makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); -Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeFile(QUrl url, + QString path, + Download::Options options = Download::Option::NoOptions, + ModrinthDownloadMeta meta = ModrinthDownloadMeta()); }; // namespace ApiDownload } // namespace Net diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index 789a6fada..d48379617 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -23,27 +23,62 @@ #include "BuildConfig.h" #include "net/HeaderProxy.h" +#include +#include + namespace Net { +struct ModrinthDownloadMeta { + QString reason; + QString gameVersion; + QString loader; + + bool isEmpty() const { return reason.isEmpty(); } + + QByteArray toJson() const + { + QJsonObject obj; + if (!reason.isEmpty()) { + obj["reason"] = reason; + } + if (!gameVersion.isEmpty()) { + obj["game_version"] = gameVersion; + } + if (!loader.isEmpty()) { + obj["loader"] = loader; + } + return QJsonDocument(obj).toJson(QJsonDocument::Compact); + } +}; + class ApiHeaderProxy : public HeaderProxy { public: - ApiHeaderProxy() : HeaderProxy() {} - virtual ~ApiHeaderProxy() = default; + ApiHeaderProxy() = default; + explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} + ~ApiHeaderProxy() override = default; public: - virtual QList headers(const QNetworkRequest& request) const override + QList headers(const QNetworkRequest& request) const override { QList hdrs; if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { - hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() }); + hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() }); } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { QString token = APPLICATION->getModrinthAPIToken(); - if (!token.isNull()) - hdrs.append({ "Authorization", token.toUtf8() }); + if (!token.isNull()) { + hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); + } + } + + if (request.url().host() == "cdn.modrinth.com" && !m_meta.isEmpty()) { + hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() }); } return hdrs; }; + + private: + ModrinthDownloadMeta m_meta; }; } // namespace Net diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 49f263406..bcb30c761 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -185,7 +185,7 @@ void ResourceDownloadDialog::confirm() return; } for (const auto& dep : task->getDependecies()) { - addResource(dep->pack, dep->version); + addResource(dep->pack, dep->version, "dependency"); depNames << dep->pack->name; } dependencyExtraInfo = task->getExtraInfo(); @@ -234,10 +234,10 @@ ResourcePage* ResourceDownloadDialog::selectedPage() return result; } -void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason) { removeResource(pack->name); - selectedPage()->addResourceToPage(pack, ver, getBaseModel()); + selectedPage()->addResourceToPage(pack, ver, getBaseModel(), std::move(downloadReason)); setButtonStatus(); } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index c9d9e568c..bea6c7689 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -64,7 +64,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* selectedPage(); - void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); + void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, QString downloadReason = "standalone"); void removeResource(const QString&); QList getTasks(); @@ -121,7 +121,10 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit ResourcePackDownloadDialog(QWidget* parent, + ResourcePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -138,7 +141,10 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resourcePacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit TexturePackDownloadDialog(QWidget* parent, + TexturePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -155,7 +161,10 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance, bool suppressInitialSearch = false); + explicit ShaderPackDownloadDialog(QWidget* parent, + ShaderPackFolderModel* shaders, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -172,7 +181,10 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* dataPacks, BaseInstance* instance, bool suppressInitialSearch = false); + explicit DataPackDownloadDialog(QWidget* parent, + DataPackFolderModel* dataPacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 99b01c35d..e229bd672 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -29,10 +29,12 @@ #include -static std::vector mcVersions(BaseInstance* inst) +namespace { +std::vector mcVersions(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } +} // namespace ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, @@ -58,8 +60,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata - auto went_well = ensureMetadata(); - if (!went_well) { + auto wentWell = ensureMetadata(); + if (!wentWell) { m_aborted = true; return; } @@ -73,12 +75,12 @@ void ResourceUpdateDialog::checkCandidates() text += tr("Mod name: %1
File name: %2
Reason: %3

").arg(mod->name(), mod->fileinfo().fileName(), reason); } - ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), - tr("Could not generate metadata for the following resources:
" - "Do you wish to proceed without those resources?"), - text); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Metadata generation failed"), + tr("Could not generate metadata for the following resources:
" + "Do you wish to proceed without those resources?"), + text); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; @@ -87,40 +89,41 @@ void ResourceUpdateDialog::checkCandidates() auto versions = mcVersions(m_instance); - SequentialTask check_task(tr("Checking for updates")); + SequentialTask checkTask(tr("Checking for updates")); if (!m_modrinthToUpdate.empty()) { m_modrinthCheckTask.reset(new ModrinthCheckUpdate(m_modrinthToUpdate, versions, m_loadersList, m_resourceModel)); connect(m_modrinthCheckTask.get(), &CheckUpdateTask::checkFailed, this, - [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); }); - check_task.addTask(m_modrinthCheckTask); + checkTask.addTask(m_modrinthCheckTask); } if (!m_flameToUpdate.empty()) { m_flameCheckTask.reset(new FlameCheckUpdate(m_flameToUpdate, versions, m_loadersList, m_resourceModel)); - connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); - }); - check_task.addTask(m_flameCheckTask); + connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); + }); + checkTask.addTask(m_flameCheckTask); } - connect(&check_task, &Task::failed, this, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(&checkTask, &Task::failed, this, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(&check_task, &Task::succeeded, this, [this, &check_task]() { - QStringList warnings = check_task.warnings(); + connect(&checkTask, &Task::succeeded, this, [this, &checkTask]() { + QStringList warnings = checkTask.warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); } }); // Check for updates - ProgressDialog progress_dialog(m_parent); - progress_dialog.setSkipButton(true, tr("Abort")); - progress_dialog.setWindowTitle(tr("Checking for updates...")); - auto ret = progress_dialog.execWithTask(&check_task); + ProgressDialog progressDialog(m_parent); + progressDialog.setSkipButton(true, tr("Abort")); + progressDialog.setWindowTitle(tr("Checking for updates...")); + auto ret = progressDialog.execWithTask(&checkTask); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { @@ -133,8 +136,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinthCheckTask) { - auto modrinth_updates = m_modrinthCheckTask->getUpdates(); - for (auto& updatable : modrinth_updates) { + auto modrinthUpdates = m_modrinthCheckTask->getUpdates(); + for (auto& updatable : modrinthUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -145,8 +148,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flameCheckTask) { - auto flame_updates = m_flameCheckTask->getUpdates(); - for (auto& updatable : flame_updates) { + auto flameUpdates = m_flameCheckTask->getUpdates(); + for (auto& updatable : flameUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -161,33 +164,35 @@ void ResourceUpdateDialog::checkCandidates() for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); - const auto& recover_url = std::get<2>(failed); + const auto& recoverUrl = std::get<2>(failed); qDebug() << mod->name() << "failed to check for updates!"; text += tr("Mod name: %1").arg(mod->name()) + "
"; - if (!reason.isEmpty()) + if (!reason.isEmpty()) { text += tr("Reason: %1").arg(reason) + "
"; - if (!recover_url.isEmpty()) + } + if (!recoverUrl.isEmpty()) { //: %1 is the link to download it manually text += tr("Possible solution: Getting the latest version manually:
%1
") - .arg(QString("%1").arg(recover_url.toString())); + .arg(QString("%1").arg(recoverUrl.toString())); + } text += "
"; } - ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), - tr("Could not check or get the following resources for updates:
" - "Do you wish to proceed without those resources?"), - text, "Disable unavailable mods"); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Failed to check for updates"), + tr("Could not check or get the following resources for updates:
" + "Do you wish to proceed without those resources?"), + text, "Disable unavailable mods"); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } // Disable unavailable mods - if (message_dialog.isOptionChecked()) { + if (messageDialog.isOptionChecked()) { for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); mod->enable(EnableAction::DISABLE); @@ -196,10 +201,10 @@ void ResourceUpdateDialog::checkCandidates() } if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto* mod_model = dynamic_cast(m_resourceModel); + auto* modModel = dynamic_cast(m_resourceModel); - if (mod_model != nullptr) { - auto depTask = makeShared(m_instance, mod_model, selectedVers); + if (modModel != nullptr) { + auto depTask = makeShared(m_instance, modModel, selectedVers); connect(depTask.get(), &Task::failed, this, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); @@ -215,10 +220,10 @@ void ResourceUpdateDialog::checkCandidates() } }); - ProgressDialog progress_dialog_deps(m_parent); - progress_dialog_deps.setSkipButton(true, tr("Abort")); - progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progress_dialog_deps.execWithTask(depTask.get()); + ProgressDialog progressDialogDeps(m_parent); + progressDialogDeps.setSkipButton(true, tr("Abort")); + progressDialogDeps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progressDialogDeps.execWithTask(depTask.get()); // If the dialog was skipped / some download error happened if (dret == QDialog::DialogCode::Rejected) { @@ -226,19 +231,20 @@ void ResourceUpdateDialog::checkCandidates() QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } - static FlameAPI api; + static FlameAPI s_api; auto dependencyExtraInfo = depTask->getExtraInfo(); for (const auto& dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) - changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_resourceModel); + if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) { + changelog = s_api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); + } + auto downloadTask = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed + changelog, dep->pack->provider, downloadTask, !extraInfo.maybe_installed }; appendResource(updatable, extraInfo.required_by); @@ -264,56 +270,58 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_aborted || m_noUpdates) + if (m_aborted || m_noUpdates) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); + } } // Part 1: Ensure we have a valid metadata auto ResourceUpdateDialog::ensureMetadata() -> bool { - auto index_dir = indexDir(); + auto indexDir2 = indexDir(); SequentialTask seq(tr("Looking for metadata")); // A better use of data structures here could remove the need for this QHash - QHash should_try_others; - QList modrinth_tmp; - QList flame_tmp; + QHash shouldTryOthers; + QList modrinthTmp; + QList flameTmp; - bool confirm_rest = false; - bool try_others_rest = false; - bool skip_rest = false; - ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; + bool confirmRest = false; + bool tryOthersRest = false; + bool skipRest = false; + ModPlatform::ResourceProvider providerRest = ModPlatform::ResourceProvider::MODRINTH; // adds resource to list based on provider - auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { + auto addToTmp = [&modrinthTmp, &flameTmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinth_tmp.push_back(resource); + modrinthTmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flame_tmp.push_back(resource); + flameTmp.push_back(resource); break; } }; // ask the user on what provider to seach for the mod first - for (auto candidate : m_candidates) { + for (auto* candidate : m_candidates) { if (candidate->status() != ResourceStatus::NO_METADATA) { onMetadataEnsured(candidate); continue; } - if (skip_rest) + if (skipRest) { continue; + } if (candidate->type() == ResourceType::FOLDER) { continue; } - if (confirm_rest) { - addToTmp(candidate, provider_rest); - should_try_others.insert(candidate->internal_id(), try_others_rest); + if (confirmRest) { + addToTmp(candidate, providerRest); + shouldTryOthers.insert(candidate->internal_id(), tryOthersRest); continue; } @@ -326,68 +334,73 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto response = chooser.getResponse(); - if (response.skip_all) - skip_rest = true; + if (response.skip_all) { + skipRest = true; + } if (response.confirm_all) { - confirm_rest = true; - provider_rest = response.chosen; - try_others_rest = response.try_others; + confirmRest = true; + providerRest = response.chosen; + tryOthersRest = response.try_others; } - should_try_others.insert(candidate->internal_id(), response.try_others); + shouldTryOthers.insert(candidate->internal_id(), response.try_others); - if (confirmed) + if (confirmed) { addToTmp(candidate, response.chosen); + } } // prepare task for the modrinth mods - if (!modrinth_tmp.empty()) { - auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); + if (!modrinthTmp.empty()) { + auto modrinthTask = makeShared(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); }); - connect(modrinth_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(modrinthTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (modrinth_task->getHashingTask()) - seq.addTask(modrinth_task->getHashingTask()); + if (modrinthTask->getHashingTask()) { + seq.addTask(modrinthTask->getHashingTask()); + } - seq.addTask(modrinth_task); + seq.addTask(modrinthTask); } // prepare task for the flame mods - if (!flame_tmp.empty()) { - auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); + if (!flameTmp.empty()) { + auto flameTask = makeShared(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME); + connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); }); - connect(flame_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(flameTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (flame_task->getHashingTask()) - seq.addTask(flame_task->getHashingTask()); + if (flameTask->getHashingTask()) { + seq.addTask(flameTask->getHashingTask()); + } - seq.addTask(flame_task); + seq.addTask(flameTask); } seq.addTask(m_secondTryMetadata); // execute all the tasks - ProgressDialog checking_dialog(m_parent); - checking_dialog.setSkipButton(true, tr("Abort")); - checking_dialog.setWindowTitle(tr("Generating metadata...")); - auto ret_metadata = checking_dialog.execWithTask(&seq); + ProgressDialog checkingDialog(m_parent); + checkingDialog.setSkipButton(true, tr("Abort")); + checkingDialog.setWindowTitle(tr("Generating metadata...")); + auto retMetadata = checkingDialog.execWithTask(&seq); - return (ret_metadata != QDialog::DialogCode::Rejected); + return (retMetadata != QDialog::DialogCode::Rejected); } void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!resource->metadata()) + if (!resource->metadata()) { return; + } switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: @@ -411,12 +424,12 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice) { - if (try_others) { - auto index_dir = indexDir(); + if (tryOthers) { + auto indexDir2 = indexDir(); - auto task = makeShared(resource, index_dir, next(first_choice)); + auto task = makeShared(resource, indexDir2, next(firstChoice)); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::failed, @@ -436,57 +449,57 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, } } -void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy) { - auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + auto* itemTop = new QTreeWidgetItem(ui->modTreeWidget); + itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); if (!info.enabled) { - item_top->setToolTip(0, tr("Mod was disabled as it may be already installed.")); + itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } - item_top->setText(0, info.name); - item_top->setExpanded(true); + itemTop->setText(0, info.name); + itemTop->setExpanded(true); - auto provider_item = new QTreeWidgetItem(item_top); - QString provider_name = ModPlatform::ProviderCapabilities::readableName(info.provider); - provider_item->setText(0, tr("Provider: %1").arg(provider_name)); - provider_item->setData(0, Qt::UserRole, provider_name); + auto* providerItem = new QTreeWidgetItem(itemTop); + QString providerName = ModPlatform::ProviderCapabilities::readableName(info.provider); + providerItem->setText(0, tr("Provider: %1").arg(providerName)); + providerItem->setData(0, Qt::UserRole, providerName); - auto old_version_item = new QTreeWidgetItem(item_top); - old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); - old_version_item->setData(0, Qt::UserRole, info.old_version); + auto* oldVersionItem = new QTreeWidgetItem(itemTop); + oldVersionItem->setText(0, tr("Old version: %1").arg(info.oldVersion)); + oldVersionItem->setData(0, Qt::UserRole, info.oldVersion); - auto new_version_item = new QTreeWidgetItem(item_top); - new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); - new_version_item->setData(0, Qt::UserRole, info.new_version); + auto* newVersionItem = new QTreeWidgetItem(itemTop); + newVersionItem->setText(0, tr("New version: %1").arg(info.newVersion)); + newVersionItem->setData(0, Qt::UserRole, info.newVersion); - if (info.new_version_type.has_value()) { - auto new_version_type_item = new QTreeWidgetItem(item_top); - new_version_type_item->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); - new_version_type_item->setData(0, Qt::UserRole, info.new_version_type.value().toString()); + if (info.newVersionType.has_value()) { + auto* newVersionTypeItem = new QTreeWidgetItem(itemTop); + newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.newVersionType.value().toString())); + newVersionTypeItem->setData(0, Qt::UserRole, info.newVersionType.value().toString()); } if (!requiredBy.isEmpty()) { - auto requiredByItem = new QTreeWidgetItem(item_top); + auto* requiredByItem = new QTreeWidgetItem(itemTop); if (requiredBy.length() == 1) { requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back())); requiredByItem->setData(0, Qt::UserRole, requiredBy.back()); } else { requiredByItem->setText(0, tr("Required by:")); - for (auto req : requiredBy) { - auto reqItem = new QTreeWidgetItem(requiredByItem); + for (const auto& req : requiredBy) { + auto* reqItem = new QTreeWidgetItem(requiredByItem); reqItem->setText(0, req); } } ui->toggleDepsButton->show(); - m_deps << item_top; + m_deps << itemTop; } - auto changelog_item = new QTreeWidgetItem(item_top); - changelog_item->setText(0, tr("Changelog of the latest version")); + auto* changelogItem = new QTreeWidgetItem(itemTop); + changelogItem->setText(0, tr("Changelog of the latest version")); - auto changelog = new QTreeWidgetItem(changelog_item); - auto changelog_area = new QTextBrowser(); + auto* changelog = new QTreeWidgetItem(changelogItem); + auto* changelogArea = new QTextBrowser(); QString text = info.changelog; changelog->setData(0, Qt::UserRole, text); @@ -494,14 +507,14 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q text = markdownToHTML(info.changelog.toUtf8()); } - changelog_area->setHtml(StringUtils::htmlListPatch(text)); - changelog_area->setOpenExternalLinks(true); - changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); - changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + changelogArea->setHtml(StringUtils::htmlListPatch(text)); + changelogArea->setOpenExternalLinks(true); + changelogArea->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); + changelogArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); + ui->modTreeWidget->setItemWidget(changelog, 0, changelogArea); - ui->modTreeWidget->addTopLevelItem(item_top); + ui->modTreeWidget->addTopLevelItem(itemTop); } auto ResourceUpdateDialog::getTasks() -> const QList diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index ea81aeb7a..9de1ee246 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -39,7 +39,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Resource* resource); void onMetadataFailed(Resource* resource, - bool try_others = false, + bool tryOthers = false, ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH); private: diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 706d35378..1cdce5e33 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -50,7 +50,6 @@ #include "ResourceDownloadTask.h" #include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "ui/dialogs/ResourceDownloadDialog.h" @@ -63,15 +62,15 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa void ModPage::setFilterWidget(std::unique_ptr& widget) { - if (m_filter_widget) + if (m_filter_widget) { disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); - - auto old = m_ui->splitter->replaceWidget(0, widget.get()); - // because we replaced the widget we also need to delete it - if (old) { - delete old; } + auto* old = m_ui->splitter->replaceWidget(0, widget.get()); + // because we replaced the widget we also need to delete it + + delete old; + m_filter_widget.swap(widget); m_filter = m_filter_widget->getFilter(); @@ -112,10 +111,13 @@ QMap ModPage::urlHandlers() const /******** Make changes to the UI ********/ -void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model) +void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index a69ee53f7..7f75995a3 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -29,10 +29,10 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); - auto filter_widget = page->createFilterWidget(); - page->setFilterWidget(filter_widget); + auto filterWidget = page->createFilterWidget(); + page->setFilterWidget(filterWidget); model->setFilter(page->getFilter()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); @@ -43,18 +43,21 @@ class ModPage : public ResourcePage { } //: The plural version of 'mod' - inline QString resourcesString() const override { return tr("mods"); } + QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' - inline QString resourceString() const override { return tr("mod"); } + QString resourceString() const override { return tr("mod"); } QMap urlHandlers() const override; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; virtual std::unique_ptr createFilterWidget() = 0; bool supportsFiltering() const override { return true; }; - auto getFilter() const -> const std::shared_ptr { return m_filter; } + auto getFilter() const -> std::shared_ptr { return m_filter; } void setFilterWidget(std::unique_ptr&); protected: diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index a3a043ba9..edd8564b6 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -12,10 +12,11 @@ #include #include #include +#include #include "Application.h" -#include "settings/SettingsObject.h" #include "BuildConfig.h" +#include "settings/SettingsObject.h" #include "modplatform/ResourceAPI.h" #include "net/ApiDownload.h" @@ -29,7 +30,7 @@ namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) +ResourceModel::ResourceModel(ResourceAPI* api) : m_api(api) { s_running_models.insert(this, true); if (APPLICATION_DYN) { @@ -62,14 +63,14 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { if (APPLICATION_DYN) { - if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); - icon_or_none.has_value()) - return icon_or_none.value(); + if (auto iconOrNone = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + iconOrNone.has_value()) { + return iconOrNone.value(); + } return QIcon::fromTheme("screenshot-placeholder"); - } else { - return {}; } + return {}; } case Qt::SizeHintRole: return QSize(0, 58); @@ -112,8 +113,9 @@ QHash ResourceModel::roleNames() const bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { return false; + } m_packs[pos] = value.value(); emit dataChanged(index, index); @@ -128,45 +130,51 @@ QString ResourceModel::debugName() const void ResourceModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid() || m_search_state == SearchState::Finished) + if (parent.isValid() || m_search_state == SearchState::Finished) { return; + } search(); } void ResourceModel::search() { - if (hasActiveSearchJob()) + if (hasActiveSearchJob()) { return; + } if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - if (network_error_code == 404) { + } + if (networkErrorCode == 404) { m_search_state = SearchState::ResetRequested; } - searchRequestFailed(reason, network_error_code); + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; callbacks.on_succeed = [this](auto& pack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestForOneSucceeded(pack); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) + if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) { runSearchJob(job); + } return; } } @@ -175,31 +183,36 @@ void ResourceModel::search() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestSucceeded(doc); }; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - searchRequestFailed(reason, network_error_code); + } + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; - if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) { runSearchJob(job); + } } void ResourceModel::loadEntry(const QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; + const auto& pack = m_packs[entry.row()]; - if (!hasActiveInfoJob()) + if (!hasActiveInfoJob()) { m_current_info_job.clear(); + } if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; @@ -207,20 +220,24 @@ void ResourceModel::loadEntry(const QModelIndex& entry) auto addonId = pack->addonId; // Use default if no callbacks are set - if (!callbacks.on_succeed) + if (!callbacks.on_succeed) { callbacks.on_succeed = [this, entry, addonId](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } versionRequestSucceeded(doc, addonId, entry); }; - if (!callbacks.on_fail) - callbacks.on_fail = [](QString reason, int) { + } + if (!callbacks.on_fail) { + callbacks.on_fail = [](const QString& reason, int) { QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions: %1").arg(reason)); }; + } - if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } if (!pack->extraDataLoaded) { @@ -228,41 +245,45 @@ void ResourceModel::loadEntry(const QModelIndex& entry) ResourceAPI::Callback callbacks{}; callbacks.on_succeed = [this, entry](auto& newpack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } infoRequestSucceeded(newpack, entry); }; - callbacks.on_fail = [this](QString reason, int) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](const QString& reason, int) { + if (!s_running_models.constFind(this).value()) { return; + } QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } qCritical() << tr("The request was aborted for an unknown reason"); }; - if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } } void ResourceModel::refresh() { - bool reset_requested = false; + bool resetRequested = false; if (hasActiveInfoJob()) { m_current_info_job.abort(); - reset_requested = true; + resetRequested = true; } if (hasActiveSearchJob()) { m_current_search_job->abort(); - reset_requested = true; + resetRequested = true; } - if (reset_requested) { + if (resetRequested) { m_search_state = SearchState::ResetRequested; return; } @@ -288,13 +309,15 @@ void ResourceModel::runSearchJob(Task::Ptr ptr) } void ResourceModel::runInfoJob(Task::Ptr ptr) { - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.clear(); + } - m_current_info_job.addTask(ptr); + m_current_info_job.addTask(std::move(ptr)); - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.run(); + } } std::optional ResourceModel::getCurrentSortingMethodByIndex() const @@ -302,11 +325,12 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional sort{}; { // Find sorting method by ID - auto sorting_methods = getSortingMethods(); - auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(), - [this](auto const& e) { return m_current_sort_index == e.index; }); - if (method != sorting_methods.constEnd()) + auto sortingMethods = getSortingMethods(); + auto method = std::find_if(sortingMethods.constBegin(), sortingMethods.constEnd(), + [this](const auto& e) { return m_current_sort_index == e.index; }); + if (method != sortingMethods.constEnd()) { sort = *method; + } } return sort; @@ -315,43 +339,47 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) { QPixmap pixmap; - if (QPixmapCache::find(url.toString(), &pixmap)) + if (QPixmapCache::find(url.toString(), &pixmap)) { return { pixmap }; + } if (!m_current_icon_job) { m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); m_current_icon_job->setAskRetry(false); } - if (m_currently_running_icon_actions.contains(url)) + if (m_currently_running_icon_actions.contains(url)) { return {}; - if (m_failed_icon_actions.contains(url)) + } + if (m_failed_icon_actions.contains(url)) { return {}; + } - auto cache_entry = APPLICATION->metacache()->resolveEntry( + auto cacheEntry = APPLICATION->metacache()->resolveEntry( metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); - auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); + auto iconFetchAction = Net::ApiDownload::makeCached(url, cacheEntry); - auto full_file_path = cache_entry->getFullPath(); - connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] { - auto icon = QIcon(full_file_path); + auto fullFilePath = cacheEntry->getFullPath(); + connect(iconFetchAction.get(), &Task::succeeded, this, [this, url, fullFilePath, index] { + auto icon = QIcon(fullFilePath); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); m_currently_running_icon_actions.remove(url); emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(icon_fetch_action.get(), &Task::failed, this, [this, url] { + connect(iconFetchAction.get(), &Task::failed, this, [this, url] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); m_currently_running_icon_actions.insert(url); - m_current_icon_job->addNetAction(icon_fetch_action); - if (!m_current_icon_job->isRunning()) + m_current_icon_job->addNetAction(iconFetchAction); + if (!m_current_icon_job->isRunning()) { QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + } return {}; } @@ -363,11 +391,11 @@ void ResourceModel::searchRequestSucceeded(QList& QList filteredNewList; for (auto pack : newList) { ModPlatform::IndexedPack::Ptr p; - if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), - [&pack](const DownloadTaskPtr i) { - const auto ipack = i->getPack(); - return ipack->provider == pack->provider && ipack->addonId == pack->addonId; - }); + if (auto sel = std::ranges::find_if(m_selected, + [&pack](const DownloadTaskPtr& i) { + const auto ipack = i->getPack(); + return ipack->provider == pack->provider && ipack->addonId == pack->addonId; + }); sel != m_selected.end()) { p = sel->get()->getPack(); } else { @@ -386,8 +414,9 @@ void ResourceModel::searchRequestSucceeded(QList& } // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (filteredNewList.size() == 0) + if (filteredNewList.size() == 0) { return; + } beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1); m_packs.append(filteredNewList); @@ -403,9 +432,9 @@ void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr p endInsertRows(); } -void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int networkErrorCode) { - switch (network_error_code) { + switch (networkErrorCode) { default: // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); @@ -432,8 +461,9 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net void ResourceModel::searchRequestAborted() { - if (m_search_state != SearchState::ResetRequested) + if (m_search_state != SearchState::ResetRequested) { qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + } // Retry fetching clearData(); @@ -444,19 +474,20 @@ void ResourceModel::searchRequestAborted() void ResourceModel::versionRequestSucceeded(QVector& doc, QVariant pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack != current_pack->addonId) + if (pack != currentPack->addonId) { return; + } - current_pack->versions = doc; - current_pack->versionsLoaded = true; + currentPack->versions = doc; + currentPack->versionsLoaded = true; // Cache info :^) - QVariant new_pack; - new_pack.setValue(current_pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(currentPack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource versions!"; return; } @@ -466,16 +497,17 @@ void ResourceModel::versionRequestSucceeded(QVector void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack->addonId != current_pack->addonId) + if (pack->addonId != currentPack->addonId) { return; + } // Cache info :^) - QVariant new_pack; - new_pack.setValue(pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(pack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource info!"; return; } @@ -486,15 +518,16 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed) + bool isIndexed, + QString downloadReason) { version.is_currently_selected = true; - m_selected.append(makeShared(pack, version, packs, is_indexed)); + m_selected.append(makeShared(std::move(pack), version, packs, isIndexed, std::move(downloadReason))); } void ResourceModel::removePack(const QString& rem) { - auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); }; + auto pred = [&rem](const DownloadTaskPtr& i) { return rem == i->getName(); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_selected.removeIf(pred); #else @@ -506,15 +539,16 @@ void ResourceModel::removePack(const QString& rem) ++it; } #endif - auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; }); + auto pack = std::ranges::find_if(m_packs, [&rem](const ModPlatform::IndexedPack::Ptr& i) { return rem == i->name; }); if (pack == m_packs.end()) { // ignore it if is not in the current search return; } if (!pack->get()->versionsLoaded) { return; } - for (auto& ver : pack->get()->versions) + for (auto& ver : pack->get()->versions) { ver.is_currently_selected = false; + } } bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 573ad8b75..9124c0e66 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -36,16 +36,16 @@ class ResourceModel : public QAbstractListModel { ResourceModel(ResourceAPI* api); ~ResourceModel() override; - auto data(const QModelIndex&, int role) const -> QVariant override; + auto data(const QModelIndex& /*index*/, int role) const -> QVariant override; auto roleNames() const -> QHash override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; virtual auto debugName() const -> QString; virtual auto metaEntryBase() const -> QString = 0; - inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } - inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } + int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } + int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } @@ -66,7 +66,7 @@ class ResourceModel : public QAbstractListModel { public slots: void fetchMore(const QModelIndex& parent) override; - inline bool canFetchMore(const QModelIndex& parent) const override + bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -94,7 +94,8 @@ class ResourceModel : public QAbstractListModel { void addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed = false); + bool isIndexed = false, + QString downloadReason = "standalone"); void removePack(const QString& rem); QList selectedPacks() { return m_selected; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 931b4a311..8baa0bbec 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -395,10 +395,13 @@ void ResourcePage::removeResourceFromDialog(const QString& packName) m_parentDialog->removeResource(packName); } -void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* baseModel) +void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& ver, + ResourceFolderModel* baseModel, + QString downloadReason) { bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(std::move(pack), ver, baseModel, isIndexed); + m_model->addPack(std::move(pack), ver, baseModel, isIndexed, std::move(downloadReason)); } void ResourcePage::modelReset() diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index c11edb1d7..03895dcd4 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -9,7 +9,6 @@ #include "ResourceDownloadTask.h" #include "modplatform/ModIndex.h" -#include "modplatform/ResourceAPI.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ResourceModel.h" @@ -78,7 +77,10 @@ class ResourcePage : public QWidget, public BasePage { void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); void removeResourceFromDialog(const QString& packName); virtual void removeResourceFromPage(const QString& name); - virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, + ModPlatform::IndexedVersion&, + ResourceFolderModel*, + QString downloadReason = "standalone"); virtual void modelReset(); diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 99c50352a..ec5fe7967 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -36,18 +36,18 @@ QMap ShaderPackResourcePage::urlHandlers() const { QMap map; map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth"); - map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), - "curseforge"); - map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"((?:www\.)?curseforge\.com\/minecraft\/customization\/([^\/]+)\/?)"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"(minecraft\.curseforge\.com\/projects\/([^\/]+)\/?)"), "curseforge"); return map; } void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* base_model) + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 92ddd9f8a..37e3cadef 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -23,7 +23,7 @@ class ShaderPackResourcePage : public ResourcePage { static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); @@ -33,17 +33,20 @@ class ShaderPackResourcePage : public ResourcePage { } //: The plural version of 'shader pack' - inline QString resourcesString() const override { return tr("shader packs"); } + QString resourcesString() const override { return tr("shader packs"); } //: The singular version of 'shader packs' - inline QString resourceString() const override { return tr("shader pack"); } + QString resourceString() const override { return tr("shader pack"); } bool supportsFiltering() const override { return false; }; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; QMap urlHandlers() const override; - inline auto helpPage() const -> QString override { return "shaderpack-platform"; } + auto helpPage() const -> QString override { return "shaderpack-platform"; } protected: ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);