diff options
Diffstat (limited to 'api/logic/FolderInstanceProvider.cpp')
-rw-r--r-- | api/logic/FolderInstanceProvider.cpp | 207 |
1 files changed, 173 insertions, 34 deletions
diff --git a/api/logic/FolderInstanceProvider.cpp b/api/logic/FolderInstanceProvider.cpp index ea0d4ef0..a6d3bdc8 100644 --- a/api/logic/FolderInstanceProvider.cpp +++ b/api/logic/FolderInstanceProvider.cpp @@ -1,7 +1,7 @@ #include "FolderInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" -#include "minecraft/onesix/OneSixInstance.h" +#include "minecraft/MinecraftInstance.h" #include "minecraft/legacy/LegacyInstance.h" #include "NullInstance.h" @@ -12,6 +12,7 @@ #include <QJsonObject> #include <QJsonArray> #include <QUuid> +#include <QTimer> const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -33,11 +34,13 @@ struct WatchLock FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir) : BaseInstanceProvider(settings) { - m_instDir = instDir; - if (!QDir::current().exists(m_instDir)) + // Create aand normalize path + if (!QDir::current().exists(instDir)) { - QDir::current().mkpath(m_instDir); + QDir::current().mkpath(instDir); } + // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! + m_instDir = QDir(instDir).canonicalPath(); m_watcher = new QFileSystemWatcher(this); connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged); m_watcher->addPath(m_instDir); @@ -46,7 +49,7 @@ FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QList< InstanceId > FolderInstanceProvider::discoverInstances() { QList<InstanceId> out; - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks); + QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); while (iter.hasNext()) { QString subDir = iter.next(); @@ -88,7 +91,7 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) if (inst_type == "OneSix" || inst_type == "Nostalgia") { - inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot)); + inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); } else if (inst_type == "Legacy") { @@ -110,24 +113,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) return inst; } -#include "InstanceImportTask.h" -Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon) -{ - return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup); -} - -#include "InstanceCreationTask.h" -Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon) -{ - return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup); -} - -#include "InstanceCopyTask.h" -Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves) -{ - return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves); -} - void FolderInstanceProvider::saveGroupList() { WatchLock foo(m_watcher, m_instDir); @@ -298,7 +283,7 @@ void FolderInstanceProvider::instanceDirContentsChanged(const QString& path) void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value) { - QString newInstDir = value.toString(); + QString newInstDir = QDir(value.toString()).canonicalPath(); if(newInstDir != m_instDir) { if(m_groupsLoaded) @@ -311,6 +296,164 @@ void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVaria } } +template <typename T> +static void clamp(T& current, T min, T max) +{ + if (current < min) + { + current = min; + } + else if(current > max) + { + current = max; + } +} + +// List of numbers from min to max. Next is exponent times bigger than previous. +class ExponentialSeries +{ +public: + ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) + { + m_current = m_min = min; + m_max = max; + m_exponent = exponent; + } + void reset() + { + m_current = m_min; + } + unsigned operator()() + { + unsigned retval = m_current; + m_current *= m_exponent; + clamp(m_current, m_min, m_max); + return retval; + } + unsigned m_current; + unsigned m_min; + unsigned m_max; + unsigned m_exponent; +}; + +/* + * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. + * Basically, it starts messing things up while MultiMC is extracting/creating instances + * and causes that horrible failure that is NTFS to lock files in place because they are open. + */ +class FolderInstanceStaging : public Task +{ +Q_OBJECT + const unsigned minBackoff = 1; + const unsigned maxBackoff = 16; +public: + FolderInstanceStaging ( + FolderInstanceProvider * parent, + Task * child, + const QString & stagingPath, + const QString& instanceName, + const QString& groupName ) + : backoff(minBackoff, maxBackoff) + { + m_parent = parent; + m_child.reset(child); + connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded); + connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed); + connect(child, &Task::status, this, &FolderInstanceStaging::setStatus); + connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress); + m_instanceName = instanceName; + m_groupName = groupName; + m_stagingPath = stagingPath; + m_backoffTimer.setSingleShot(true); + connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded); + } + +protected: + virtual void executeTask() override + { + m_child->start(); + } + QStringList warnings() const override + { + return m_child->warnings(); + } + +private slots: + void childSucceded() + { + unsigned sleepTime = backoff(); + if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) + { + emitSucceeded(); + return; + } + // we actually failed, retry? + if(sleepTime == maxBackoff) + { + emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); + return; + } + qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + m_backoffTimer.start(sleepTime * 500); + } + void childFailed(const QString & reason) + { + m_parent->destroyStagingPath(m_stagingPath); + emitFailed(reason); + } + +private: + ExponentialSeries backoff; + QString m_stagingPath; + FolderInstanceProvider * m_parent; + unique_qobject_ptr<Task> m_child; + QString m_instanceName; + QString m_groupName; + QTimer m_backoffTimer; +}; + +#include "InstanceImportTask.h" +Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceImportTask(m_globalSettings, sourceUrl, stagingPath, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include "InstanceCreationTask.h" +Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceCreationTask(m_globalSettings, stagingPath, version, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include <modplatform/FtbPackInstallTask.h> +Task * FolderInstanceProvider::ftbCreationTask(FtbPackDownloader *downloader, const QString& instName, const QString& instGroup, const QString& instIcon) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new FtbPackInstallTask(downloader, m_globalSettings, stagingPath, instName, instIcon, instGroup); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +#include "InstanceCopyTask.h" +Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves) +{ + auto stagingPath = getStagedInstancePath(); + auto task = new InstanceCopyTask(m_globalSettings, stagingPath, oldInstance, instName, instIcon, instGroup, copySaves); + return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup); +} + +// FIXME: find a better place for this +#include "minecraft/legacy/LegacyUpgradeTask.h" +Task * FolderInstanceProvider::legacyUpgradeTask(const InstancePtr& oldInstance) +{ + auto stagingPath = getStagedInstancePath(); + QString newName = tr("%1 (Migrated)").arg(oldInstance->name()); + auto task = new LegacyUpgradeTask(m_globalSettings, stagingPath, oldInstance, newName); + return new FolderInstanceStaging(this, task, stagingPath, newName, oldInstance->group()); +} + QString FolderInstanceProvider::getStagedInstancePath() { QString key = QUuid::createUuid().toString(); @@ -324,21 +467,16 @@ QString FolderInstanceProvider::getStagedInstancePath() return path; } -bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName, - const QString& groupName) +bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) { - if(!path.contains(keyPath)) - { - qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath; - return false; - } QDir dir; QString instID = FS::DirNameFromString(instanceName, m_instDir); { WatchLock lock(m_watcher, m_instDir); - if(!dir.rename(path, FS::PathCombine(m_instDir, instID))) + QString destination = FS::PathCombine(m_instDir, instID); + if(!dir.rename(path, destination)) { - destroyStagingPath(keyPath); + qWarning() << "Failed to move" << path << "to" << destination; return false; } groupMap[instID] = groupName; @@ -354,3 +492,4 @@ bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath) return FS::deletePath(keyPath); } +#include "FolderInstanceProvider.moc" |