diff options
| author | Sefa Eyeoglu <contact@scrumplex.net> | 2023-08-06 21:54:00 +0200 | 
|---|---|---|
| committer | Sefa Eyeoglu <contact@scrumplex.net> | 2023-08-06 21:54:00 +0200 | 
| commit | 74fe2fb2a6282a9292cc912b865ce0179dbc3412 (patch) | |
| tree | d006abece962a65d3538b9b0d8f4e3d264d59eca /launcher/modplatform | |
| parent | a83e5be8f2acd66f83ad181e54fe688ed08c1b6f (diff) | |
| parent | efaf4024ab22a53a3ef05f0a41b746b7561e087c (diff) | |
| download | PrismLauncher-74fe2fb2a6282a9292cc912b865ce0179dbc3412.tar.gz PrismLauncher-74fe2fb2a6282a9292cc912b865ce0179dbc3412.tar.bz2 PrismLauncher-74fe2fb2a6282a9292cc912b865ce0179dbc3412.zip | |
Merge remote-tracking branch 'upstream/staging' into curseforge-url-handle
Diffstat (limited to 'launcher/modplatform')
26 files changed, 810 insertions, 343 deletions
| diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 93b5ce76..c3eadd06 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -145,7 +145,8 @@ void EnsureMetadataTask::executeTask()          connect(project_task.get(), &Task::finished, this, [=] {              invalidade_leftover();              project_task->deleteLater(); -            m_current_task = nullptr; +            if (m_current_task) +                m_current_task.reset();          });          m_current_task = project_task; @@ -154,7 +155,8 @@ void EnsureMetadataTask::executeTask()      connect(version_task.get(), &Task::finished, [=] {          version_task->deleteLater(); -        m_current_task = nullptr; +        if (m_current_task) +            m_current_task.reset();      });      if (m_mods.size() > 1) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 22ea02da..8260a25c 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -53,6 +53,8 @@  #include "meta/Version.h"  #include "meta/VersionList.h" +#include "net/ApiDownload.h" +  #include "BuildConfig.h"  #include "Application.h" @@ -82,9 +84,9 @@ void PackInstallTask::executeTask()  {      qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();      NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; -    auto searchUrl = +    auto searchUrl =           QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));      QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);      QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); @@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()      auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);      entry->setStale(true); -    auto dl = Net::Download::makeCached(url, entry); +    auto dl = Net::ApiDownload::makeCached(url, entry);      if (!m_version.configs.sha1.isEmpty()) {          auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());          dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); @@ -684,7 +686,7 @@ void PackInstallTask::installConfigs()          abortable = true;          setProgress(current, total);      }); -    connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); +    connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);      connect(jobPtr.get(), &NetJob::aborted, [&]{          abortable = false;          jobPtr.reset(); @@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()              entry->setStale(true);              modsToExtract.insert(entry->getFullPath(), mod); -            auto dl = Net::Download::makeCached(url, entry); +            auto dl = Net::ApiDownload::makeCached(url, entry);              if (!mod.md5.isEmpty()) {                  auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());                  dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); @@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()              entry->setStale(true);              modsToDecomp.insert(entry->getFullPath(), mod); -            auto dl = Net::Download::makeCached(url, entry); +            auto dl = Net::ApiDownload::makeCached(url, entry);              if (!mod.md5.isEmpty()) {                  auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());                  dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); @@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()              auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);              entry->setStale(true); -            auto dl = Net::Download::makeCached(url, entry); +            auto dl = Net::ApiDownload::makeCached(url, entry);              if (!mod.md5.isEmpty()) {                  auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());                  dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); @@ -852,7 +854,7 @@ void PackInstallTask::downloadMods()          abortable = true;          setProgress(current, total);      }); -    connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); +    connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);      connect(jobPtr.get(), &NetJob::aborted, [&]      {          abortable = false; diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index ce7a6055..fcf8517f 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -1,7 +1,9 @@  #include "FileResolvingTask.h"  #include "Json.h" +#include "net/ApiUpload.h"  #include "net/Upload.h" +#include "net/ApiDownload.h"  #include "modplatform/modrinth/ModrinthPackIndex.h" @@ -21,6 +23,10 @@ bool Flame::FileResolvingTask::abort()  void Flame::FileResolvingTask::executeTask()  { +    if (m_toProcess.files.isEmpty()) {  // no file to resolve so leave it empty and emit success immediately +        emitSucceeded(); +        return; +    }      setStatus(tr("Resolving mod IDs..."));      setProgress(0, 3);      m_dljob.reset(new NetJob("Mod id resolver", m_network)); @@ -34,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()              return l;          }));      QByteArray data = Json::toText(object); -    auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data); +    auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);      m_dljob->addNetAction(dl);      auto step_progress = std::make_shared<TaskStepProgress>(); @@ -48,7 +54,7 @@ void Flame::FileResolvingTask::executeTask()          stepProgress(*step_progress);          emitFailed(reason);      }); -    connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); +    connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);      connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {          qDebug() << "Resolve slug progress" << current << total;          step_progress->update(current, total); @@ -95,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()              if (!hash.isEmpty()) {                  auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);                  auto output = std::make_shared<QByteArray>(); -                auto dl = Net::Download::makeByteArray(QUrl(url), output); +                auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);                  QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });                  m_checkJob->addNetAction(dl); @@ -114,7 +120,7 @@ void Flame::FileResolvingTask::netJobFinished()          stepProgress(*step_progress);          emitFailed(reason);      }); -    connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); +    connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);      connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {          qDebug() << "Resolve slug progress" << current << total;          step_progress->update(current, total); @@ -128,12 +134,13 @@ void Flame::FileResolvingTask::netJobFinished()      m_checkJob->start();  } -void Flame::FileResolvingTask::modrinthCheckFinished() { +void Flame::FileResolvingTask::modrinthCheckFinished() +{      setProgress(2, 3);      qDebug() << "Finished with blocked mods : " << blockedProjects.size();      for (auto it = blockedProjects.keyBegin(); it != blockedProjects.keyEnd(); it++) { -        auto &out = *it; +        auto& out = *it;          auto bytes = blockedProjects[out];          if (!out->resolved) {              continue; @@ -153,28 +160,26 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {              out->resolved = false;          }      } -    //copy to an output list and filter out projects found on modrinth +    // copy to an output list and filter out projects found on modrinth      auto block = std::make_shared<QList<File*>>();      auto it = blockedProjects.keys(); -    std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) { -        return !f->resolved; -    }); -    //Display not found mods early +    std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File* f) { return !f->resolved; }); +    // Display not found mods early      if (!block->empty()) { -        //blocked mods found, we need the slug for displaying.... we need another job :D ! +        // blocked mods found, we need the slug for displaying.... we need another job :D !          m_slugJob.reset(new NetJob("Slug Job", m_network));          int index = 0;          for (auto mod : *block) {              auto projectId = mod->projectId;              auto output = std::make_shared<QByteArray>();              auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); -            auto dl = Net::Download::makeByteArray(url, output); +            auto dl = Net::ApiDownload::makeByteArray(url, output);              qDebug() << "Fetching url slug for file:" << mod->fileName;              QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {                  auto mod = block->at(index);  // use the shared_ptr so it is captured and only freed when we are done                  auto json = QJsonDocument::fromJson(*output); -                auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"), -                        "websiteUrl"); +                auto base = +                    Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json), "data"), "links"), "websiteUrl");                  auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));                  mod->websiteUrl = link;              }); @@ -192,7 +197,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {              stepProgress(*step_progress);              emitFailed(reason);          }); -        connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress); +        connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propagateStepProgress);          connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {              qDebug() << "Resolve slug progress" << current << total;              step_progress->update(current, total); diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 485d6736..2871f06a 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -8,7 +8,9 @@  #include "Application.h"  #include "BuildConfig.h"  #include "Json.h" +#include "net/ApiUpload.h"  #include "net/NetJob.h" +#include "net/ApiDownload.h"  #include "net/Upload.h"  Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response) @@ -26,7 +28,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); +    netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));      return netJob;  } @@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString      auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());      auto response = std::make_shared<QByteArray>(); -    netJob->addNetAction(Net::Download::makeByteArray( +    netJob->addNetAction(Net::ApiDownload::makeByteArray(          QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")              .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),          response)); @@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString      auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());      auto response = std::make_shared<QByteArray>();      netJob->addNetAction( -        Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response)); +        Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));      QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {          QJsonParseError parse_error{}; @@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe      auto response = std::make_shared<QByteArray>();      ModPlatform::IndexedVersion ver; -    netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));      QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {          QJsonParseError parse_error{}; @@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); +    netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));      QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); @@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); +    netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));      QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index a2628e34..e10fedc3 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -13,6 +13,8 @@  #include "minecraft/mod/ModFolderModel.h"  #include "minecraft/mod/ResourceFolderModel.h" +#include "net/ApiDownload.h" +  static FlameAPI api;  bool FlameCheckUpdate::abort() @@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)      auto response = std::make_shared<QByteArray>();      auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString()); -    auto dl = Net::Download::makeByteArray(url, response); +    auto dl = Net::ApiDownload::makeByteArray(url, response);      get_project_job->addNetAction(dl);      QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() { @@ -77,7 +79,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)      auto response = std::make_shared<QByteArray>();      auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId)); -    auto dl = Net::Download::makeByteArray(url, response); +    auto dl = Net::ApiDownload::makeByteArray(url, response);      get_file_info_job->addNetAction(dl);      QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() { diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index e7641d64..c170a4f5 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -57,14 +57,11 @@  #include <QDebug>  #include <QFileInfo> +#include "meta/Index.h" +#include "meta/VersionList.h"  #include "minecraft/World.h"  #include "minecraft/mod/tasks/LocalResourceParse.h" - - -const static QMap<QString, QString> forgemap = { { "1.2.5", "3.4.9.171" }, -                                                 { "1.4.2", "6.0.1.355" }, -                                                 { "1.4.7", "6.6.2.534" }, -                                                 { "1.5.2", "7.8.1.737" } }; +#include "net/ApiDownload.h"  static const FlameAPI api; @@ -259,6 +256,56 @@ bool FlameCreationTask::updateInstance()      return false;  } +QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType, QString loaderVersion, QString mcVersion) +{ +    if (loaderVersion == "recommended") { +        auto vlist = APPLICATION->metadataIndex()->get(uid); +        if (!vlist) { +            setError(tr("Failed to get local metadata index for %1").arg(uid)); +            return {}; +        } + +        if (!vlist->isLoaded()) { +            QEventLoop loadVersionLoop; +            auto task = vlist->getLoadTask(); +            connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit); +            if (!task->isRunning()) +                task->start(); + +            loadVersionLoop.exec(); +        } + +        for (auto version : vlist->versions()) { +            // first recommended build we find, we use. +            if (!version->isRecommended()) +                continue; +            auto reqs = version->requiredSet(); + +            // filter by minecraft version, if the loader depends on a certain version. +            // not all mod loaders depend on a given Minecraft version, so we won't do this +            // filtering for those loaders. +            if (loaderType == "forge") { +                auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) { +                    return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; +                }); +                if (iter == reqs.end()) +                    continue; +            } +            return version->descriptor(); +        } + +        setError(tr("Failed to find version for %1 loader").arg(loaderType)); +        return {}; +    } + +    if (loaderVersion.isEmpty()) { +        emitFailed(tr("No loader version set for modpack!")); +        return {}; +    } + +    return loaderVersion; +} +  bool FlameCreationTask::createInstance()  {      QEventLoop loop; @@ -297,22 +344,29 @@ bool FlameCreationTask::createInstance()          }      } -    QString forgeVersion; -    QString fabricVersion; -    // TODO: is Quilt relevant here? +    QString loaderType; +    QString loaderUid; +    QString loaderVersion; +      for (auto& loader : m_pack.minecraft.modLoaders) {          auto id = loader.id;          if (id.startsWith("forge-")) {              id.remove("forge-"); -            forgeVersion = id; -            continue; -        } -        if (id.startsWith("fabric-")) { +            loaderType = "forge"; +            loaderUid = "net.minecraftforge"; +        } else if (loaderType == "fabric") {              id.remove("fabric-"); -            fabricVersion = id; +            loaderType = "fabric"; +            loaderUid = "net.fabricmc.fabric-loader"; +        } else if (loaderType == "quilt") { +            id.remove("quilt-"); +            loaderType = "quilt"; +            loaderUid = "org.quiltmc.quilt-loader"; +        } else { +            logWarning(tr("Unknown mod loader in manifest: %1").arg(id));              continue;          } -        logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); +        loaderVersion = id;      }      QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); @@ -329,19 +383,12 @@ bool FlameCreationTask::createInstance()      auto components = instance.getPackProfile();      components->buildingFromScratch();      components->setComponentVersion("net.minecraft", mcVersion, true); -    if (!forgeVersion.isEmpty()) { -        // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. -        if (forgeVersion == "recommended") { -            if (forgemap.contains(mcVersion)) { -                forgeVersion = forgemap[mcVersion]; -            } else { -                logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); -            } -        } -        components->setComponentVersion("net.minecraftforge", forgeVersion); +    if (!loaderType.isEmpty()) { +        auto version = getVersionForLoader(loaderUid, loaderType, loaderVersion, mcVersion); +        if (version.isEmpty()) +            return false; +        components->setComponentVersion(loaderUid, version);      } -    if (!fabricVersion.isEmpty()) -        components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);      if (m_instIcon != "default") {          instance.setIconKey(m_instIcon); @@ -386,7 +433,7 @@ bool FlameCreationTask::createInstance()      });      connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);      connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); -    connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress); +    connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);      connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);      m_mod_id_resolver->start(); @@ -477,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)              case Flame::File::Type::Mod: {                  if (!result.url.isEmpty()) {                      qDebug() << "Will download" << result.url << "to" << path; -                    auto dl = Net::Download::makeFile(result.url, path); +                    auto dl = Net::ApiDownload::makeFile(result.url, path);                      m_files_job->addNetAction(dl);                  }                  break; @@ -502,11 +549,11 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)          m_files_job.reset();          setError(reason);      }); -    connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){ +    connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {          setDetails(tr("%1 out of %2 complete").arg(current).arg(total));          setProgress(current, total);      }); -    connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress); +    connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);      connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);      setStatus(tr("Downloading mods...")); @@ -545,7 +592,6 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)      setAbortable(true);  } -  void FlameCreationTask::validateZIPResouces()  {      qDebug() << "Validating whether resources stored as .zip are in the right place"; @@ -563,11 +609,13 @@ void FlameCreationTask::validateZIPResouces()                  if (FS::move(localPath, destPath)) {                      return destPath;                  } +            } else { +                qDebug() << "Target folder of" << fileName << "is correct at" << targetFolder;              }              return localPath;          }; -        auto installWorld = [this](QString worldPath){ +        auto installWorld = [this](QString worldPath) {              qDebug() << "Installing World from" << worldPath;              QFileInfo worldFileInfo(worldPath);              World w(worldFileInfo); @@ -584,29 +632,29 @@ void FlameCreationTask::validateZIPResouces()          QString worldPath;          switch (type) { -            case PackedResourceType::ResourcePack : +            case PackedResourceType::Mod: +                validatePath(fileName, targetFolder, "mods"); +                break; +            case PackedResourceType::ResourcePack:                  validatePath(fileName, targetFolder, "resourcepacks");                  break; -            case PackedResourceType::TexturePack : +            case PackedResourceType::TexturePack:                  validatePath(fileName, targetFolder, "texturepacks");                  break; -            case PackedResourceType::DataPack : +            case PackedResourceType::DataPack:                  validatePath(fileName, targetFolder, "datapacks");                  break; -            case PackedResourceType::Mod : -                validatePath(fileName, targetFolder, "mods"); -                break; -            case PackedResourceType::ShaderPack : +            case PackedResourceType::ShaderPack:                  // in theroy flame API can't do this but who knows, that *may* change ?                  // better to handle it if it *does* occure in the future                  validatePath(fileName, targetFolder, "shaderpacks");                  break; -            case PackedResourceType::WorldSave : +            case PackedResourceType::WorldSave:                  worldPath = validatePath(fileName, targetFolder, "saves");                  installWorld(worldPath);                  break; -            case PackedResourceType::UNKNOWN : -            default : +            case PackedResourceType::UNKNOWN: +            default:                  qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";                  break;          } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 0ae4735b..603d3693 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -57,10 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask {                        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)) +        : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))      {          setStagingPath(staging_path);          setParentSettings(global_settings); @@ -78,6 +75,7 @@ class FlameCreationTask final : public InstanceCreationTask {      void setupDownloadJob(QEventLoop&);      void copyBlockedMods(QList<BlockedMod> const& blocked_mods);      void validateZIPResouces(); +    QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);     private:      QWidget* m_parent = nullptr; diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp index ac0da214..f5f3af37 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.cpp +++ b/launcher/modplatform/flame/FlamePackExportTask.cpp @@ -26,6 +26,7 @@  #include <QMessageBox>  #include <QtConcurrentRun>  #include <algorithm> +#include <iterator>  #include <memory>  #include "Json.h"  #include "MMCZip.h" @@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()  bool FlamePackExportTask::abort()  { -    if (task != nullptr) { +    if (task) {          task->abort(); -        task = nullptr;          emitAborted();          return true;      } - -    if (buildZipFuture.isRunning()) { -        buildZipFuture.cancel(); -        // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur -        // immediately. -        return true; -    } -      return false;  } @@ -166,7 +158,7 @@ void FlamePackExportTask::collectHashes()          stepProgress(*progressStep);          emitFailed(reason);      }); -    connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propogateStepProgress); +    connect(hashingTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);      connect(hashingTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {          progressStep->update(current, total); @@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()      setStatus(tr("Adding files..."));      setProgress(4, 5); -    buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { -        QuaZip zip(output); -        if (!zip.open(QuaZip::mdCreate)) { -            QFile::remove(output); -            return BuildZipResult(tr("Could not create file")); -        } - -        if (buildZipFuture.isCanceled()) -            return BuildZipResult(); +    auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true); +    zipTask->addExtraFile("manifest.json", generateIndex()); +    zipTask->addExtraFile("modlist.html", generateHTML()); -        QuaZipFile indexFile(&zip); -        if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) { -            QFile::remove(output); -            return BuildZipResult(tr("Could not create index")); -        } -        indexFile.write(generateIndex()); +    QStringList exclude; +    std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude), +                   [this](QString file) { return gameRoot.relativeFilePath(file); }); +    zipTask->setExcludeFiles(exclude); -        QuaZipFile modlist(&zip); -        if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) { -            QFile::remove(output); -            return BuildZipResult(tr("Could not create index")); -        } -        QString content = ""; -        for (auto mod : resolvedFiles) { -            if (mod.isMod) { -                content += QString(TEMPLATE) -                               .replace("{name}", mod.name.toHtmlEscaped()) -                               .replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped()) -                               .replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : ""); -            } -        } -        content = "<ul>" + content + "</ul>"; -        modlist.write(content.toUtf8()); - -        auto progressStep = std::make_shared<TaskStepProgress>(); - -        size_t progress = 0; -        for (const QFileInfo& file : files) { -            if (buildZipFuture.isCanceled()) { -                QFile::remove(output); -                progressStep->state = TaskStepState::Failed; -                stepProgress(*progressStep); -                return BuildZipResult(); -            } -            progressStep->update(progress, files.length()); -            stepProgress(*progressStep); - -            const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); -            if (!resolvedFiles.contains(file.absoluteFilePath()) && -                !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { -                QFile::remove(output); -                return BuildZipResult(tr("Could not read and compress %1").arg(relative)); -            } -            progress++; -        } - -        zip.close(); - -        if (zip.getZipError() != 0) { -            QFile::remove(output); -            progressStep->state = TaskStepState::Failed; -            stepProgress(*progressStep); -            return BuildZipResult(tr("A zip error occurred")); -        } +    auto progressStep = std::make_shared<TaskStepProgress>(); +    connect(zipTask.get(), &Task::finished, this, [this, progressStep] {          progressStep->state = TaskStepState::Succeeded;          stepProgress(*progressStep); -        return BuildZipResult();      }); -    connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish); -    buildZipWatcher.setFuture(buildZipFuture); -} -void FlamePackExportTask::finish() -{ -    if (buildZipFuture.isCanceled()) -        emitAborted(); -    else { -        const BuildZipResult result = buildZipFuture.result(); -        if (result.has_value()) -            emitFailed(result.value()); -        else -            emitSucceeded(); -    } +    connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded); +    connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted); +    connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { +        progressStep->state = TaskStepState::Failed; +        stepProgress(*progressStep); +        emitFailed(reason); +    }); +    connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress); + +    connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { +        progressStep->update(current, total); +        stepProgress(*progressStep); +    }); +    connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { +        progressStep->status = status; +        stepProgress(*progressStep); +    }); +    task.reset(zipTask); +    zipTask->start();  }  QByteArray FlamePackExportTask::generateIndex() @@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()      return QJsonDocument(obj).toJson(QJsonDocument::Compact);  } + +QByteArray FlamePackExportTask::generateHTML() +{ +    QString content = ""; +    for (auto mod : resolvedFiles) { +        if (mod.isMod) { +            content += QString(TEMPLATE) +                           .replace("{name}", mod.name.toHtmlEscaped()) +                           .replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped()) +                           .replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : ""); +        } +    } +    content = "<ul>" + content + "</ul>"; +    return content.toUtf8(); +}
\ No newline at end of file diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h index 3dee0a7e..d3dc6281 100644 --- a/launcher/modplatform/flame/FlamePackExportTask.h +++ b/launcher/modplatform/flame/FlamePackExportTask.h @@ -19,8 +19,6 @@  #pragma once -#include <QFuture> -#include <QFutureWatcher>  #include "BaseInstance.h"  #include "MMCZip.h"  #include "minecraft/MinecraftInstance.h" @@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {      const QString output;      const MMCZip::FilterFunction filter; -    typedef std::optional<QString> BuildZipResult;      struct ResolvedFile {          int addonId;          int version; @@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {      QMap<QString, HashInfo> pendingHashes{};      QMap<QString, ResolvedFile> resolvedFiles{};      Task::Ptr task; -    QFuture<BuildZipResult> buildZipFuture; -    QFutureWatcher<BuildZipResult> buildZipWatcher;      void collectFiles();      void collectHashes();      void makeApiRequest();      void getProjectsInfo();      void buildZip(); -    void finish();      QByteArray generateIndex(); +    QByteArray generateHTML();  }; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 22008297..ee4d0766 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -76,13 +76,8 @@ bool Flame::File::parseFromObject(const QJsonObject& obj,  bool throw_on_blocked      // It is also optional      type = File::Type::SingleFile; -    if (fileName.endsWith(".zip")) { -        // this is probably a resource pack -        targetFolder = "resourcepacks"; -    } else { -        // this is probably a mod, dunno what else could modpacks download -        targetFolder = "mods"; -    } +    targetFolder = "mods"; +      // get the hash      hash = QString();      auto hashes = Json::ensureArray(obj, "hashes"); diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp new file mode 100644 index 00000000..1f01c4a8 --- /dev/null +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ +#include "ExportToModList.h" +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> + +namespace ExportToModList { +QString toHTML(QList<Mod*> mods, OptionalData extraData) +{ +    QStringList lines; +    for (auto mod : mods) { +        auto meta = mod->metadata(); +        auto modName = mod->name().toHtmlEscaped(); +        if (extraData & Url) { +            auto url = mod->metaurl().toHtmlEscaped(); +            if (!url.isEmpty()) +                modName = QString("<a href=\"%1\">%2</a>").arg(url, modName); +        } +        auto line = modName; +        if (extraData & Version) { +            auto ver = mod->version(); +            if (ver.isEmpty() && meta != nullptr) +                ver = meta->version().toString(); +            if (!ver.isEmpty()) +                line += QString(" [%1]").arg(ver.toHtmlEscaped()); +        } +        if (extraData & Authors && !mod->authors().isEmpty()) +            line += " by " + mod->authors().join(", ").toHtmlEscaped(); +        lines.append(QString("<li>%1</li>").arg(line)); +    } +    return QString("<html><body><ul>\n\t%1\n</ul></body></html>").arg(lines.join("\n\t")); +} + +QString toMarkdown(QList<Mod*> mods, OptionalData extraData) +{ +    QStringList lines; +    for (auto mod : mods) { +        auto meta = mod->metadata(); +        auto modName = mod->name(); +        if (extraData & Url) { +            auto url = mod->metaurl(); +            if (!url.isEmpty()) +                modName = QString("[%1](%2)").arg(modName, url); +        } +        auto line = modName; +        if (extraData & Version) { +            auto ver = mod->version(); +            if (ver.isEmpty() && meta != nullptr) +                ver = meta->version().toString(); +            if (!ver.isEmpty()) +                line += QString(" [%1]").arg(ver); +        } +        if (extraData & Authors && !mod->authors().isEmpty()) +            line += " by " + mod->authors().join(", "); +        lines << "- " + line; +    } +    return lines.join("\n"); +} + +QString toPlainTXT(QList<Mod*> mods, OptionalData extraData) +{ +    QStringList lines; +    for (auto mod : mods) { +        auto meta = mod->metadata(); +        auto modName = mod->name(); + +        auto line = modName; +        if (extraData & Url) { +            auto url = mod->metaurl(); +            if (!url.isEmpty()) +                line += QString(" (%1)").arg(url); +        } +        if (extraData & Version) { +            auto ver = mod->version(); +            if (ver.isEmpty() && meta != nullptr) +                ver = meta->version().toString(); +            if (!ver.isEmpty()) +                line += QString(" [%1]").arg(ver); +        } +        if (extraData & Authors && !mod->authors().isEmpty()) +            line += " by " + mod->authors().join(", "); +        lines << line; +    } +    return lines.join("\n"); +} + +QString toJSON(QList<Mod*> mods, OptionalData extraData) +{ +    QJsonArray lines; +    for (auto mod : mods) { +        auto meta = mod->metadata(); +        auto modName = mod->name(); +        QJsonObject line; +        line["name"] = modName; +        if (extraData & Url) { +            auto url = mod->metaurl(); +            if (!url.isEmpty()) +                line["url"] = url; +        } +        if (extraData & Version) { +            auto ver = mod->version(); +            if (ver.isEmpty() && meta != nullptr) +                ver = meta->version().toString(); +            if (!ver.isEmpty()) +                line["version"] = ver; +        } +        if (extraData & Authors && !mod->authors().isEmpty()) +            line["authors"] = QJsonArray::fromStringList(mod->authors()); +        lines << line; +    } +    QJsonDocument doc; +    doc.setArray(lines); +    return doc.toJson(); +} + +QString toCSV(QList<Mod*> mods, OptionalData extraData) +{ +    QStringList lines; +    for (auto mod : mods) { +        QStringList data; +        auto meta = mod->metadata(); +        auto modName = mod->name(); + +        data << modName; +        if (extraData & Url) +            data << mod->metaurl(); +        if (extraData & Version) { +            auto ver = mod->version(); +            if (ver.isEmpty() && meta != nullptr) +                ver = meta->version().toString(); +            data << ver; +        } +        if (extraData & Authors) { +            QString authors; +            if (mod->authors().length() == 1) +                authors = mod->authors().back(); +            else if (mod->authors().length() > 1) +                authors = QString("\"%1\"").arg(mod->authors().join(",")); +            data << authors; +        } +        lines << data.join(","); +    } +    return lines.join("\n"); +} + +QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData) +{ +    switch (format) { +        case HTML: +            return toHTML(mods, extraData); +        case MARKDOWN: +            return toMarkdown(mods, extraData); +        case PLAINTXT: +            return toPlainTXT(mods, extraData); +        case JSON: +            return toJSON(mods, extraData); +        case CSV: +            return toCSV(mods, extraData); +        default: { +            return QString("unknown format:%1").arg(format); +        } +    } +} + +QString exportToModList(QList<Mod*> mods, QString lineTemplate) +{ +    QStringList lines; +    for (auto mod : mods) { +        auto meta = mod->metadata(); +        auto modName = mod->name(); +        auto url = mod->metaurl(); +        auto ver = mod->version(); +        if (ver.isEmpty() && meta != nullptr) +            ver = meta->version().toString(); +        auto authors = mod->authors().join(", "); +        lines << QString(lineTemplate) +                     .replace("{name}", modName) +                     .replace("{url}", url) +                     .replace("{version}", ver) +                     .replace("{authors}", authors); +    } +    return lines.join("\n"); +} +}  // namespace ExportToModList
\ No newline at end of file diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h new file mode 100644 index 00000000..7ea4ba9c --- /dev/null +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ +#pragma once +#include <QList> +#include <QString> +#include "minecraft/mod/Mod.h" + +namespace ExportToModList { + +enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; +enum OptionalData { +    Authors = 1 << 0, +    Url = 1 << 1, +    Version = 1 << 2, +}; +QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData); +QString exportToModList(QList<Mod*> mods, QString lineTemplate); +}  // namespace ExportToModList diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp index c278f800..9bc6dd5c 100644 --- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp +++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp @@ -10,6 +10,8 @@  #include "modplatform/ModIndex.h" +#include "net/ApiDownload.h" +  Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const  {      auto search_url_optional = getSearchURL(args); @@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&      auto response = std::make_shared<QByteArray>();      auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network()); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response));      QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {          QJsonParseError parse_error{}; @@ -85,7 +87,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi      auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());      auto response = std::make_shared<QByteArray>(); -    netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));      QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {          QJsonParseError parse_error{}; @@ -113,7 +115,7 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA      auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network()); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));      return netJob;  } diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp new file mode 100644 index 00000000..4a1bbef9 --- /dev/null +++ b/launcher/modplatform/import_ftb/PackHelpers.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +#include "modplatform/import_ftb/PackHelpers.h" + +#include <QIcon> +#include <QString> +#include <QVariant> + +#include "FileSystem.h" +#include "Json.h" + +namespace FTBImportAPP { + +Modpack parseDirectory(QString path) +{ +    Modpack modpack{ path }; +    auto instanceFile = QFileInfo(FS::PathCombine(path, "instance.json")); +    if (!instanceFile.exists() || !instanceFile.isFile()) +        return {}; +    try { +        auto doc = Json::requireDocument(instanceFile.absoluteFilePath(), "FTB_APP instance JSON file"); +        const auto root = doc.object(); +        modpack.uuid = Json::requireString(root, "uuid", "uuid"); +        modpack.id = Json::requireInteger(root, "id", "id"); +        modpack.versionId = Json::requireInteger(root, "versionId", "versionId"); +        modpack.name = Json::requireString(root, "name", "name"); +        modpack.version = Json::requireString(root, "version", "version"); +        modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion"); +        modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs"); +    } catch (const Exception& e) { +        qDebug() << "Couldn't load ftb instance json: " << e.cause(); +        return {}; +    } +    auto versionsFile = QFileInfo(FS::PathCombine(path, "version.json")); +    if (!versionsFile.exists() || !versionsFile.isFile()) +        return {}; +    try { +        auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file"); +        const auto root = doc.object(); +        auto targets = Json::requireArray(root, "targets", "targets"); + +        for (auto target : targets) { +            auto obj = Json::requireObject(target, "target"); +            auto name = Json::requireString(obj, "name", "name"); +            auto version = Json::requireString(obj, "version", "version"); +            if (name == "forge") { +                modpack.loaderType = ResourceAPI::Forge; +                modpack.version = version; +                break; +            } else if (name == "fabric") { +                modpack.loaderType = ResourceAPI::Fabric; +                modpack.version = version; +                break; +            } else if (name == "quilt") { +                modpack.loaderType = ResourceAPI::Quilt; +                modpack.version = version; +                break; +            } +        } +    } catch (const Exception& e) { +        qDebug() << "Couldn't load ftb version json: " << e.cause(); +        return {}; +    } +    auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg")); +    if (iconFile.exists() && iconFile.isFile()) { +        modpack.icon = QIcon(iconFile.absoluteFilePath()); +    } +    return modpack; +} + +}  // namespace FTBImportAPP diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h new file mode 100644 index 00000000..8ea4f3fa --- /dev/null +++ b/launcher/modplatform/import_ftb/PackHelpers.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ +#pragma once + +#include <QIcon> +#include <QList> +#include <QMetaType> +#include <QString> +#include <QVariant> +#include "modplatform/ResourceAPI.h" + +namespace FTBImportAPP { + +struct Modpack { +    QString path; + +    // json data +    QString uuid; +    int id; +    int versionId; +    QString name; +    QString version; +    QString mcVersion; +    // not needed for instance creation +    QVariant jvmArgs; + +    std::optional<ResourceAPI::ModLoaderType> loaderType; +    QString loaderVersion; + +    QIcon icon; +}; + +typedef QList<Modpack> ModpackList; + +Modpack parseDirectory(QString path); + +}  // namespace FTBImportAPP + +// We need it for the proxy model +Q_DECLARE_METATYPE(FTBImportAPP::Modpack) diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp new file mode 100644 index 00000000..b5e424d1 --- /dev/null +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +#include "PackInstallTask.h" + +#include <QtConcurrent> + +#include "BaseInstance.h" +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "modplatform/ResourceAPI.h" +#include "modplatform/import_ftb/PackHelpers.h" +#include "settings/INISettingsObject.h" + +namespace FTBImportAPP { + +void PackInstallTask::executeTask() +{ +    setStatus(tr("Copying files...")); +    setAbortable(false); +    progress(1, 2); + +    m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { +        FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft")); +        folderCopy.followSymlinks(true); +        return folderCopy(); +    }); +    connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &PackInstallTask::copySettings); +    connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &PackInstallTask::emitAborted); +    m_copyFutureWatcher.setFuture(m_copyFuture); +} + +void PackInstallTask::copySettings() +{ +    setStatus(tr("Copying settings...")); +    progress(2, 2); +    QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); +    auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath); +    instanceSettings->suspendSave(); +    MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); +    instance.settings()->set("InstanceType", "OneSix"); + +    if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) { +        instance.settings()->set("OverrideJavaArgs", true); +        instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); +    } + +    auto components = instance.getPackProfile(); +    components->buildingFromScratch(); +    components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + +    auto modloader = m_pack.loaderType; +    if (modloader.has_value()) +        switch (modloader.value()) { +            case ResourceAPI::Forge: { +                components->setComponentVersion("net.minecraftforge", m_pack.version, true); +                break; +            } +            case ResourceAPI::Fabric: { +                components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); +                break; +            } +            case ResourceAPI::Quilt: { +                components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); +                break; +            } +            case ResourceAPI::Cauldron: +                break; +            case ResourceAPI::LiteLoader: +                break; +        } +    components->saveNow(); + +    instance.setName(name()); +    if (m_instIcon == "default") +        m_instIcon = "ftb_logo"; +    instance.setIconKey(m_instIcon); +    instanceSettings->resumeSave(); + +    emitSucceeded(); +} + +}  // namespace FTBImportAPP diff --git a/launcher/modplatform/import_ftb/PackInstallTask.h b/launcher/modplatform/import_ftb/PackInstallTask.h new file mode 100644 index 00000000..842e4b35 --- /dev/null +++ b/launcher/modplatform/import_ftb/PackInstallTask.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + *  Prism Launcher - Minecraft Launcher + *  Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com> + * + *  This program is free software: you can redistribute it and/or modify + *  it under the terms of the GNU General Public License as published by + *  the Free Software Foundation, version 3. + * + *  This program is distributed in the hope that it will be useful, + *  but WITHOUT ANY WARRANTY; without even the implied warranty of + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + *  GNU General Public License for more details. + * + *  You should have received a copy of the GNU General Public License + *  along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <QFuture> +#include <QFutureWatcher> + +#include "InstanceTask.h" +#include "PackHelpers.h" + +namespace FTBImportAPP { + +class PackInstallTask : public InstanceTask { +    Q_OBJECT + +   public: +    explicit PackInstallTask(const Modpack& pack) : m_pack(pack) {} +    virtual ~PackInstallTask() = default; + +   protected: +    virtual void executeTask() override; + +   private slots: +    void copySettings(); + +   private: +    QFuture<bool> m_copyFuture; +    QFutureWatcher<bool> m_copyFutureWatcher; + +    const Modpack m_pack; +}; + +}  // namespace FTBImportAPP diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index a8a0fc2c..e5ad8244 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -40,6 +40,8 @@  #include "BuildConfig.h"  #include "Application.h" +#include "net/ApiDownload.h" +  namespace LegacyFTB {  void PackFetchTask::fetch() @@ -51,7 +53,7 @@ void PackFetchTask::fetch()      QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");      qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); -    jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData)); +    jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));      QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");      qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); @@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)      for (auto& packCode : toFetch) {          auto data = std::make_shared<QByteArray>();          NetJob* job = new NetJob("Fetching private pack", m_network); -        job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); +        job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data));          QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {              ModpackList packs; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 36c142ac..beb295dc 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -37,16 +37,18 @@  #include <QtConcurrent> -#include "MMCZip.h"  #include "BaseInstance.h"  #include "FileSystem.h" -#include "settings/INISettingsObject.h" +#include "MMCZip.h" +#include "minecraft/GradleSpecifier.h"  #include "minecraft/MinecraftInstance.h"  #include "minecraft/PackProfile.h" -#include "minecraft/GradleSpecifier.h" +#include "settings/INISettingsObject.h" -#include "BuildConfig.h"  #include "Application.h" +#include "BuildConfig.h" + +#include "net/ApiDownload.h"  namespace LegacyFTB { @@ -65,6 +67,7 @@ void PackInstallTask::executeTask()  void PackInstallTask::downloadPack()  {      setStatus(tr("Downloading zip for %1").arg(m_pack.name)); +    setProgress(1, 4);      setAbortable(false);      archivePath = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); @@ -76,13 +79,12 @@ void PackInstallTask::downloadPack()      } else {          url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);      } -    netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath)); +    netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath)); -    connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); -    connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); -    connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); -    connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress); -    connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); +    connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip); +    connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed); +    connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress); +    connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::emitAborted);      netJobContainer->start(); @@ -90,27 +92,6 @@ void PackInstallTask::downloadPack()      progress(1, 4);  } -void PackInstallTask::onDownloadSucceeded() -{ -    unzip(); -} - -void PackInstallTask::onDownloadFailed(QString reason) -{ -    emitFailed(reason); -} - -void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) -{ -    progress(current, total * 4); -    setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); -} - -void PackInstallTask::onDownloadAborted() -{ -    emitAborted(); -} -  void PackInstallTask::unzip()  {      setStatus(tr("Extracting modpack")); @@ -120,16 +101,17 @@ void PackInstallTask::unzip()      QDir extractDir(m_stagingPath);      m_packZip.reset(new QuaZip(archivePath)); -    if(!m_packZip->open(QuaZip::mdUnzip)) -    { +    if (!m_packZip->open(QuaZip::mdUnzip)) {          emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));          return;      }  #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -    m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, extractDir.absolutePath() + "/unzip"); +    m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), QOverload<QString, QString>::of(MMCZip::extractDir), archivePath, +                                        extractDir.absolutePath() + "/unzip");  #else -    m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); +    m_extractFuture = +        QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");  #endif      connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);      connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled); @@ -151,11 +133,9 @@ void PackInstallTask::install()      setStatus(tr("Installing modpack"));      progress(3, 4);      QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); -    if(unzipMcDir.exists()) -    { -        //ok, found minecraft dir, move contents to instance dir -        if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) -        { +    if (unzipMcDir.exists()) { +        // ok, found minecraft dir, move contents to instance dir +        if (!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) {              emitFailed(tr("Failed to move unzipped Minecraft!"));              return;          } @@ -172,23 +152,20 @@ void PackInstallTask::install()      bool fallback = true; -    //handle different versions +    // handle different versions      QFile packJson(m_stagingPath + "/.minecraft/pack.json");      QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); -    if(packJson.exists()) -    { +    if (packJson.exists()) {          packJson.open(QIODevice::ReadOnly | QIODevice::Text);          QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll());          packJson.close(); -        //we only care about the libs +        // we only care about the libs          QJsonArray libs = doc.object().value("libraries").toArray(); -        foreach (const QJsonValue &value, libs) -        { +        foreach (const QJsonValue& value, libs) {              QString nameValue = value.toObject().value("name").toString(); -            if(!nameValue.startsWith("net.minecraftforge")) -            { +            if (!nameValue.startsWith("net.minecraftforge")) {                  continue;              } @@ -199,16 +176,13 @@ void PackInstallTask::install()              fallback = false;              break;          } -      } -    if(jarmodDir.exists()) -    { +    if (jarmodDir.exists()) {          qDebug() << "Found jarmods, installing...";          QStringList jarmods; -        for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) -        { +        for (auto info : jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {              qDebug() << "Jarmod:" << info.fileName();              jarmods.push_back(info.absoluteFilePath());          } @@ -217,12 +191,11 @@ void PackInstallTask::install()          fallback = false;      } -    //just nuke unzip directory, it s not needed anymore +    // just nuke unzip directory, it s not needed anymore      FS::deletePath(m_stagingPath + "/unzip"); -    if(fallback) -    { -        //TODO: Some fallback mechanism... or just keep failing! +    if (fallback) { +        // TODO: Some fallback mechanism... or just keep failing!          emitFailed(tr("No installation method found!"));          return;      } @@ -232,8 +205,7 @@ void PackInstallTask::install()      progress(4, 4);      instance.setName(name()); -    if(m_instIcon == "default") -    { +    if (m_instIcon == "default") {          m_instIcon = "ftb_logo";      }      instance.setIconKey(m_instIcon); @@ -252,4 +224,4 @@ bool PackInstallTask::abort()      return InstanceTask::abort();  } -} +}  // namespace LegacyFTB diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index da791e06..30ff4859 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -1,12 +1,12 @@  #pragma once -#include "InstanceTask.h" -#include "net/NetJob.h"  #include <quazip/quazip.h>  #include <quazip/quazipdir.h> +#include "InstanceTask.h" +#include "PackHelpers.h"  #include "meta/Index.h"  #include "meta/Version.h"  #include "meta/VersionList.h" -#include "PackHelpers.h" +#include "net/NetJob.h"  #include "net/NetJob.h" @@ -14,36 +14,31 @@  namespace LegacyFTB { -class PackInstallTask : public InstanceTask -{ +class PackInstallTask : public InstanceTask {      Q_OBJECT -public: +   public:      explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version); -    virtual ~PackInstallTask(){} +    virtual ~PackInstallTask() {}      bool canAbort() const override { return true; }      bool abort() override; -protected: +   protected:      //! Entry point for tasks.      virtual void executeTask() override; -private: +   private:      void downloadPack();      void unzip();      void install(); -private slots: -    void onDownloadSucceeded(); -    void onDownloadFailed(QString reason); -    void onDownloadProgress(qint64 current, qint64 total); -    void onDownloadAborted(); +   private slots:      void onUnzipFinished();      void onUnzipCanceled(); -private: /* data */ +   private: /* data */      shared_qobject_ptr<QNetworkAccessManager> m_network;      bool abortable = false;      std::unique_ptr<QuaZip> m_packZip; @@ -56,4 +51,4 @@ private: /* data */      QString m_version;  }; -} +}  // namespace LegacyFTB diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp index 364cf3f3..466c5b10 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.cpp +++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp @@ -6,6 +6,8 @@  #include "Application.h"  #include "Json.h" +#include "net/ApiDownload.h" +#include "net/ApiUpload.h"  #include "net/NetJob.h"  #include "net/Upload.h" @@ -13,7 +15,7 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::sh  {      auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); -    netJob->addNetAction(Net::Download::makeByteArray( +    netJob->addNetAction(Net::ApiDownload::makeByteArray(          QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));      return netJob; @@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); +    netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));      return netJob;  } @@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray( +    netJob->addNetAction(Net::ApiUpload::makeByteArray(          QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));      return netJob; @@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,      QJsonDocument body(body_obj);      auto body_raw = body.toJson(); -    netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); +    netJob->addNetAction( +        Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));      return netJob;  } @@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr      auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());      auto searchUrl = getMultipleModInfoURL(addonIds); -    netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); +    netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));      return netJob;  } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 76f07277..2e42d6e7 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -12,6 +12,7 @@  #include "net/ChecksumValidator.h"  #include "net/NetJob.h" +#include "net/ApiDownload.h"  #include "settings/INISettingsObject.h"  #include "ui/dialogs/CustomMessageBox.h" @@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()          }          qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; -        auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path); +        auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);          dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));          m_files_job->addNetAction(dl); @@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()              // MultipleOptionsTask's , once those exist :)              auto param = dl.toWeakRef();              connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] { -                auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path); +                auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);                  ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));                  m_files_job->addNetAction(ndl);                  if (auto shared = param.lock()) shared->succeeded(); @@ -267,7 +268,7 @@ bool ModrinthCreationTask::createInstance()          setDetails(tr("%1 out of %2 complete").arg(current).arg(total));          setProgress(current, total);       }); -    connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress); +    connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propagateStepProgress);      setStatus(tr("Downloading mods..."));      m_files_job->start(); diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp index 30fe566d..7bf29639 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp @@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()  bool ModrinthPackExportTask::abort()  { -    if (task != nullptr) { +    if (task) {          task->abort(); -        task = nullptr;          emitAborted();          return true;      } - -    if (buildZipFuture.isRunning()) { -        buildZipFuture.cancel(); -        // NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur -        // immediately. -        return true; -    } -      return false;  } @@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()  {      setStatus(tr("Adding files...")); -    buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { -        QuaZip zip(output); -        if (!zip.open(QuaZip::mdCreate)) { -            QFile::remove(output); -            return BuildZipResult(tr("Could not create file")); -        } - -        if (buildZipFuture.isCanceled()) -            return BuildZipResult(); - -        QuaZipFile indexFile(&zip); -        if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { -            QFile::remove(output); -            return BuildZipResult(tr("Could not create index")); -        } -        indexFile.write(generateIndex()); - -        size_t progress = 0; -        for (const QFileInfo& file : files) { -            if (buildZipFuture.isCanceled()) { -                QFile::remove(output); -                return BuildZipResult(); -            } - -            setProgress(progress, files.length()); -            const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath()); -            if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) { -                QFile::remove(output); -                return BuildZipResult(tr("Could not read and compress %1").arg(relative)); -            } -            progress++; -        } +    auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true); +    zipTask->addExtraFile("modrinth.index.json", generateIndex()); -        zip.close(); +    zipTask->setExcludeFiles(resolvedFiles.keys()); -        if (zip.getZipError() != 0) { -            QFile::remove(output); -            return BuildZipResult(tr("A zip error occurred")); -        } +    auto progressStep = std::make_shared<TaskStepProgress>(); +    connect(zipTask.get(), &Task::finished, this, [this, progressStep] { +        progressStep->state = TaskStepState::Succeeded; +        stepProgress(*progressStep); +    }); -        return BuildZipResult(); +    connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded); +    connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted); +    connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) { +        progressStep->state = TaskStepState::Failed; +        stepProgress(*progressStep); +        emitFailed(reason);      }); -    connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish); -    buildZipWatcher.setFuture(buildZipFuture); -} +    connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress); -void ModrinthPackExportTask::finish() -{ -    if (buildZipFuture.isCanceled()) -        emitAborted(); -    else { -        const BuildZipResult result = buildZipFuture.result(); -        if (result.has_value()) -            emitFailed(result.value()); -        else -            emitSucceeded(); -    } +    connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) { +        progressStep->update(current, total); +        stepProgress(*progressStep); +    }); +    connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) { +        progressStep->status = status; +        stepProgress(*progressStep); +    }); +    task.reset(zipTask); +    zipTask->start();  }  QByteArray ModrinthPackExportTask::generateIndex() diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h index 96f292c1..1f9e0eb7 100644 --- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h +++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h @@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {      const QString output;      const MMCZip::FilterFunction filter; -    typedef std::optional<QString> BuildZipResult; -      ModrinthAPI api;      QFileInfoList files;      QMap<QString, QString> pendingHashes;      QMap<QString, ResolvedFile> resolvedFiles;      Task::Ptr task; -    QFuture<BuildZipResult> buildZipFuture; -    QFutureWatcher<BuildZipResult> buildZipWatcher;      void collectFiles();      void collectHashes();      void makeApiRequest();      void parseApiResponse(const std::shared_ptr<QByteArray> response);      void buildZip(); -    void finish();      QByteArray generateIndex();  }; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index f07ca24a..b2097c1d 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -23,6 +23,8 @@  #include "Application.h" +#include "net/ApiDownload.h" +  Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)  {      m_sourceUrl = sourceUrl; @@ -45,12 +47,12 @@ void Technic::SingleZipPackInstallTask::executeTask()      auto entry = APPLICATION->metacache()->resolveEntry("general", path);      entry->setStale(true);      m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); -    m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); +    m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));      m_archivePath = entry->getFullPath();      auto job = m_filesNetJob.get();      connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);      connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged); -    connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress); +    connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propagateStepProgress);      connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);      m_filesNetJob->start();  } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 6a05d17a..d7e9f640 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -43,6 +43,7 @@  #include "SolderPackManifest.h"  #include "TechnicPackProcessor.h"  #include "net/ChecksumValidator.h" +#include "net/ApiDownload.h"  Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,                                                        const QUrl& solderUrl, @@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()      m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));      auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); -    m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response)); +    m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));      auto job = m_filesNetJob.get();      connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); @@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()      for (const auto& mod : build.mods) {          auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); -        auto dl = Net::Download::makeFile(mod.url, path); +        auto dl = Net::ApiDownload::makeFile(mod.url, path);          if (!mod.md5.isEmpty()) {              auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());              dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); @@ -126,7 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()      connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);      connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); -    connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress); +    connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propagateStepProgress);      connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);      connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);      m_filesNetJob->start(); | 
