diff options
author | Petr Mrázek <peterix@gmail.com> | 2016-08-14 02:33:31 +0200 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2016-08-14 23:22:54 +0200 |
commit | 042f3ef55c0b469f438542152c4eb02b0789ea3c (patch) | |
tree | 03e0c15b200786558babd0fe58edac88ed1bfd1e /api/logic/minecraft | |
parent | 2f0441b3c1cd9fc3bcb176d2852da8f92a6e6777 (diff) | |
download | MultiMC-042f3ef55c0b469f438542152c4eb02b0789ea3c.tar MultiMC-042f3ef55c0b469f438542152c4eb02b0789ea3c.tar.gz MultiMC-042f3ef55c0b469f438542152c4eb02b0789ea3c.tar.lz MultiMC-042f3ef55c0b469f438542152c4eb02b0789ea3c.tar.xz MultiMC-042f3ef55c0b469f438542152c4eb02b0789ea3c.zip |
GH-352 Make OneSix instance update downloads cancellable
Diffstat (limited to 'api/logic/minecraft')
19 files changed, 597 insertions, 312 deletions
diff --git a/api/logic/minecraft/MinecraftVersionList.cpp b/api/logic/minecraft/MinecraftVersionList.cpp index 4e4eafbc..e3f416d9 100644 --- a/api/logic/minecraft/MinecraftVersionList.cpp +++ b/api/logic/minecraft/MinecraftVersionList.cpp @@ -61,6 +61,10 @@ public: explicit MCVListVersionUpdateTask(MinecraftVersionList *vlist, std::shared_ptr<MinecraftVersion> updatedVersion); virtual ~MCVListVersionUpdateTask() override{}; virtual void executeTask() override; + bool canAbort() const override; + +public slots: + bool abort() override; protected slots: @@ -71,6 +75,7 @@ protected: QByteArray versionIndexData; std::shared_ptr<MinecraftVersion> updatedVersion; MinecraftVersionList *m_list; + bool m_aborted = false; }; class ListLoadError : public Exception @@ -410,6 +415,11 @@ MCVListVersionUpdateTask::MCVListVersionUpdateTask(MinecraftVersionList *vlist, void MCVListVersionUpdateTask::executeTask() { + if(m_aborted) + { + emitFailed(tr("Task aborted.")); + return; + } auto job = new NetJob("Version index"); job->addNetAction(Net::Download::makeByteArray(QUrl(updatedVersion->getUrl()), &versionIndexData)); specificVersionDownloadJob.reset(job); @@ -419,6 +429,21 @@ void MCVListVersionUpdateTask::executeTask() specificVersionDownloadJob->start(); } +bool MCVListVersionUpdateTask::canAbort() const +{ + return true; +} + +bool MCVListVersionUpdateTask::abort() +{ + m_aborted = true; + if(specificVersionDownloadJob) + { + return specificVersionDownloadJob->abort(); + } + return true; +} + void MCVListVersionUpdateTask::json_downloaded() { specificVersionDownloadJob.reset(); diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp index adf96552..8b762866 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.cpp +++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp @@ -35,6 +35,12 @@ ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : Ne void ForgeXzDownload::start() { + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } m_status = Job_InProgress; if (!m_entry->isStale()) { @@ -76,9 +82,17 @@ void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - // TODO: log the reason why - m_status = Job_Failed; + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } } void ForgeXzDownload::failAndTryNextMirror() @@ -90,7 +104,7 @@ void ForgeXzDownload::failAndTryNextMirror() void ForgeXzDownload::downloadFinished() { // if the download succeeded - if (m_status != Job_Failed) + if (m_status != Job_Failed && m_status != Job_Aborted) { // nothing went wrong... m_status = Job_Finished; @@ -110,6 +124,14 @@ void ForgeXzDownload::downloadFinished() return; } } + else if(m_status == Job_Aborted) + { + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(m_index_within_job); + emit aborted(m_index_within_job); + return; + } // else the download failed else { @@ -147,6 +169,7 @@ void ForgeXzDownload::downloadReadyRead() const size_t buffer_size = 8196; +// NOTE: once this gets here, it can't be aborted anymore. we don't care. void ForgeXzDownload::decompressAndInstall() { // rewind the downloaded temp file @@ -356,3 +379,16 @@ void ForgeXzDownload::decompressAndInstall() m_reply.reset(); emit succeeded(m_index_within_job); } + +bool ForgeXzDownload::abort() +{ + if(m_reply) + m_reply->abort(); + m_status = Job_Aborted; + return true; +} + +bool ForgeXzDownload::canAbort() +{ + return true; +} diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h index 67524405..e51ff9e5 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.h +++ b/api/logic/minecraft/forge/ForgeXzDownload.h @@ -41,17 +41,19 @@ public: return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry)); } virtual ~ForgeXzDownload(){}; + bool canAbort() override; protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead(); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void downloadFinished() override; + void downloadReadyRead() override; public slots: - virtual void start(); + void start() override; + bool abort() override; private: void decompressAndInstall(); diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp b/api/logic/minecraft/ftb/OneSixFTBInstance.cpp index 81e939a1..5edad080 100644 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp +++ b/api/logic/minecraft/ftb/OneSixFTBInstance.cpp @@ -125,7 +125,7 @@ void OneSixFTBInstance::createProfile() m_profile.reset(new MinecraftProfile(new FTBProfileStrategy(this))); } -std::shared_ptr<Task> OneSixFTBInstance::createUpdateTask() +shared_qobject_ptr<Task> OneSixFTBInstance::createUpdateTask() { return OneSixInstance::createUpdateTask(); } diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.h b/api/logic/minecraft/ftb/OneSixFTBInstance.h index e7f8f485..a5621685 100644 --- a/api/logic/minecraft/ftb/OneSixFTBInstance.h +++ b/api/logic/minecraft/ftb/OneSixFTBInstance.h @@ -13,7 +13,7 @@ public: virtual void createProfile() override; - virtual std::shared_ptr<Task> createUpdateTask() override; + virtual shared_qobject_ptr<Task> createUpdateTask() override; virtual QString id() const override; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index 7c552369..7ed2041c 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -87,12 +87,12 @@ bool LegacyInstance::shouldUseCustomBaseJar() const } -std::shared_ptr<Task> LegacyInstance::createUpdateTask() +shared_qobject_ptr<Task> LegacyInstance::createUpdateTask() { // make sure the jar mods list is initialized by asking for it. auto list = jarModList(); // create an update task - return std::shared_ptr<Task>(new LegacyUpdate(this, this)); + return shared_qobject_ptr<Task>(new LegacyUpdate(this, this)); } std::shared_ptr<Task> LegacyInstance::createJarModdingTask() diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index f1cefbcc..1d95340b 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -113,7 +113,7 @@ public: virtual bool shouldUpdate() const override; virtual void setShouldUpdate(bool val) override; - virtual std::shared_ptr<Task> createUpdateTask() override; + virtual shared_qobject_ptr<Task> createUpdateTask() override; virtual std::shared_ptr<Task> createJarModdingTask() override; virtual QString createLaunchScript(AuthSessionPtr session) override; diff --git a/api/logic/minecraft/onesix/OneSixInstance.cpp b/api/logic/minecraft/onesix/OneSixInstance.cpp index 8b5491e2..1bf75bdb 100644 --- a/api/logic/minecraft/onesix/OneSixInstance.cpp +++ b/api/logic/minecraft/onesix/OneSixInstance.cpp @@ -60,9 +60,9 @@ QSet<QString> OneSixInstance::traits() } } -std::shared_ptr<Task> OneSixInstance::createUpdateTask() +shared_qobject_ptr<Task> OneSixInstance::createUpdateTask() { - return std::shared_ptr<Task>(new OneSixUpdate(this)); + return shared_qobject_ptr<Task>(new OneSixUpdate(this)); } QString replaceTokensIn(QString text, QMap<QString, QString> with) diff --git a/api/logic/minecraft/onesix/OneSixInstance.h b/api/logic/minecraft/onesix/OneSixInstance.h index 1a917e79..c857075f 100644 --- a/api/logic/minecraft/onesix/OneSixInstance.h +++ b/api/logic/minecraft/onesix/OneSixInstance.h @@ -52,7 +52,7 @@ public: QString worldDir() const; virtual QString instanceConfigFolder() const override; - virtual std::shared_ptr<Task> createUpdateTask() override; + virtual shared_qobject_ptr<Task> createUpdateTask() override; virtual std::shared_ptr<Task> createJarModdingTask() override; virtual QString createLaunchScript(AuthSessionPtr session) override; QStringList verboseDescription(AuthSessionPtr session) override; diff --git a/api/logic/minecraft/onesix/OneSixUpdate.cpp b/api/logic/minecraft/onesix/OneSixUpdate.cpp index d3cd197d..02a6f561 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.cpp +++ b/api/logic/minecraft/onesix/OneSixUpdate.cpp @@ -18,332 +18,138 @@ #include "OneSixUpdate.h" #include "OneSixInstance.h" -#include <QtNetwork> - #include <QFile> #include <QFileInfo> #include <QTextStream> #include <QDataStream> -#include <JlCompress.h> #include "BaseInstance.h" #include "minecraft/MinecraftVersionList.h" #include "minecraft/MinecraftProfile.h" #include "minecraft/Library.h" #include "net/URLConstants.h" -#include "net/ChecksumValidator.h" -#include "minecraft/AssetsUtils.h" -#include "Exception.h" -#include "MMCZip.h" #include <FileSystem.h> -OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) -{ -} +#include "update/FoldersTask.h" +#include "update/LibrariesTask.h" +#include "update/FMLLibrariesTask.h" +#include "update/AssetUpdateTask.h" -void OneSixUpdate::executeTask() +OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) + // create folders { - emitFailed(tr("Failed to create folder for minecraft binaries.")); - return; + m_tasks.append(std::make_shared<FoldersTask>(m_inst)); } - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast<MinecraftVersion>(ENV.getVersion("net.minecraft", m_inst->intendedVersionId())); - if (targetVersion == nullptr) - { - // don't do anything if it was invalid - emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); - return; - } - if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) + // add a version update task, if necessary { - qDebug() << "Instance either provides a version file or doesn't need an update."; - jarlibStart(); - return; + auto list = std::dynamic_pointer_cast<MinecraftVersionList>(ENV.getVersionList("net.minecraft")); + auto version = std::dynamic_pointer_cast<MinecraftVersion>(list->findVersion(m_inst->intendedVersionId())); + if (version == nullptr) + { + // don't do anything if it was invalid + m_preFailure = tr("The specified Minecraft version is invalid. Choose a different one."); + } + else if (m_inst->providesVersionFile() || !version->needsUpdate()) + { + qDebug() << "Instance either provides a version file or doesn't need an update."; + } + else + { + auto versionUpdateTask = list->createUpdateTask(m_inst->intendedVersionId()); + if (!versionUpdateTask) + { + qDebug() << "Didn't spawn an update task."; + } + else + { + m_tasks.append(versionUpdateTask); + } + } } - versionUpdateTask = std::dynamic_pointer_cast<MinecraftVersionList>(ENV.getVersionList("net.minecraft"))->createUpdateTask(m_inst->intendedVersionId()); - if (!versionUpdateTask) + + // libraries download { - qDebug() << "Didn't spawn an update task."; - jarlibStart(); - return; + m_tasks.append(std::make_shared<LibrariesTask>(m_inst)); } - connect(versionUpdateTask.get(), SIGNAL(succeeded()), SLOT(jarlibStart())); - connect(versionUpdateTask.get(), &NetJob::failed, this, &OneSixUpdate::versionUpdateFailed); - connect(versionUpdateTask.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - setStatus(tr("Getting the version files from Mojang...")); - versionUpdateTask->start(); -} -void OneSixUpdate::versionUpdateFailed(QString reason) -{ - emitFailed(reason); -} - -void OneSixUpdate::assetIndexStart() -{ - setStatus(tr("Updating assets index...")); - OneSixInstance *inst = (OneSixInstance *)m_inst; - auto profile = inst->getMinecraftProfile(); - auto assets = profile->getMinecraftAssets(); - QUrl indexUrl = assets->url; - QString localPath = assets->id + ".json"; - auto job = new NetJob(tr("Asset index for %1").arg(inst->name())); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - entry->setStale(true); - auto hexSha1 = assets->sha1.toLatin1(); - qDebug() << "Asset index SHA1:" << hexSha1; - auto dl = Net::Download::makeCached(indexUrl, entry); - auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - job->addNetAction(dl); - - jarlibDownloadJob.reset(job); - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetIndexFinished())); - connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetIndexFailed); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - - qDebug() << m_inst->name() << ": Starting asset index download"; - jarlibDownloadJob->start(); -} - -void OneSixUpdate::assetIndexFinished() -{ - AssetsIndex index; - qDebug() << m_inst->name() << ": Finished asset index download"; - - OneSixInstance *inst = (OneSixInstance *)m_inst; - auto profile = inst->getMinecraftProfile(); - auto assets = profile->getMinecraftAssets(); - - QString asset_fname = "assets/indexes/" + assets->id + ".json"; - // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) + // FML libraries download and copy into the instance { - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); - metacache->evictEntry(entry); - emitFailed(tr("Failed to read the assets index!")); + m_tasks.append(std::make_shared<FMLLibrariesTask>(m_inst)); } - auto job = index.getDownloadJob(); - if(job) + // assets update { - setStatus(tr("Getting the assets files from Mojang...")); - jarlibDownloadJob = job; - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(assetsFinished())); - connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::assetsFailed); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - jarlibDownloadJob->start(); - return; + m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst)); } - assetsFinished(); -} - -void OneSixUpdate::assetIndexFailed(QString reason) -{ - qDebug() << m_inst->name() << ": Failed asset index download"; - emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); -} - -void OneSixUpdate::assetsFinished() -{ - emitSucceeded(); -} - -void OneSixUpdate::assetsFailed(QString reason) -{ - emitFailed(tr("Failed to download assets:\n%1").arg(reason)); } -void OneSixUpdate::jarlibStart() +void OneSixUpdate::executeTask() { - setStatus(tr("Getting the library files from Mojang...")); - qDebug() << m_inst->name() << ": downloading libraries"; - OneSixInstance *inst = (OneSixInstance *)m_inst; - inst->reloadProfile(); - if(inst->flags() & BaseInstance::VersionBrokenFlag) + if(!m_preFailure.isEmpty()) { - emitFailed(tr("Failed to load the version description files - check the instance for errors.")); + emitFailed(m_preFailure); return; } + next(); +} - // Build a list of URLs that will need to be downloaded. - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); - // minecraft.jar for this version +void OneSixUpdate::next() +{ + if(m_abort) { - QString version_id = profile->getMinecraftVersion(); - QString localPath = version_id + "/" + version_id + ".jar"; - QString urlstr = profile->getMainJarUrl(); - - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); - - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("versions", localPath); - job->addNetAction(Net::Download::makeCached(QUrl(urlstr), entry)); - jarlibDownloadJob.reset(job); + emitFailed(tr("Aborted by user.")); + return; } - - auto libs = profile->getLibraries(); - - auto metacache = ENV.metacache(); - QList<LibraryPtr> brokenLocalLibs; - - QStringList failedFiles; - for (auto lib : libs) + m_currentTask ++; + if(m_currentTask > 0) { - auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles); - for(auto dl : dls) - { - jarlibDownloadJob->addNetAction(dl); - } + auto task = m_tasks[m_currentTask - 1]; + disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); + disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); + disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress); + disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); } - if (!brokenLocalLibs.empty()) + if(m_currentTask == m_tasks.size()) { - jarlibDownloadJob.reset(); - - QString failed_all = failedFiles.join("\n"); - emitFailed(tr("Some libraries marked as 'local' are missing their jar " - "files:\n%1\n\nYou'll have to correct this problem manually. If this is " - "an externally tracked instance, make sure to run it at least once " - "outside of MultiMC.").arg(failed_all)); + emitSucceeded(); return; } - - connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); - connect(jarlibDownloadJob.get(), &NetJob::failed, this, &OneSixUpdate::jarlibFailed); - connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), - SIGNAL(progress(qint64, qint64))); - - jarlibDownloadJob->start(); + auto task = m_tasks[m_currentTask]; + connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); + connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); + connect(task.get(), &Task::progress, this, &OneSixUpdate::progress); + connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); + task->start(); } -void OneSixUpdate::jarlibFinished() +void OneSixUpdate::subtaskSucceeded() { - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); - - if (profile->hasTrait("legacyFML")) - { - fmllibsStart(); - } - else - { - assetIndexStart(); - } + next(); } -void OneSixUpdate::jarlibFailed(QString reason) +void OneSixUpdate::subtaskFailed(QString error) { - QStringList failed = jarlibDownloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed( - tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); + emitFailed(error); } -void OneSixUpdate::fmllibsStart() -{ - // Get the mod list - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); - bool forge_present = false; - - QString version = inst->intendedVersionId(); - auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - assetIndexStart(); - return; - } - - auto &libList = fmlLibsMapping[version]; - - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - forge_present = (profile->versionPatch("net.minecraftforge") != nullptr); - // we don't... - if (!forge_present) - { - assetIndexStart(); - return; - } - - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - assetIndexStart(); - return; - } - - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } - - connect(dljob, SIGNAL(succeeded()), SLOT(fmllibsFinished())); - connect(dljob, &NetJob::failed, this, &OneSixUpdate::fmllibsFailed); - connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); - legacyDownloadJob.reset(dljob); - legacyDownloadJob->start(); -} - -void OneSixUpdate::fmllibsFinished() +bool OneSixUpdate::abort() { - legacyDownloadJob.reset(); - if (!fmlLibsToProcess.isEmpty()) + if(!m_abort) { - setStatus(tr("Copying FML libraries into the instance...")); - OneSixInstance *inst = (OneSixInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) + m_abort = true; + auto task = m_tasks[m_currentTask]; + if(task->canAbort()) { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if (!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; + return task->abort(); } - progress(index, fmlLibsToProcess.size()); } - assetIndexStart(); + return true; } -void OneSixUpdate::fmllibsFailed(QString reason) +bool OneSixUpdate::canAbort() const { - emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries.\nReason:\n%1").arg(reason)); - return; + return true; } - diff --git a/api/logic/minecraft/onesix/OneSixUpdate.h b/api/logic/minecraft/onesix/OneSixUpdate.h index b5195364..3780ef2e 100644 --- a/api/logic/minecraft/onesix/OneSixUpdate.h +++ b/api/logic/minecraft/onesix/OneSixUpdate.h @@ -32,36 +32,22 @@ class OneSixUpdate : public Task Q_OBJECT public: explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0); - virtual void executeTask(); + void executeTask() override; + bool canAbort() const override; private slots: - void versionUpdateFailed(QString reason); - - void jarlibStart(); - void jarlibFinished(); - void jarlibFailed(QString reason); - - void fmllibsStart(); - void fmllibsFinished(); - void fmllibsFailed(QString reason); - - void assetIndexStart(); - void assetIndexFinished(); - void assetIndexFailed(QString reason); - - void assetsFinished(); - void assetsFailed(QString reason); + bool abort() override; + void subtaskSucceeded(); + void subtaskFailed(QString error); private: - NetJobPtr jarlibDownloadJob; - NetJobPtr legacyDownloadJob; - - /// target version, determined during this task - std::shared_ptr<MinecraftVersion> targetVersion; - /// the task that is spawned for version updates - std::shared_ptr<Task> versionUpdateTask; + void next(); +private: OneSixInstance *m_inst = nullptr; - QList<FMLlib> fmlLibsToProcess; + QList<std::shared_ptr<Task>> m_tasks; + QString m_preFailure; + int m_currentTask = -1; + bool m_abort = false; }; diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp b/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp new file mode 100644 index 00000000..21600ff0 --- /dev/null +++ b/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp @@ -0,0 +1,99 @@ +#include "Env.h" +#include "AssetUpdateTask.h" +#include "minecraft/onesix/OneSixInstance.h" +#include "net/ChecksumValidator.h" +#include "minecraft/AssetsUtils.h" + +AssetUpdateTask::AssetUpdateTask(OneSixInstance * inst) +{ + m_inst = inst; +} +void AssetUpdateTask::executeTask() +{ + setStatus(tr("Updating assets index...")); + auto profile = m_inst->getMinecraftProfile(); + auto assets = profile->getMinecraftAssets(); + QUrl indexUrl = assets->url; + QString localPath = assets->id + ".json"; + auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); + + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + entry->setStale(true); + auto hexSha1 = assets->sha1.toLatin1(); + qDebug() << "Asset index SHA1:" << hexSha1; + auto dl = Net::Download::makeCached(indexUrl, entry); + auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + job->addNetAction(dl); + + downloadJob.reset(job); + + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + + qDebug() << m_inst->name() << ": Starting asset index download"; + downloadJob->start(); +} + +bool AssetUpdateTask::canAbort() const +{ + return true; +} + +void AssetUpdateTask::assetIndexFinished() +{ + AssetsIndex index; + qDebug() << m_inst->name() << ": Finished asset index download"; + + auto profile = m_inst->getMinecraftProfile(); + auto assets = profile->getMinecraftAssets(); + + QString asset_fname = "assets/indexes/" + assets->id + ".json"; + // FIXME: this looks like a job for a generic validator based on json schema? + if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) + { + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); + metacache->evictEntry(entry); + emitFailed(tr("Failed to read the assets index!")); + } + + auto job = index.getDownloadJob(); + if(job) + { + setStatus(tr("Getting the assets files from Mojang...")); + downloadJob = job; + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + downloadJob->start(); + return; + } + emitSucceeded(); +} + +void AssetUpdateTask::assetIndexFailed(QString reason) +{ + qDebug() << m_inst->name() << ": Failed asset index download"; + emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); +} + +void AssetUpdateTask::assetsFailed(QString reason) +{ + emitFailed(tr("Failed to download assets:\n%1").arg(reason)); +} + +bool AssetUpdateTask::abort() +{ + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted FMLLibrariesTask"; + } + return true; +} diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.h b/api/logic/minecraft/onesix/update/AssetUpdateTask.h new file mode 100644 index 00000000..dff72571 --- /dev/null +++ b/api/logic/minecraft/onesix/update/AssetUpdateTask.h @@ -0,0 +1,25 @@ +#pragma once +#include "tasks/Task.h" +#include "net/NetJob.h" +class OneSixInstance; + +class AssetUpdateTask : public Task +{ +public: + AssetUpdateTask(OneSixInstance * inst); + void executeTask() override; + + bool canAbort() const override; + +private slots: + void assetIndexFinished(); + void assetIndexFailed(QString reason); + void assetsFailed(QString reason); + +public slots: + bool abort() override; + +private: + OneSixInstance *m_inst; + NetJobPtr downloadJob; +}; diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp b/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp new file mode 100644 index 00000000..1cbee95e --- /dev/null +++ b/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp @@ -0,0 +1,132 @@ +#include "Env.h" +#include <FileSystem.h> +#include <minecraft/VersionFilterData.h> +#include "FMLLibrariesTask.h" +#include "minecraft/onesix/OneSixInstance.h" + + +FMLLibrariesTask::FMLLibrariesTask(OneSixInstance * inst) +{ + m_inst = inst; +} +void FMLLibrariesTask::executeTask() +{ + // Get the mod list + OneSixInstance *inst = (OneSixInstance *)m_inst; + std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); + bool forge_present = false; + + if (!profile->hasTrait("legacyFML")) + { + emitSucceeded(); + } + + QString version = inst->intendedVersionId(); + auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; + if (!fmlLibsMapping.contains(version)) + { + emitSucceeded(); + return; + } + + auto &libList = fmlLibsMapping[version]; + + // determine if we need some libs for FML or forge + setStatus(tr("Checking for FML libraries...")); + forge_present = (profile->versionPatch("net.minecraftforge") != nullptr); + // we don't... + if (!forge_present) + { + emitSucceeded(); + return; + } + + // now check the lib folder inside the instance for files. + for (auto &lib : libList) + { + QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); + if (libInfo.exists()) + continue; + fmlLibsToProcess.append(lib); + } + + // if everything is in place, there's nothing to do here... + if (fmlLibsToProcess.isEmpty()) + { + emitSucceeded(); + return; + } + + // download missing libs to our place + setStatus(tr("Dowloading FML libraries...")); + auto dljob = new NetJob("FML libraries"); + auto metacache = ENV.metacache(); + for (auto &lib : fmlLibsToProcess) + { + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename + : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; + dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); + } + + connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); + connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); + downloadJob.reset(dljob); + downloadJob->start(); +} + +bool FMLLibrariesTask::canAbort() const +{ + return true; +} + +void FMLLibrariesTask::fmllibsFinished() +{ + downloadJob.reset(); + if (!fmlLibsToProcess.isEmpty()) + { + setStatus(tr("Copying FML libraries into the instance...")); + OneSixInstance *inst = (OneSixInstance *)m_inst; + auto metacache = ENV.metacache(); + int index = 0; + for (auto &lib : fmlLibsToProcess) + { + progress(index, fmlLibsToProcess.size()); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = FS::PathCombine(inst->libDir(), lib.filename); + if (!FS::ensureFilePathExists(path)) + { + emitFailed(tr("Failed creating FML library folder inside the instance.")); + return; + } + if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) + { + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); + return; + } + index++; + } + progress(index, fmlLibsToProcess.size()); + } + emitSucceeded(); +} +void FMLLibrariesTask::fmllibsFailed(QString reason) +{ + QStringList failed = downloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); +} + +bool FMLLibrariesTask::abort() +{ + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted FMLLibrariesTask"; + } + return true; +} diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h b/api/logic/minecraft/onesix/update/FMLLibrariesTask.h new file mode 100644 index 00000000..d1c250e4 --- /dev/null +++ b/api/logic/minecraft/onesix/update/FMLLibrariesTask.h @@ -0,0 +1,27 @@ +#pragma once +#include "tasks/Task.h" +#include "net/NetJob.h" +class OneSixInstance; + +class FMLLibrariesTask : public Task +{ +public: + FMLLibrariesTask(OneSixInstance * inst); + + void executeTask() override; + + bool canAbort() const override; + +private slots: + void fmllibsFinished(); + void fmllibsFailed(QString reason); + +public slots: + bool abort() override; + +private: + OneSixInstance *m_inst; + NetJobPtr downloadJob; + QList<FMLlib> fmlLibsToProcess; +}; + diff --git a/api/logic/minecraft/onesix/update/FoldersTask.cpp b/api/logic/minecraft/onesix/update/FoldersTask.cpp new file mode 100644 index 00000000..239a2675 --- /dev/null +++ b/api/logic/minecraft/onesix/update/FoldersTask.cpp @@ -0,0 +1,20 @@ +#include "FoldersTask.h" +#include "minecraft/onesix/OneSixInstance.h" +#include <QDir> + +FoldersTask::FoldersTask(OneSixInstance * inst) +{ + m_inst = inst; +} + +void FoldersTask::executeTask() +{ + // Make directories + QDir mcDir(m_inst->minecraftRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed(tr("Failed to create folder for minecraft binaries.")); + return; + } + emitSucceeded(); +} diff --git a/api/logic/minecraft/onesix/update/FoldersTask.h b/api/logic/minecraft/onesix/update/FoldersTask.h new file mode 100644 index 00000000..552d3098 --- /dev/null +++ b/api/logic/minecraft/onesix/update/FoldersTask.h @@ -0,0 +1,14 @@ +#pragma once + +#include "tasks/Task.h" + +class OneSixInstance; +class FoldersTask : public Task +{ +public: + FoldersTask(OneSixInstance * inst); + void executeTask() override; +private: + OneSixInstance *m_inst; +}; + diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.cpp b/api/logic/minecraft/onesix/update/LibrariesTask.cpp new file mode 100644 index 00000000..123b14a3 --- /dev/null +++ b/api/logic/minecraft/onesix/update/LibrariesTask.cpp @@ -0,0 +1,89 @@ +#include "Env.h" +#include "LibrariesTask.h" +#include "minecraft/onesix/OneSixInstance.h" + +LibrariesTask::LibrariesTask(OneSixInstance * inst) +{ + m_inst = inst; +} + +void LibrariesTask::executeTask() +{ + setStatus(tr("Getting the library files from Mojang...")); + qDebug() << m_inst->name() << ": downloading libraries"; + OneSixInstance *inst = (OneSixInstance *)m_inst; + inst->reloadProfile(); + if(inst->flags() & BaseInstance::VersionBrokenFlag) + { + emitFailed(tr("Failed to load the version description files - check the instance for errors.")); + return; + } + + // Build a list of URLs that will need to be downloaded. + std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile(); + // minecraft.jar for this version + { + QString version_id = profile->getMinecraftVersion(); + QString localPath = version_id + "/" + version_id + ".jar"; + QString urlstr = profile->getMainJarUrl(); + + auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); + + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("versions", localPath); + job->addNetAction(Net::Download::makeCached(QUrl(urlstr), entry)); + downloadJob.reset(job); + } + + auto libs = profile->getLibraries(); + + auto metacache = ENV.metacache(); + QList<LibraryPtr> brokenLocalLibs; + QStringList failedFiles; + for (auto lib : libs) + { + auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles); + for(auto dl : dls) + { + downloadJob->addNetAction(dl); + } + } + // FIXME: this is never filled!!!! + if (!brokenLocalLibs.empty()) + { + downloadJob.reset(); + QString failed_all = failedFiles.join("\n"); + emitFailed(tr("Some libraries marked as 'local' are missing their jar " + "files:\n%1\n\nYou'll have to correct this problem manually. If this is " + "an externally tracked instance, make sure to run it at least once " + "outside of MultiMC.").arg(failed_all)); + return; + } + connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); + connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); + downloadJob->start(); +} + +bool LibrariesTask::canAbort() const +{ + return true; +} + +void LibrariesTask::jarlibFailed(QString reason) +{ + emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); +} + +bool LibrariesTask::abort() +{ + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted LibrariesTask"; + } + return true; +} diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.h b/api/logic/minecraft/onesix/update/LibrariesTask.h new file mode 100644 index 00000000..80cf0d2a --- /dev/null +++ b/api/logic/minecraft/onesix/update/LibrariesTask.h @@ -0,0 +1,24 @@ +#pragma once +#include "tasks/Task.h" +#include "net/NetJob.h" +class OneSixInstance; + +class LibrariesTask : public Task +{ +public: + LibrariesTask(OneSixInstance * inst); + + void executeTask() override; + + bool canAbort() const override; + +private slots: + void jarlibFailed(QString reason); + +public slots: + bool abort() override; + +private: + OneSixInstance *m_inst; + NetJobPtr downloadJob; +}; |