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: [PATCH] GH-352 Make OneSix instance update downloads cancellable --- api/logic/BaseInstance.h | 3 +- api/logic/CMakeLists.txt | 8 + api/logic/NullInstance.h | 2 +- api/logic/QObjectPtr.h | 4 + api/logic/launch/steps/Update.cpp | 32 +- api/logic/launch/steps/Update.h | 16 +- api/logic/minecraft/MinecraftVersionList.cpp | 25 ++ api/logic/minecraft/forge/ForgeXzDownload.cpp | 44 +- api/logic/minecraft/forge/ForgeXzDownload.h | 12 +- api/logic/minecraft/ftb/OneSixFTBInstance.cpp | 2 +- api/logic/minecraft/ftb/OneSixFTBInstance.h | 2 +- api/logic/minecraft/legacy/LegacyInstance.cpp | 4 +- api/logic/minecraft/legacy/LegacyInstance.h | 2 +- api/logic/minecraft/onesix/OneSixInstance.cpp | 4 +- api/logic/minecraft/onesix/OneSixInstance.h | 2 +- api/logic/minecraft/onesix/OneSixUpdate.cpp | 392 +++++------------- api/logic/minecraft/onesix/OneSixUpdate.h | 36 +- .../onesix/update/AssetUpdateTask.cpp | 99 +++++ .../minecraft/onesix/update/AssetUpdateTask.h | 25 ++ .../onesix/update/FMLLibrariesTask.cpp | 132 ++++++ .../onesix/update/FMLLibrariesTask.h | 27 ++ .../minecraft/onesix/update/FoldersTask.cpp | 20 + .../minecraft/onesix/update/FoldersTask.h | 14 + .../minecraft/onesix/update/LibrariesTask.cpp | 89 ++++ .../minecraft/onesix/update/LibrariesTask.h | 24 ++ api/logic/net/Download.cpp | 48 ++- api/logic/net/Download.h | 14 +- api/logic/net/NetAction.h | 12 +- api/logic/net/NetJob.cpp | 49 +++ api/logic/net/NetJob.h | 11 +- application/InstanceWindow.cpp | 1 + application/MainWindow.cpp | 1 + 32 files changed, 796 insertions(+), 360 deletions(-) create mode 100644 api/logic/minecraft/onesix/update/AssetUpdateTask.cpp create mode 100644 api/logic/minecraft/onesix/update/AssetUpdateTask.h create mode 100644 api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp create mode 100644 api/logic/minecraft/onesix/update/FMLLibrariesTask.h create mode 100644 api/logic/minecraft/onesix/update/FoldersTask.cpp create mode 100644 api/logic/minecraft/onesix/update/FoldersTask.h create mode 100644 api/logic/minecraft/onesix/update/LibrariesTask.cpp create mode 100644 api/logic/minecraft/onesix/update/LibrariesTask.h diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index 9a6976cb..ebaaeb83 100644 --- a/api/logic/BaseInstance.h +++ b/api/logic/BaseInstance.h @@ -16,6 +16,7 @@ #pragma once #include +#include "QObjectPtr.h" #include #include #include @@ -152,7 +153,7 @@ public: virtual SettingsObjectPtr settings() const; /// returns a valid update task - virtual std::shared_ptr createUpdateTask() = 0; + virtual shared_qobject_ptr createUpdateTask() = 0; /// returns a valid launcher (task container) virtual std::shared_ptr createLaunchTask(AuthSessionPtr account) = 0; diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index 4514d8c3..415584fc 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -200,6 +200,14 @@ set(MINECRAFT_SOURCES minecraft/onesix/OneSixProfileStrategy.h minecraft/onesix/OneSixVersionFormat.cpp minecraft/onesix/OneSixVersionFormat.h + minecraft/onesix/update/AssetUpdateTask.h + minecraft/onesix/update/AssetUpdateTask.cpp + minecraft/onesix/update/FMLLibrariesTask.cpp + minecraft/onesix/update/FMLLibrariesTask.h + minecraft/onesix/update/FoldersTask.cpp + minecraft/onesix/update/FoldersTask.h + minecraft/onesix/update/LibrariesTask.cpp + minecraft/onesix/update/LibrariesTask.h minecraft/launch/CreateServerResourcePacksFolder.cpp minecraft/launch/CreateServerResourcePacksFolder.h minecraft/launch/ModMinecraftJar.cpp diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h index d87fb6f7..4fb31854 100644 --- a/api/logic/NullInstance.h +++ b/api/logic/NullInstance.h @@ -48,7 +48,7 @@ public: { return nullptr; } - virtual std::shared_ptr< Task > createUpdateTask() override + virtual shared_qobject_ptr< Task > createUpdateTask() override { return nullptr; } diff --git a/api/logic/QObjectPtr.h b/api/logic/QObjectPtr.h index b81b3234..000d228d 100644 --- a/api/logic/QObjectPtr.h +++ b/api/logic/QObjectPtr.h @@ -48,6 +48,10 @@ public: using namespace std::placeholders; m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); } + void reset(const shared_qobject_ptr &other) + { + m_ptr = other.m_ptr; + } void reset() { m_ptr.reset(); diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp index 4901f001..ad0c4fc4 100644 --- a/api/logic/launch/steps/Update.cpp +++ b/api/logic/launch/steps/Update.cpp @@ -18,7 +18,12 @@ void Update::executeTask() { - m_updateTask = m_parent->instance()->createUpdateTask(); + if(m_aborted) + { + emitFailed(tr("Task aborted.")); + return; + } + m_updateTask.reset(m_parent->instance()->createUpdateTask()); if(m_updateTask) { connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); @@ -39,12 +44,37 @@ void Update::updateFinished() { if(m_updateTask->successful()) { + m_updateTask.reset(); emitSucceeded(); } else { QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason()); + m_updateTask.reset(); emit logLine(reason, MessageLevel::Fatal); emitFailed(reason); } } + +bool Update::canAbort() const +{ + if(m_updateTask) + { + return m_updateTask->canAbort(); + } + return true; +} + + +bool Update::abort() +{ + m_aborted = true; + if(m_updateTask) + { + if(m_updateTask->canAbort()) + { + return m_updateTask->abort(); + } + } + return true; +} diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h index 14928253..1739de47 100644 --- a/api/logic/launch/steps/Update.h +++ b/api/logic/launch/steps/Update.h @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include @@ -27,15 +28,16 @@ public: explicit Update(LaunchTask *parent):LaunchStep(parent) {}; virtual ~Update() {}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } - virtual void proceed(); + void executeTask() override; + bool canAbort() const override; + void proceed() override; +public slots: + bool abort() override; + private slots: void updateFinished(); private: - std::shared_ptr m_updateTask; + shared_qobject_ptr m_updateTask; + bool m_aborted = false; }; 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 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 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 OneSixFTBInstance::createUpdateTask() +shared_qobject_ptr 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 createUpdateTask() override; + virtual shared_qobject_ptr 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 LegacyInstance::createUpdateTask() +shared_qobject_ptr 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(new LegacyUpdate(this, this)); + return shared_qobject_ptr(new LegacyUpdate(this, this)); } std::shared_ptr 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 createUpdateTask() override; + virtual shared_qobject_ptr createUpdateTask() override; virtual std::shared_ptr 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 OneSixInstance::traits() } } -std::shared_ptr OneSixInstance::createUpdateTask() +shared_qobject_ptr OneSixInstance::createUpdateTask() { - return std::shared_ptr(new OneSixUpdate(this)); + return shared_qobject_ptr(new OneSixUpdate(this)); } QString replaceTokensIn(QString text, QMap 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 createUpdateTask() override; + virtual shared_qobject_ptr createUpdateTask() override; virtual std::shared_ptr 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 - #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 +#include "update/FoldersTask.h" +#include "update/LibrariesTask.h" +#include "update/FMLLibrariesTask.h" +#include "update/AssetUpdateTask.h" + OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { + // create folders + { + m_tasks.append(std::make_shared(m_inst)); + } + + // add a version update task, if necessary + { + 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); + } + } + } + + // libraries download + { + m_tasks.append(std::make_shared(m_inst)); + } + + // FML libraries download and copy into the instance + { + m_tasks.append(std::make_shared(m_inst)); + } + + // assets update + { + m_tasks.append(std::make_shared(m_inst)); + } } void OneSixUpdate::executeTask() { - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) + if(!m_preFailure.isEmpty()) { - emitFailed(tr("Failed to create folder for minecraft binaries.")); + emitFailed(m_preFailure); return; } + next(); +} - // 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) +void OneSixUpdate::next() +{ + if(m_abort) { - // don't do anything if it was invalid - emitFailed(tr("The specified Minecraft version is invalid. Choose a different one.")); + emitFailed(tr("Aborted by user.")); return; } - if (m_inst->providesVersionFile() || !targetVersion->needsUpdate()) + m_currentTask ++; + if(m_currentTask > 0) { - qDebug() << "Instance either provides a version file or doesn't need an update."; - jarlibStart(); + 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(m_currentTask == m_tasks.size()) + { + emitSucceeded(); return; } - versionUpdateTask = std::dynamic_pointer_cast(ENV.getVersionList("net.minecraft"))->createUpdateTask(m_inst->intendedVersionId()); - if (!versionUpdateTask) + 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::subtaskSucceeded() +{ + next(); +} + +void OneSixUpdate::subtaskFailed(QString error) +{ + emitFailed(error); +} + + +bool OneSixUpdate::abort() +{ + if(!m_abort) { - qDebug() << "Didn't spawn an update task."; - jarlibStart(); - return; - } - 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)) - { - 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...")); - 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; - } - 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() -{ - 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 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)); - jarlibDownloadJob.reset(job); - } - - auto libs = profile->getLibraries(); - - auto metacache = ENV.metacache(); - QList brokenLocalLibs; - - QStringList failedFiles; - for (auto lib : libs) - { - auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles); - for(auto dl : dls) + m_abort = true; + auto task = m_tasks[m_currentTask]; + if(task->canAbort()) { - jarlibDownloadJob->addNetAction(dl); + return task->abort(); } } - if (!brokenLocalLibs.empty()) - { - 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)); - 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(); + return true; } -void OneSixUpdate::jarlibFinished() +bool OneSixUpdate::canAbort() const { - OneSixInstance *inst = (OneSixInstance *)m_inst; - std::shared_ptr profile = inst->getMinecraftProfile(); - - if (profile->hasTrait("legacyFML")) - { - fmllibsStart(); - } - else - { - assetIndexStart(); - } + return true; } - -void OneSixUpdate::jarlibFailed(QString reason) -{ - 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)); -} - -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() -{ - legacyDownloadJob.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()); - } - assetIndexStart(); -} - -void OneSixUpdate::fmllibsFailed(QString reason) -{ - emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries.\nReason:\n%1").arg(reason)); - return; -} - 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 targetVersion; - /// the task that is spawned for version updates - std::shared_ptr versionUpdateTask; + void next(); +private: OneSixInstance *m_inst = nullptr; - QList fmlLibsToProcess; + QList> 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 +#include +#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 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 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 + +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 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 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; +}; diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp index 70fe7608..7610cae3 100644 --- a/api/logic/net/Download.cpp +++ b/api/logic/net/Download.cpp @@ -65,6 +65,12 @@ void Download::addValidator(Validator * v) void Download::start() { + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } QNetworkRequest request(m_url); m_status = m_sink->init(request); switch(m_status) @@ -80,6 +86,8 @@ void Download::start() case Job_Failed: emit failed(m_index_within_job); return; + case Job_Aborted: + return; } request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0"); @@ -103,9 +111,17 @@ void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) void Download::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - 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; + } } bool Download::handleRedirect() @@ -154,6 +170,14 @@ void Download::downloadFinished() emit failed(m_index_within_job); return; } + else if(m_status == Job_Aborted) + { + qDebug() << "Download aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(m_index_within_job); + return; + } // make sure we got all the remaining data, if any auto data = m_reply->readAll(); @@ -197,3 +221,21 @@ void Download::downloadReadyRead() } } + +bool Net::Download::abort() +{ + if(m_reply) + { + m_reply->abort(); + } + else + { + m_status = Job_Aborted; + } + return true; +} + +bool Net::Download::canAbort() +{ + return true; +} diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h index 8e38dfc9..fab39ffe 100644 --- a/api/logic/net/Download.h +++ b/api/logic/net/Download.h @@ -38,25 +38,25 @@ public: static Download::Ptr makeFile(QUrl url, QString path); public: /* methods */ - // FIXME: remove this QString getTargetFilepath() { return m_target_path; } - // FIXME: remove this void addValidator(Validator * v); + bool abort() override; + bool canAbort() override; private: /* methods */ bool handleRedirect(); 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; private: /* data */ // FIXME: remove this, it has no business being here. diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h index 2b3f1b7b..07456ce1 100644 --- a/api/logic/net/NetAction.h +++ b/api/logic/net/NetAction.h @@ -28,7 +28,8 @@ enum JobStatus Job_NotStarted, Job_InProgress, Job_Finished, - Job_Failed + Job_Failed, + Job_Aborted }; typedef std::shared_ptr NetActionPtr; @@ -54,6 +55,14 @@ public: { return m_failures; } + virtual bool abort() + { + return false; + } + virtual bool canAbort() + { + return false; + } public: /// the network reply @@ -79,6 +88,7 @@ signals: void netActionProgress(int index, qint64 current, qint64 total); void succeeded(int index); void failed(int index); + void aborted(int index); protected slots: diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp index ca86242a..2e375760 100644 --- a/api/logic/net/NetJob.cpp +++ b/api/logic/net/NetJob.cpp @@ -47,6 +47,15 @@ void NetJob::partFailed(int index) startMoreParts(); } +void NetJob::partAborted(int index) +{ + m_aborted = true; + m_doing.remove(index); + m_failed.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); +} + void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { auto &slot = parts_progress[index]; @@ -85,6 +94,11 @@ void NetJob::startMoreParts() qDebug() << m_job_name << "succeeded."; emitSucceeded(); } + else if(m_aborted) + { + qDebug() << m_job_name << "aborted."; + emitFailed(tr("Job '%1' aborted.").arg(m_job_name)); + } else { qCritical() << m_job_name << "failed."; @@ -104,6 +118,7 @@ void NetJob::startMoreParts() // connect signals :D connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); part->start(); @@ -121,3 +136,37 @@ QStringList NetJob::getFailedFiles() failed.sort(); return failed; } + +bool NetJob::canAbort() const +{ + bool canFullyAbort = true; + // can abort the waiting? + for(auto index: m_todo) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + // can abort the active? + for(auto index: m_doing) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + return canFullyAbort; +} + +bool NetJob::abort() +{ + bool fullyAborted = true; + // fail all waiting + m_failed.unite(m_todo.toSet()); + m_todo.clear(); + // abort active + auto toKill = m_doing.toList(); + for(auto index: toKill) + { + auto part = downloads[index]; + fullyAborted &= part->abort(); + } + return fullyAborted; +} diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h index 3d16b21f..e1c266dd 100644 --- a/api/logic/net/NetJob.h +++ b/api/logic/net/NetJob.h @@ -75,24 +75,26 @@ public: { return downloads.size(); } - virtual bool isRunning() const + virtual bool isRunning() const override { return m_running; } QStringList getFailedFiles(); + bool canAbort() const override; + private slots: void startMoreParts(); public slots: - virtual void executeTask(); - // FIXME: implement - virtual bool abort() {return false;}; + virtual void executeTask() override; + virtual bool abort() override; private slots: void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); void partSucceeded(int index); void partFailed(int index); + void partAborted(int index); private: struct part_info @@ -112,4 +114,5 @@ private: qint64 current_progress = 0; qint64 total_progress = 0; bool m_running = false; + bool m_aborted = false; }; diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp index 0b9d7019..6b24f488 100644 --- a/application/InstanceWindow.cpp +++ b/application/InstanceWindow.cpp @@ -212,6 +212,7 @@ void InstanceWindow::onFailed(QString reason) void InstanceWindow::onProgressRequested(Task* task) { ProgressDialog progDialog(this); + progDialog.setSkipButton(true, tr("Abort")); m_proc->proceed(); progDialog.execWithTask(task); } diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp index ce6f7181..92f2260b 100644 --- a/application/MainWindow.cpp +++ b/application/MainWindow.cpp @@ -1185,6 +1185,7 @@ void MainWindow::finalizeInstance(InstancePtr inst) }); if(update) { + loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(update.get()); } }