From 042f3ef55c0b469f438542152c4eb02b0789ea3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 14 Aug 2016 02:33:31 +0200 Subject: GH-352 Make OneSix instance update downloads cancellable --- api/logic/minecraft/onesix/OneSixUpdate.cpp | 346 ++++++---------------------- 1 file changed, 76 insertions(+), 270 deletions(-) (limited to 'api/logic/minecraft/onesix/OneSixUpdate.cpp') 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 - #include #include #include #include -#include #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 -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(m_inst)); } - // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = std::dynamic_pointer_cast(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(ENV.getVersionList("net.minecraft")); + auto version = std::dynamic_pointer_cast(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(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(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(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(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 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 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 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 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; } - -- cgit v1.2.3