summaryrefslogtreecommitdiffstats
path: root/api/logic/FolderInstanceProvider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/FolderInstanceProvider.cpp')
-rw-r--r--api/logic/FolderInstanceProvider.cpp207
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"