summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2017-11-11 01:38:31 +0100
committerPetr Mrázek <peterix@gmail.com>2017-12-03 01:22:34 +0100
commit85ae710d407eb31527183d5f8bec0399eb209f33 (patch)
tree23647a83506ad3bd052720fb40068240e9768481
parent17c8f31a09da6bdfc4aa7f67b2ca86b791f2ba96 (diff)
downloadMultiMC-85ae710d407eb31527183d5f8bec0399eb209f33.tar
MultiMC-85ae710d407eb31527183d5f8bec0399eb209f33.tar.gz
MultiMC-85ae710d407eb31527183d5f8bec0399eb209f33.tar.lz
MultiMC-85ae710d407eb31527183d5f8bec0399eb209f33.tar.xz
MultiMC-85ae710d407eb31527183d5f8bec0399eb209f33.zip
GH-2026 implement changes necessary to support 1.13 snapshots
-rw-r--r--api/logic/BaseInstance.cpp2
-rw-r--r--api/logic/BaseInstance.h6
-rw-r--r--api/logic/CMakeLists.txt8
-rw-r--r--api/logic/InstanceCreationTask.cpp13
-rw-r--r--api/logic/InstanceImportTask.cpp6
-rw-r--r--api/logic/NullInstance.h2
-rw-r--r--api/logic/ProblemProvider.h6
-rw-r--r--api/logic/launch/steps/Update.cpp2
-rw-r--r--api/logic/launch/steps/Update.h4
-rw-r--r--api/logic/meta/BaseEntity.cpp4
-rw-r--r--api/logic/meta/BaseEntity.h3
-rw-r--r--api/logic/meta/JsonFormat.cpp66
-rw-r--r--api/logic/meta/JsonFormat.h33
-rw-r--r--api/logic/meta/Version.cpp19
-rw-r--r--api/logic/meta/Version.h15
-rw-r--r--api/logic/meta/VersionList.cpp31
-rw-r--r--api/logic/minecraft/Component.cpp408
-rw-r--r--api/logic/minecraft/Component.h104
-rw-r--r--api/logic/minecraft/ComponentList.cpp1140
-rw-r--r--api/logic/minecraft/ComponentList.h93
-rw-r--r--api/logic/minecraft/ComponentList_p.h42
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.cpp688
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.h37
-rw-r--r--api/logic/minecraft/ComponentUpdateTask_p.h32
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp127
-rw-r--r--api/logic/minecraft/MinecraftInstance.h12
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.cpp45
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.h47
-rw-r--r--api/logic/minecraft/MinecraftUpdate.cpp41
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.cpp31
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.h2
-rw-r--r--api/logic/minecraft/ProfilePatch.cpp188
-rw-r--r--api/logic/minecraft/ProfilePatch.h71
-rw-r--r--api/logic/minecraft/ProfileUtils.cpp51
-rw-r--r--api/logic/minecraft/ProfileUtils.h3
-rw-r--r--api/logic/minecraft/VersionFile.h21
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.cpp5
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.cpp2
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h2
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.cpp101
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.cpp7
-rw-r--r--api/logic/minecraft/update/LibrariesTask.cpp6
-rw-r--r--api/logic/net/Mode.h10
-rw-r--r--application/LaunchController.cpp2
-rw-r--r--application/MainWindow.cpp2
-rw-r--r--application/dialogs/NewInstanceDialog.cpp2
-rw-r--r--application/pages/ModFolderPage.cpp6
-rw-r--r--application/pages/VersionPage.cpp89
-rw-r--r--application/pages/VersionPage.h3
-rw-r--r--application/pages/VersionPage.ui13
-rw-r--r--application/pages/global/PackagesPage.cpp23
51 files changed, 2625 insertions, 1051 deletions
diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp
index f52b2812..027b8f78 100644
--- a/api/logic/BaseInstance.cpp
+++ b/api/logic/BaseInstance.cpp
@@ -197,7 +197,7 @@ bool BaseInstance::canLaunch() const
return (!hasVersionBroken() && !isRunning());
}
-bool BaseInstance::reload()
+bool BaseInstance::reloadSettings()
{
return m_settings->reload();
}
diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h
index 26d4bc35..d0909031 100644
--- a/api/logic/BaseInstance.h
+++ b/api/logic/BaseInstance.h
@@ -30,6 +30,8 @@
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
+#include "net/Mode.h"
+
#include "multimc_logic_export.h"
class QDir;
@@ -148,7 +150,7 @@ public:
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
- virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
+ virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
@@ -224,7 +226,7 @@ public:
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
- virtual bool reload();
+ bool reloadSettings();
/**
* 'print' a verbose desription of the instance into a QStringList
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
index 42bea98c..8a8ef495 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -239,8 +239,14 @@ set(MINECRAFT_SOURCES
minecraft/MinecraftInstance.h
minecraft/LaunchProfile.cpp
minecraft/LaunchProfile.h
+ minecraft/Component.cpp
+ minecraft/Component.h
minecraft/ComponentList.cpp
minecraft/ComponentList.h
+ minecraft/ComponentUpdateTask.cpp
+ minecraft/ComponentUpdateTask.h
+ minecraft/MinecraftLoadAndCheck.h
+ minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp
@@ -260,8 +266,6 @@ set(MINECRAFT_SOURCES
minecraft/MojangDownloadInfo.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
- minecraft/ProfilePatch.cpp
- minecraft/ProfilePatch.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h
diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp
index 21892f82..8a68815a 100644
--- a/api/logic/InstanceCreationTask.cpp
+++ b/api/logic/InstanceCreationTask.cpp
@@ -5,6 +5,7 @@
//FIXME: remove this
#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version,
const QString& instName, const QString& instIcon, const QString& instGroup)
@@ -25,11 +26,13 @@ void InstanceCreationTask::executeTask()
instanceSettings->suspendSave();
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
- auto inst = new MinecraftInstance(m_globalSettings, instanceSettings, m_stagingPath);
- inst->setComponentVersion("net.minecraft", m_version->descriptor());
- inst->setName(m_instName);
- inst->setIconKey(m_instIcon);
- inst->init();
+ MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = inst.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
+ inst.setName(m_instName);
+ inst.setIconKey(m_instIcon);
+ inst.init();
instanceSettings->resumeSave();
}
emitSucceeded();
diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp
index e2782cd8..80f68458 100644
--- a/api/logic/InstanceImportTask.cpp
+++ b/api/logic/InstanceImportTask.cpp
@@ -242,7 +242,9 @@ void InstanceImportTask::processFlame()
mcVersion.remove(QRegExp("[.]+$"));
qWarning() << "Mysterious trailing dots removed from Minecraft version while importing pack.";
}
- instance.setComponentVersion("net.minecraft", mcVersion);
+ auto components = instance.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
{
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
@@ -257,7 +259,7 @@ void InstanceImportTask::processFlame()
qWarning() << "Could not map recommended forge version for" << mcVersion;
}
}
- instance.setComponentVersion("net.minecraftforge", forgeVersion);
+ components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (m_instIcon != "default")
{
diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h
index f689c5ab..27a8a251 100644
--- a/api/logic/NullInstance.h
+++ b/api/logic/NullInstance.h
@@ -29,7 +29,7 @@ public:
{
return nullptr;
}
- virtual shared_qobject_ptr< Task > createUpdateTask() override
+ virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
{
return nullptr;
}
diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h
index 4040f4fa..978710f0 100644
--- a/api/logic/ProblemProvider.h
+++ b/api/logic/ProblemProvider.h
@@ -1,5 +1,7 @@
#pragma once
+#include "multimc_logic_export.h"
+
enum class ProblemSeverity
{
None,
@@ -13,7 +15,7 @@ struct PatchProblem
QString m_description;
};
-class ProblemProvider
+class MULTIMC_LOGIC_EXPORT ProblemProvider
{
public:
virtual ~ProblemProvider() {};
@@ -21,7 +23,7 @@ public:
virtual ProblemSeverity getProblemSeverity() const = 0;
};
-class ProblemContainer : public ProblemProvider
+class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
{
public:
const QList<PatchProblem> getProblems() const override
diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp
index f06c39eb..42ca3c67 100644
--- a/api/logic/launch/steps/Update.cpp
+++ b/api/logic/launch/steps/Update.cpp
@@ -23,7 +23,7 @@ void Update::executeTask()
emitFailed(tr("Task aborted."));
return;
}
- m_updateTask.reset(m_parent->instance()->createUpdateTask());
+ m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h
index d855a1db..e9573bcd 100644
--- a/api/logic/launch/steps/Update.h
+++ b/api/logic/launch/steps/Update.h
@@ -19,13 +19,14 @@
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
+#include <net/Mode.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep
{
Q_OBJECT
public:
- explicit Update(LaunchTask *parent):LaunchStep(parent) {};
+ explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {};
virtual ~Update() {};
void executeTask() override;
@@ -40,4 +41,5 @@ private slots:
private:
shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false;
+ Net::Mode m_mode = Net::Mode::Offline;
};
diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp
index 6bac71ee..7cf327be 100644
--- a/api/logic/meta/BaseEntity.cpp
+++ b/api/logic/meta/BaseEntity.cpp
@@ -99,7 +99,7 @@ bool Meta::BaseEntity::loadLocalFile()
}
}
-void Meta::BaseEntity::load()
+void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if(!isLoaded())
@@ -110,7 +110,7 @@ void Meta::BaseEntity::load()
}
}
// if we need remote update, run the update task
- if(!shouldStartRemoteUpdate())
+ if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate())
{
return;
}
diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h
index 4483beab..4c74b1a7 100644
--- a/api/logic/meta/BaseEntity.h
+++ b/api/logic/meta/BaseEntity.h
@@ -20,6 +20,7 @@
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
+#include "net/Mode.h"
class Task;
namespace Meta
@@ -54,7 +55,7 @@ public:
bool isLoaded() const;
bool shouldStartRemoteUpdate() const;
- void load();
+ void load(Net::Mode loadType);
shared_qobject_ptr<Task> getCurrentTask();
protected: /* methods */
diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp
index d13c89df..2c313478 100644
--- a/api/logic/meta/JsonFormat.cpp
+++ b/api/logic/meta/JsonFormat.cpp
@@ -51,18 +51,11 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
version->setType(ensureString(obj, "type", QString()));
version->setParentUid(ensureString(obj, "parentUid", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
- if(obj.contains("requires"))
- {
- QHash<QString, QString> requires;
- auto reqobj = requireObject(obj, "requires");
- auto iter = reqobj.begin();
- while(iter != reqobj.end())
- {
- requires[iter.key()] = requireString(iter.value());
- iter++;
- }
- version->setRequires(requires);
- }
+ version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
+ RequireSet requires, conflicts;
+ parseRequires(obj, &requires, "requires");
+ parseRequires(obj, &conflicts, "conflicts");
+ version->setRequires(requires, conflicts);
return version;
}
@@ -145,4 +138,53 @@ void parseVersion(const QJsonObject &obj, Version *ptr)
throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
}
}
+
+/*
+[
+{"uid":"foo", "equals":"version"}
+]
+*/
+void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName)
+{
+ if(obj.contains(keyName))
+ {
+ QSet<QString> requires;
+ auto reqArray = requireArray(obj, keyName);
+ auto iter = reqArray.begin();
+ while(iter != reqArray.end())
+ {
+ auto reqObject = requireObject(*iter);
+ auto uid = requireString(reqObject, "uid");
+ auto equals = ensureString(reqObject, "equals", QString());
+ auto suggests = ensureString(reqObject, "suggests", QString());
+ ptr->insert({uid, equals, suggests});
+ iter++;
+ }
+ }
}
+void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName)
+{
+ if(!ptr || ptr->empty())
+ {
+ return;
+ }
+ QJsonArray arrOut;
+ for(auto &iter: *ptr)
+ {
+ QJsonObject reqOut;
+ reqOut.insert("uid", iter.uid);
+ if(!iter.equalsVersion.isEmpty())
+ {
+ reqOut.insert("equals", iter.equalsVersion);
+ }
+ if(!iter.suggests.isEmpty())
+ {
+ reqOut.insert("suggests", iter.suggests);
+ }
+ arrOut.append(reqOut);
+ }
+ obj.insert(keyName, arrOut);
+}
+
+}
+
diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h
index aaed07fc..9b9dcd91 100644
--- a/api/logic/meta/JsonFormat.h
+++ b/api/logic/meta/JsonFormat.h
@@ -20,6 +20,7 @@
#include "Exception.h"
#include "meta/BaseEntity.h"
+#include <set>
namespace Meta
{
@@ -32,9 +33,41 @@ class ParseException : public Exception
public:
using Exception::Exception;
};
+struct Require
+{
+ bool operator==(const Require & rhs) const
+ {
+ return uid == rhs.uid;
+ }
+ bool operator<(const Require & rhs) const
+ {
+ return uid < rhs.uid;
+ }
+ bool deepEquals(const Require & rhs) const
+ {
+ return uid == rhs.uid
+ && equalsVersion == rhs.equalsVersion
+ && suggests == rhs.suggests;
+ }
+ QString uid;
+ QString equalsVersion;
+ QString suggests;
+};
+
+inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
+{
+ return qHash(key.uid, seed);
+}
+
+using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
void parseVersion(const QJsonObject &obj, Version *ptr);
void parseVersionList(const QJsonObject &obj, VersionList *ptr);
+// FIXME: this has a different shape than the others...FIX IT!?
+void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires");
+void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires");
}
+
+Q_DECLARE_METATYPE(std::set<Meta::Require>); \ No newline at end of file
diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp
index bf739157..d82878a1 100644
--- a/api/logic/meta/Version.cpp
+++ b/api/logic/meta/Version.cpp
@@ -74,12 +74,20 @@ void Meta::Version::merge(const std::shared_ptr<BaseEntity> &other)
}
if (m_requires != version->m_requires)
{
- setRequires(version->m_requires);
+ m_requires = version->m_requires;
+ }
+ if (m_conflicts != version->m_conflicts)
+ {
+ m_conflicts = version->m_conflicts;
}
if (m_parentUid != version->m_parentUid)
{
setParentUid(version->m_parentUid);
}
+ if(m_volatile != version->m_volatile)
+ {
+ setVolatile(version->m_volatile);
+ }
if(version->m_data)
{
setData(version->m_data);
@@ -109,12 +117,19 @@ void Meta::Version::setTime(const qint64 time)
emit timeChanged();
}
-void Meta::Version::setRequires(const QHash<QString, QString> &requires)
+void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
{
m_requires = requires;
+ m_conflicts = conflicts;
emit requiresChanged();
}
+void Meta::Version::setVolatile(bool volatile_)
+{
+ m_volatile = volatile_;
+}
+
+
void Meta::Version::setData(const VersionFilePtr &data)
{
m_data = data;
diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h
index 2f92ee9f..61f253c6 100644
--- a/api/logic/meta/Version.h
+++ b/api/logic/meta/Version.h
@@ -28,6 +28,8 @@
#include "multimc_logic_export.h"
+#include "JsonFormat.h"
+
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
@@ -65,7 +67,7 @@ public: /* con/des */
{
return m_time;
}
- const QHash<QString, QString> &requires() const
+ const Meta::RequireSet &requires() const
{
return m_requires;
}
@@ -77,6 +79,10 @@ public: /* con/des */
{
return m_recommended;
}
+ bool isLoaded() const
+ {
+ return m_data != nullptr;
+ }
void merge(const std::shared_ptr<BaseEntity> &other) override;
void parse(const QJsonObject &obj) override;
@@ -87,7 +93,8 @@ public: // for usage by format parsers only
void setParentUid(const QString &parentUid);
void setType(const QString &type);
void setTime(const qint64 time);
- void setRequires(const QHash<QString, QString> &requires);
+ void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
+ void setVolatile(bool volatile_);
void setRecommended(bool recommended);
void setProvidesRecommendations();
void setData(const VersionFilePtr &data);
@@ -106,7 +113,9 @@ private:
QString m_version;
QString m_type;
qint64 m_time = 0;
- QHash<QString, QString> m_requires;
+ Meta::RequireSet m_requires;
+ Meta::RequireSet m_conflicts;
+ bool m_volatile = false;
VersionFilePtr m_data;
};
}
diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp
index 4da0fb76..8910c4d7 100644
--- a/api/logic/meta/VersionList.cpp
+++ b/api/logic/meta/VersionList.cpp
@@ -30,7 +30,7 @@ VersionList::VersionList(const QString &uid, QObject *parent)
shared_qobject_ptr<Task> VersionList::getLoadTask()
{
- load();
+ load(Net::Mode::Online);
return getCurrentTask();
}
@@ -81,10 +81,13 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
return QVariant();
}
auto & reqs = version->requires();
- auto iter = reqs.find(parentUid);
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [&parentUid](const Require & req)
+ {
+ return req.uid == parentUid;
+ });
if (iter != reqs.end())
{
- return iter.value();
+ return (*iter).equalsVersion;
}
}
case TypeRole: return version->type();
@@ -159,6 +162,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions)
setupAddedVersion(i, m_versions.at(i));
}
+ // FIXME: this is dumb, we have 'recommended' as part of the metadata already...
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel();
@@ -169,6 +173,22 @@ void VersionList::parse(const QJsonObject& obj)
parseVersionList(obj, this);
}
+// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
+static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
+{
+ if(!a)
+ return b;
+ if(!b)
+ return a;
+ if(a->type() == b->type())
+ {
+ // newer of same type wins
+ return (a->rawTime() > b->rawTime() ? a : b);
+ }
+ // 'release' type wins
+ return (a->type() == "release" ? a : b);
+}
+
void VersionList::merge(const BaseEntity::Ptr &other)
{
const VersionListPtr list = std::dynamic_pointer_cast<VersionList>(other);
@@ -199,10 +219,7 @@ void VersionList::merge(const BaseEntity::Ptr &other)
// connect it.
setupAddedVersion(m_versions.size(), version);
m_versions.append(version);
- if (!m_recommended || (version->type() == "release" && version->rawTime() > m_recommended->rawTime()))
- {
- m_recommended = version;
- }
+ m_recommended = getBetterVersion(m_recommended, version);
}
endResetModel();
}
diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp
new file mode 100644
index 00000000..db523142
--- /dev/null
+++ b/api/logic/minecraft/Component.cpp
@@ -0,0 +1,408 @@
+#include <meta/VersionList.h>
+#include <meta/Index.h>
+#include <Env.h>
+#include "Component.h"
+
+#include "meta/Version.h"
+#include "VersionFile.h"
+#include "minecraft/ComponentList.h"
+#include <FileSystem.h>
+#include <QSaveFile>
+#include "OneSixVersionFormat.h"
+#include <assert.h>
+
+Component::Component(ComponentList * parent, const QString& uid)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_uid = uid;
+}
+
+Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_metaVersion = version;
+ m_uid = version->uid();
+ m_version = m_cachedVersion = version->version();
+ m_cachedName = version->name();
+ m_loaded = version->isLoaded();
+}
+
+Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_file = file;
+ m_uid = uid;
+ m_cachedVersion = m_file->version;
+ m_cachedName = m_file->name;
+ m_loaded = true;
+}
+
+std::shared_ptr<Meta::Version> Component::getMeta()
+{
+ return m_metaVersion;
+}
+
+void Component::applyTo(LaunchProfile* profile)
+{
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ vfile->applyTo(profile);
+ }
+ else
+ {
+ profile->applyProblemSeverity(getProblemSeverity());
+ }
+}
+
+std::shared_ptr<class VersionFile> Component::getVersionFile() const
+{
+ if(m_metaVersion)
+ {
+ if(!m_metaVersion->isLoaded())
+ {
+ m_metaVersion->load(Net::Mode::Online);
+ }
+ return m_metaVersion->data();
+ }
+ else
+ {
+ return m_file;
+ }
+}
+
+std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
+{
+ // FIXME: what if the metadata index isn't loaded yet?
+ if(ENV.metadataIndex()->hasUid(m_uid))
+ {
+ return ENV.metadataIndex()->get(m_uid);
+ }
+ return nullptr;
+}
+
+int Component::getOrder()
+{
+ if(m_orderOverride)
+ return m_order;
+
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ return vfile->order;
+ }
+ return 0;
+}
+void Component::setOrder(int order)
+{
+ m_orderOverride = true;
+ m_order = order;
+}
+QString Component::getID()
+{
+ return m_uid;
+}
+QString Component::getName()
+{
+ if (!m_cachedName.isEmpty())
+ return m_cachedName;
+ return m_uid;
+}
+QString Component::getVersion()
+{
+ return m_cachedVersion;
+}
+QString Component::getFilename()
+{
+ return m_parent->patchFilePathForUid(m_uid);
+}
+QDateTime Component::getReleaseDateTime()
+{
+ if(m_metaVersion)
+ {
+ return m_metaVersion->time();
+ }
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ return vfile->releaseTime;
+ }
+ // FIXME: fake
+ return QDateTime::currentDateTime();
+}
+
+bool Component::isCustom()
+{
+ return m_file != nullptr;
+};
+
+bool Component::isCustomizable()
+{
+ if(m_metaVersion)
+ {
+ if(getVersionFile())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isRemovable()
+{
+ return !m_important;
+}
+bool Component::isRevertible()
+{
+ if (isCustom())
+ {
+ if(ENV.metadataIndex()->hasUid(m_uid))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isMoveable()
+{
+ // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
+ return true;
+}
+bool Component::isVersionChangeable()
+{
+ auto list = getVersionList();
+ if(list)
+ {
+ if(!list->isLoaded())
+ {
+ list->load(Net::Mode::Online);
+ }
+ return list->count() != 0;
+ }
+ return false;
+}
+
+void Component::setImportant(bool state)
+{
+ if(m_important != state)
+ {
+ m_important = state;
+ emit dataChanged();
+ }
+}
+
+ProblemSeverity Component::getProblemSeverity() const
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ return file->getProblemSeverity();
+ }
+ return ProblemSeverity::Error;
+}
+
+const QList<PatchProblem> Component::getProblems() const
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ return file->getProblems();
+ }
+ return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
+}
+
+void Component::setVersion(const QString& version)
+{
+ if(version == m_version)
+ {
+ return;
+ }
+ m_version = version;
+ if(m_loaded)
+ {
+ // we are loaded and potentially have state to invalidate
+ if(m_file)
+ {
+ // we have a file... explicit version has been changed and there is nothing else to do.
+ }
+ else
+ {
+ // we don't have a file, therefore we are loaded with metadata
+ m_cachedVersion = version;
+ // see if the meta version is loaded
+ auto metaVersion = ENV.metadataIndex()->get(m_uid, version);
+ if(metaVersion->isLoaded())
+ {
+ // if yes, we can continue with that.
+ m_metaVersion = metaVersion;
+ }
+ else
+ {
+ // if not, we need loading
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ updateCachedData();
+ }
+ }
+ else
+ {
+ // not loaded... assume it will be sorted out later by the update task
+ }
+ emit dataChanged();
+}
+
+bool Component::customize()
+{
+ if(isCustom())
+ {
+ return false;
+ }
+
+ auto filename = getFilename();
+ if(!FS::ensureFilePathExists(filename))
+ {
+ return false;
+ }
+ // FIXME: get rid of this try-catch.
+ try
+ {
+ QSaveFile jsonFile(filename);
+ if(!jsonFile.open(QIODevice::WriteOnly))
+ {
+ return false;
+ }
+ auto vfile = getVersionFile();
+ if(!vfile)
+ {
+ return false;
+ }
+ auto document = OneSixVersionFormat::versionFileToJson(vfile);
+ jsonFile.write(document.toJson());
+ if(!jsonFile.commit())
+ {
+ return false;
+ }
+ m_file = vfile;
+ m_metaVersion.reset();
+ emit dataChanged();
+ }
+ catch (Exception &error)
+ {
+ qWarning() << "Version could not be loaded:" << error.cause();
+ }
+ return true;
+}
+
+bool Component::revert()
+{
+ if(!isCustom())
+ {
+ // already not custom
+ return true;
+ }
+ auto filename = getFilename();
+ bool result = true;
+ // just kill the file and reload
+ if(QFile::exists(filename))
+ {
+ result = QFile::remove(filename);
+ }
+ if(result)
+ {
+ // file gone...
+ m_file.reset();
+
+ // check local cache for metadata...
+ auto version = ENV.metadataIndex()->get(m_uid, m_version);
+ if(version->isLoaded())
+ {
+ m_metaVersion = version;
+ }
+ else
+ {
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ emit dataChanged();
+ }
+ return result;
+}
+
+/**
+ * deep inspecting compare for requirement sets
+ * By default, only uids are compared for set operations.
+ * This compares all fields of the Require structs in the sets.
+ */
+static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
+{
+ // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
+ if(a.size() != b.size())
+ {
+ return false;
+ }
+ for(const auto & reqA :a)
+ {
+ const auto &iter2 = b.find(reqA);
+ if(iter2 == b.cend())
+ {
+ return false;
+ }
+ const auto & reqB = *iter2;
+ if(!reqA.deepEquals(reqB))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Component::updateCachedData()
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ bool changed = false;
+ if(m_cachedName != file->name)
+ {
+ m_cachedName = file->name;
+ changed = true;
+ }
+ if(m_cachedVersion != file->version)
+ {
+ m_cachedVersion = file->version;
+ changed = true;
+ }
+ if(m_cachedVolatile != file->m_volatile)
+ {
+ m_cachedVolatile = file->m_volatile;
+ changed = true;
+ }
+ if(!deepCompare(m_cachedRequires, file->requires))
+ {
+ m_cachedRequires = file->requires;
+ changed = true;
+ }
+ if(!deepCompare(m_cachedConflicts, file->conflicts))
+ {
+ m_cachedConflicts = file->conflicts;
+ changed = true;
+ }
+ if(changed)
+ {
+ emit dataChanged();
+ }
+ }
+ else
+ {
+ // in case we removed all the metadata
+ m_cachedRequires.clear();
+ m_cachedConflicts.clear();
+ emit dataChanged();
+ }
+}
diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h
new file mode 100644
index 00000000..4917149b
--- /dev/null
+++ b/api/logic/minecraft/Component.h
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <memory>
+#include <QList>
+#include <QJsonDocument>
+#include <QDateTime>
+#include "meta/JsonFormat.h"
+#include "ProblemProvider.h"
+#include "QObjectPtr.h"
+#include "multimc_logic_export.h"
+
+class ComponentList;
+class LaunchProfile;
+namespace Meta
+{
+ class Version;
+ class VersionList;
+}
+class VersionFile;
+
+class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
+{
+Q_OBJECT
+public:
+ Component(ComponentList * parent, const QString &uid);
+
+ // DEPRECATED: remove these constructors?
+ Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
+ Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
+
+ virtual ~Component(){};
+ void applyTo(LaunchProfile *profile);
+
+ bool isMoveable();
+ bool isCustomizable();
+ bool isRevertible();
+ bool isRemovable();
+ bool isCustom();
+ bool isVersionChangeable();
+
+ // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
+ void setOrder(int order);
+ int getOrder();
+
+ QString getID();
+ QString getName();
+ QString getVersion();
+ std::shared_ptr<Meta::Version> getMeta();
+ QDateTime getReleaseDateTime();
+
+ QString getFilename();
+
+ std::shared_ptr<class VersionFile> getVersionFile() const;
+ std::shared_ptr<class Meta::VersionList> getVersionList() const;
+
+ void setImportant (bool state);
+
+ const QList<PatchProblem> getProblems() const override;
+ ProblemSeverity getProblemSeverity() const override;
+
+ void setVersion(const QString & version);
+ bool customize();
+ bool revert();
+
+ void updateCachedData();
+
+signals:
+ void dataChanged();
+
+public: /* data */
+ ComponentList * m_parent;
+
+ // BEGIN: persistent component list properties
+ /// ID of the component
+ QString m_uid;
+ /// version of the component - when there's a custom json override, this is also the version the component reverts to
+ QString m_version;
+ /// if true, this has been added automatically to satisfy dependencies and may be automatically removed
+ bool m_dependencyOnly = false;
+ /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
+ bool m_important = false;
+
+ /// cached name for display purposes, taken from the version file (meta or local override)
+ QString m_cachedName;
+ /// cached version for display AND other purposes, taken from the version file (meta or local override)
+ QString m_cachedVersion;
+ /// cached set of requirements, taken from the version file (meta or local override)
+ Meta::RequireSet m_cachedRequires;
+ Meta::RequireSet m_cachedConflicts;
+ /// if true, the component is volatile and may be automatically removed when no longer needed
+ bool m_cachedVolatile = false;
+ // END: persistent component list properties
+
+ // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
+ bool m_orderOverride = false;
+ int m_order = 0;
+
+ // load state
+ std::shared_ptr<Meta::Version> m_metaVersion;
+ std::shared_ptr<VersionFile> m_file;
+ bool m_loaded = false;
+};
+
+typedef shared_qobject_ptr<Component> ComponentPtr;
diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp
index 34d3bc14..342c10b3 100644
--- a/api/logic/minecraft/ComponentList.cpp
+++ b/api/logic/minecraft/ComponentList.cpp
@@ -21,7 +21,6 @@
#include <QJsonArray>
#include <QDebug>
-#include "minecraft/ComponentList.h"
#include "Exception.h"
#include <minecraft/OneSixVersionFormat.h>
#include <FileSystem.h>
@@ -30,67 +29,666 @@
#include <meta/Index.h>
#include <minecraft/MinecraftInstance.h>
#include <QUuid>
+#include <QTimer>
+#include <Json.h>
+
+#include "ComponentList.h"
+#include "ComponentList_p.h"
+#include "ComponentUpdateTask.h"
ComponentList::ComponentList(MinecraftInstance * instance)
: QAbstractListModel()
{
- m_instance = instance;
+ d.reset(new ComponentListData);
+ d->m_instance = instance;
+ d->m_saveTimer.setSingleShot(true);
+ d->m_saveTimer.setInterval(5000);
+ connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save);
}
ComponentList::~ComponentList()
{
+ if(saveIsScheduled())
+ {
+ d->m_saveTimer.stop();
+ save();
+ }
+}
+
+// BEGIN: component file format
+
+static const int currentComponentsFileVersion = 1;
+
+static QJsonObject componentToJsonV1(ComponentPtr component)
+{
+ QJsonObject obj;
+ // critical
+ obj.insert("uid", component->m_uid);
+ if(!component->m_version.isEmpty())
+ {
+ obj.insert("version", component->m_version);
+ }
+ if(component->m_dependencyOnly)
+ {
+ obj.insert("dependencyOnly", true);
+ }
+ if(component->m_important)
+ {
+ obj.insert("important", true);
+ }
+
+ // cached
+ if(!component->m_cachedVersion.isEmpty())
+ {
+ obj.insert("cachedVersion", component->m_cachedVersion);
+ }
+ if(!component->m_cachedName.isEmpty())
+ {
+ obj.insert("cachedName", component->m_cachedName);
+ }
+ Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires");
+ Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
+ if(component->m_cachedVolatile)
+ {
+ obj.insert("cachedVolatile", true);
+ }
+ return obj;
+}
+
+static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj)
+{
+ // critical
+ auto uid = Json::requireString(obj.value("uid"));
+ auto filePath = componentJsonPattern.arg(uid);
+ auto component = new Component(parent, uid);
+ component->m_version = Json::ensureString(obj.value("version"));
+ component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false);
+ component->m_important = Json::ensureBoolean(obj.value("important"), false);
+
+ // cached
+ // TODO @RESILIENCE: ignore invalid values/structure here?
+ component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion"));
+ component->m_cachedName = Json::ensureString(obj.value("cachedName"));
+ Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires");
+ Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
+ component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false);
+ return component;
+}
+
+// Save the given component container data to a file
+static bool saveComponentList(const QString & filename, const ComponentContainer & container)
+{
+ QJsonObject obj;
+ obj.insert("formatVersion", currentComponentsFileVersion);
+ QJsonArray orderArray;
+ for(auto component: container)
+ {
+ orderArray.append(componentToJsonV1(component));
+ }
+ obj.insert("components", orderArray);
+ QSaveFile outFile(filename);
+ if (!outFile.open(QFile::WriteOnly))
+ {
+ qCritical() << "Couldn't open" << outFile.fileName()
+ << "for writing:" << outFile.errorString();
+ return false;
+ }
+ auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
+ if(outFile.write(data) != data.size())
+ {
+ qCritical() << "Couldn't write all the data into" << outFile.fileName()
+ << "because:" << outFile.errorString();
+ return false;
+ }
+ if(!outFile.commit())
+ {
+ qCritical() << "Couldn't save" << outFile.fileName()
+ << "because:" << outFile.errorString();
+ }
+ return true;
+}
+
+// Read the given file into component containers
+static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
+{
+ QFile componentsFile(filename);
+ if (!componentsFile.exists())
+ {
+ qWarning() << "Components file doesn't exist. This should never happen.";
+ return false;
+ }
+ if (!componentsFile.open(QFile::ReadOnly))
+ {
+ qCritical() << "Couldn't open" << componentsFile.fileName()
+ << " for reading:" << componentsFile.errorString();
+ qWarning() << "Ignoring overriden order";
+ return false;
+ }
+
+ // and it's valid JSON
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
+ qWarning() << "Ignoring overriden order";
+ return false;
+ }
+
+ // and then read it and process it if all above is true.
+ try
+ {
+ auto obj = Json::requireObject(doc);
+ // check order file version.
+ auto version = Json::requireInteger(obj.value("formatVersion"));
+ if (version != currentComponentsFileVersion)
+ {
+ throw JSONValidationError(QObject::tr("Invalid component file version, expected %1")
+ .arg(currentComponentsFileVersion));
+ }
+ auto orderArray = Json::requireArray(obj.value("components"));
+ for(auto item: orderArray)
+ {
+ auto obj = Json::requireObject(item, "Component must be an object.");
+ container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
+ }
+ }
+ catch (JSONValidationError &err)
+ {
+ qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
+ container.clear();
+ return false;
+ }
+ return true;
+}
+
+// END: component file format
+
+// BEGIN: save/load logic
+
+bool ComponentList::saveIsScheduled() const
+{
+ return d->dirty;
+}
+
+void ComponentList::buildingFromScratch()
+{
+ d->loaded = true;
+ d->dirty = true;
+}
+
+void ComponentList::scheduleSave()
+{
+ if(!d->loaded)
+ {
+ qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
+ return;
+ }
+ if(!d->dirty)
+ {
+ d->dirty = true;
+ qDebug() << "Component list save is scheduled for" << d->m_instance->name();
+ }
+ d->m_saveTimer.start();
+}
+
+QString ComponentList::componentsFilePath() const
+{
+ return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
+}
+
+QString ComponentList::patchesPattern() const
+{
+ return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json");
+}
+
+QString ComponentList::patchFilePathForUid(const QString& uid) const
+{
+ return patchesPattern().arg(uid);
+}
+
+void ComponentList::save()
+{
+ qDebug() << "Component list save performed now for" << d->m_instance->name();
+ auto filename = componentsFilePath();
+ saveComponentList(filename, d->components);
+ d->dirty = false;
+}
+
+bool ComponentList::load()
+{
+ auto filename = componentsFilePath();
+ QFile componentsFile(filename);
+
+ // migrate old config to new one, if needed
+ if(!componentsFile.exists())
+ {
+ if(!migratePreComponentConfig())
+ {
+ // FIXME: the user should be notified...
+ qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name();
+ return false;
+ }
+ }
+
+ // load the new component list and swap it with the current one...
+ ComponentContainer newComponents;
+ if(!loadComponentList(this, filename, patchesPattern(), newComponents))
+ {
+ qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
+ return false;
+ }
+ else
+ {
+ // FIXME: actually use fine-grained updates, not this...
+ beginResetModel();
+ // disconnect all the old components
+ for(auto component: d->components)
+ {
+ disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ }
+ d->components.clear();
+ d->componentIndex.clear();
+ for(auto component: newComponents)
+ {
+ if(d->componentIndex.contains(component->m_uid))
+ {
+ qWarning() << "Ignoring duplicate component entry" << component->m_uid;
+ continue;
+ }
+ connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ d->components.append(component);
+ d->componentIndex[component->m_uid] = component;
+ }
+ endResetModel();
+ d->loaded = true;
+ return true;
+ }
+}
+
+void ComponentList::reload(Net::Mode netmode)
+{
+ // Do not reload when the update/resolve task is running. It is in control.
+ if(d->m_updateTask)
+ {
+ return;
+ }
+
+ // flush any scheduled saves to not lose state
+ if(saveIsScheduled())
+ {
+ save();
+ }
+
+ // FIXME: differentiate when a reapply is required by propagating state from components
+ invalidateLaunchProfile();
+
+ if(load())
+ {
+ resolve(netmode);
+ }
+}
+
+shared_qobject_ptr<Task> ComponentList::getCurrentTask()
+{
+ return d->m_updateTask;
}
-void ComponentList::reload()
+void ComponentList::resolve(Net::Mode netmode)
+{
+ auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this);
+ d->m_updateTask.reset(updateTask);
+ connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded);
+ connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed);
+ d->m_updateTask->start();
+}
+
+
+void ComponentList::updateSucceeded()
{
- beginResetModel();
- load_internal();
- reapplyPatches();
- endResetModel();
+ qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
+ d->m_updateTask.reset();
+ invalidateLaunchProfile();
}
-void ComponentList::clearPatches()
+void ComponentList::updateFailed(const QString& error)
{
- beginResetModel();
- m_patches.clear();
- endResetModel();
+ qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error;
+ d->m_updateTask.reset();
+ invalidateLaunchProfile();
}
-void ComponentList::appendPatch(ProfilePatchPtr patch)
+// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig).
+static void upgradeDeprecatedFiles(QString root, QString instanceName)
{
- int index = m_patches.size();
+ auto versionJsonPath = FS::PathCombine(root, "version.json");
+ auto customJsonPath = FS::PathCombine(root, "custom.json");
+ auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json");
+
+ QString sourceFile;
+ QString renameFile;
+
+ // convert old crap.
+ if(QFile::exists(customJsonPath))
+ {
+ sourceFile = customJsonPath;
+ renameFile = versionJsonPath;
+ }
+ else if(QFile::exists(versionJsonPath))
+ {
+ sourceFile = versionJsonPath;
+ }
+ if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
+ {
+ if(!FS::ensureFilePathExists(mcJson))
+ {
+ qWarning() << "Couldn't create patches folder for" << instanceName;
+ return;
+ }
+ if(!renameFile.isEmpty() && QFile::exists(renameFile))
+ {
+ if(!QFile::rename(renameFile, renameFile + ".old"))
+ {
+ qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName;
+ return;
+ }
+ }
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
+ ProfileUtils::removeLwjglFromPatch(file);
+ file->uid = "net.minecraft";
+ file->version = file->minecraftVersion;
+ file->name = "Minecraft";
+
+ Meta::Require needsLwjgl;
+ needsLwjgl.uid = "org.lwjgl";
+ file->requires.insert(needsLwjgl);
+
+ if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson))
+ {
+ return;
+ }
+ if(!QFile::rename(sourceFile, sourceFile + ".old"))
+ {
+ qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName;
+ return;
+ }
+ }
+}
+
+/*
+ * Migrate old layout to the component based one...
+ * - Part of the version information is taken from `instance.cfg` (fed to this class from outside).
+ * - Part is taken from the old order.json file.
+ * - Part is loaded from loose json files in the instance's `patches` directory.
+ */
+bool ComponentList::migratePreComponentConfig()
+{
+ // upgrade the very old files from the beginnings of MultiMC 5
+ upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
+
+ QList<ComponentPtr> components;
+ QSet<QString> loaded;
+
+ auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict)
+ {
+ auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json");
+ auto intendedVersion = d->getOldConfigVersion(uid);
+ // load up the base minecraft patch
+ ComponentPtr component;
+ if(QFile::exists(jsonFilePath))
+ {
+ if(intendedVersion.isEmpty())
+ {
+ intendedVersion = emptyVersion;
+ }
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
+ bool fileChanged = false;
+ // if uid is missing or incorrect, fix it
+ if(file->uid != uid)
+ {
+ file->uid = uid;
+ fileChanged = true;
+ }
+ // if version is missing, add it from the outside.
+ if(file->version.isEmpty())
+ {
+ file->version = intendedVersion;
+ fileChanged = true;
+ }
+ // if this is a dependency (LWJGL), mark it also as volatile
+ if(asDependency)
+ {
+ file->m_volatile = true;
+ fileChanged = true;
+ }
+ // insert requirements if needed
+ if(!req.uid.isEmpty())
+ {
+ file->requires.insert(req);
+ fileChanged = true;
+ }
+ // insert conflicts if needed
+ if(!conflict.uid.isEmpty())
+ {
+ file->conflicts.insert(conflict);
+ fileChanged = true;
+ }
+ if(fileChanged)
+ {
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath);
+ }
+ component = new Component(this, uid, file);
+ component->m_version = intendedVersion;
+ }
+ else if(!intendedVersion.isEmpty())
+ {
+ auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion);
+ component = new Component(this, metaVersion);
+ }
+ else
+ {
+ return;
+ }
+ component->m_dependencyOnly = asDependency;
+ component->m_important = !asDependency;
+ components.append(component);
+ };
+ // TODO: insert depends and conflicts here if these are customized files...
+ Meta::Require reqLwjgl;
+ reqLwjgl.uid = "org.lwjgl";
+ reqLwjgl.suggests = "2.9.1";
+ Meta::Require conflictLwjgl3;
+ conflictLwjgl3.uid = "org.lwjgl3";
+ Meta::Require nullReq;
+ addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3);
+ addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq);
+
+ // first, collect all other file-based patches and load them
+ QMap<QString, ComponentPtr> loadedComponents;
+ QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches"));
+ for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
+ {
+ // parse the file
+ qDebug() << "Reading" << info.fileName();
+ auto file = ProfileUtils::parseJsonFile(info, true);
+
+ // correct missing or wrong uid based on the file name
+ QString uid = info.completeBaseName();
+
+ // ignore builtins, they've been handled already
+ if (uid == "net.minecraft")
+ continue;
+ if (uid == "org.lwjgl")
+ continue;
+
+ // handle horrible corner cases
+ if(uid.isEmpty())
+ {
+ // if you have a file named '.json', make it just go away.
+ // FIXME: @QUALITY do not ignore return value
+ QFile::remove(info.absoluteFilePath());
+ continue;
+ }
+ bool fileChanged = false;
+ if(file->uid != uid)
+ {
+ file->uid = uid;
+ fileChanged = true;
+ }
+ if(fileChanged)
+ {
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath());
+ }
+
+ auto component = new Component(this, file->uid, file);
+ auto version = d->getOldConfigVersion(file->uid);
+ if(!version.isEmpty())
+ {
+ component->m_version = version;
+ }
+ loadedComponents[file->uid] = component;
+ }
+ // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files
+ auto loadSpecial = [&](const QString & uid, int order)
+ {
+ auto patchVersion = d->getOldConfigVersion(uid);
+ if(!patchVersion.isEmpty() && !loadedComponents.contains(uid))
+ {
+ auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion));
+ patch->setOrder(order);
+ loadedComponents[uid] = patch;
+ }
+ };
+ loadSpecial("net.minecraftforge", 5);
+ loadSpecial("com.mumfrey.liteloader", 10);
+
+ // load the old order.json file, if present
+ ProfileUtils::PatchOrder userOrder;
+ ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder);
+
+ // now add all the patches by user sort order
+ for (auto uid : userOrder)
+ {
+ // ignore builtins
+ if (uid == "net.minecraft")
+ continue;
+ if (uid == "org.lwjgl")
+ continue;
+ // ordering has a patch that is gone?
+ if(!loadedComponents.contains(uid))
+ {
+ continue;
+ }
+ components.append(loadedComponents.take(uid));
+ }
+
+ // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json
+ if(!loadedComponents.isEmpty())
+ {
+ // inserting into multimap by order number as key sorts the patches and detects duplicates
+ QMultiMap<int, ComponentPtr> files;
+ auto iter = loadedComponents.begin();
+ while(iter != loadedComponents.end())
+ {
+ files.insert((*iter)->getOrder(), *iter);
+ iter++;
+ }
+
+ // then just extract the patches and put them in the list
+ for (auto order : files.keys())
+ {
+ const auto &values = files.values(order);
+ for(auto &value: values)
+ {
+ // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
+ components.append(value);
+ }
+ }
+ }
+ // new we have a complete list of components...
+ return saveComponentList(componentsFilePath(), components);
+}
+
+// END: save/load
+
+void ComponentList::appendComponent(ComponentPtr component)
+{
+ insertComponent(d->components.size(), component);
+}
+
+void ComponentList::insertComponent(size_t index, ComponentPtr component)
+{
+ auto id = component->getID();
+ if(id.isEmpty())
+ {
+ qWarning() << "Attempt to add a component with empty ID!";
+ return;
+ }
+ if(d->componentIndex.contains(id))
+ {
+ qWarning() << "Attempt to add a component that is already present!";
+ return;
+ }
beginInsertRows(QModelIndex(), index, index);
- m_patches.append(patch);
+ d->components.insert(index, component);
+ d->componentIndex[id] = component;
endInsertRows();
+ scheduleSave();
+}
+
+void ComponentList::componentDataChanged()
+{
+ auto objPtr = qobject_cast<Component *>(sender());
+ if(!objPtr)
+ {
+ qWarning() << "ComponentList got dataChenged signal from a non-Component!";
+ return;
+ }
+ // figure out which one is it... in a seriously dumb way.
+ int index = 0;
+ for (auto component: d->components)
+ {
+ if(component.get() == objPtr)
+ {
+ emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
+ scheduleSave();
+ return;
+ }
+ index++;
+ }
+ qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!";
}
bool ComponentList::remove(const int index)
{
- auto patch = versionPatch(index);
+ auto patch = getComponent(index);
if (!patch->isRemovable())
{
- qDebug() << "Patch" << patch->getID() << "is non-removable";
+ qWarning() << "Patch" << patch->getID() << "is non-removable";
return false;
}
- if(!removePatch_internal(patch))
+ if(!removeComponent_internal(patch))
{
qCritical() << "Patch" << patch->getID() << "could not be removed";
return false;
}
beginRemoveRows(QModelIndex(), index, index);
- m_patches.removeAt(index);
+ d->components.removeAt(index);
+ d->componentIndex.remove(patch->getID());
endRemoveRows();
- reapplyPatches();
- saveCurrentOrder();
+ invalidateLaunchProfile();
+ scheduleSave();
return true;
}
bool ComponentList::remove(const QString id)
{
int i = 0;
- for (auto patch : m_patches)
+ for (auto patch : d->components)
{
if (patch->getID() == id)
{
@@ -103,66 +701,62 @@ bool ComponentList::remove(const QString id)
bool ComponentList::customize(int index)
{
- auto patch = versionPatch(index);
+ auto patch = getComponent(index);
if (!patch->isCustomizable())
{
qDebug() << "Patch" << patch->getID() << "is not customizable";
return false;
}
- if(!customizePatch_internal(patch))
+ if(!patch->customize())
{
qCritical() << "Patch" << patch->getID() << "could not be customized";
return false;
}
- reapplyPatches();
- saveCurrentOrder();
- // FIXME: maybe later in unstable
- // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
+ invalidateLaunchProfile();
+ scheduleSave();
return true;
}
bool ComponentList::revertToBase(int index)
{
- auto patch = versionPatch(index);
+ auto patch = getComponent(index);
if (!patch->isRevertible())
{
qDebug() << "Patch" << patch->getID() << "is not revertible";
return false;
}
- if(!revertPatch_internal(patch))
+ if(!patch->revert())
{
qCritical() << "Patch" << patch->getID() << "could not be reverted";
return false;
}
- reapplyPatches();
- saveCurrentOrder();
- // FIXME: maybe later in unstable
- // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
+ invalidateLaunchProfile();
+ scheduleSave();
return true;
}
-ProfilePatchPtr ComponentList::versionPatch(const QString &id)
+ComponentPtr ComponentList::getComponent(const QString &id)
{
- for (auto patch : m_patches)
+ auto iter = d->componentIndex.find(id);
+ if (iter == d->componentIndex.end())
{
- if (patch->getID() == id)
- {
- return patch;
- }
+ return nullptr;
}
- return nullptr;
+ return *iter;
}
-ProfilePatchPtr ComponentList::versionPatch(int index)
+ComponentPtr ComponentList::getComponent(int index)
{
- if(index < 0 || index >= m_patches.size())
+ if(index < 0 || index >= d->components.size())
+ {
return nullptr;
- return m_patches[index];
+ }
+ return d->components[index];
}
bool ComponentList::isVanilla()
{
- for(auto patchptr: m_patches)
+ for(auto patchptr: d->components)
{
if(patchptr->isCustom())
return false;
@@ -173,7 +767,7 @@ bool ComponentList::isVanilla()
bool ComponentList::revertToVanilla()
{
// remove patches, if present
- auto VersionPatchesCopy = m_patches;
+ auto VersionPatchesCopy = d->components;
for(auto & it: VersionPatchesCopy)
{
if (!it->isCustom())
@@ -185,14 +779,14 @@ bool ComponentList::revertToVanilla()
if(!remove(it->getID()))
{
qWarning() << "Couldn't remove" << it->getID() << "from profile!";
- reapplyPatches();
- saveCurrentOrder();
+ invalidateLaunchProfile();
+ scheduleSave();
return false;
}
}
}
- reapplyPatches();
- saveCurrentOrder();
+ invalidateLaunchProfile();
+ scheduleSave();
return true;
}
@@ -204,17 +798,17 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
int row = index.row();
int column = index.column();
- if (row < 0 || row >= m_patches.size())
+ if (row < 0 || row >= d->components.size())
return QVariant();
- auto patch = m_patches.at(row);
+ auto patch = d->components.at(row);
if (role == Qt::DisplayRole)
{
switch (column)
{
case 0:
- return m_patches.at(row)->getName();
+ return d->components.at(row)->getName();
case 1:
{
if(patch->isCustom())
@@ -283,7 +877,7 @@ Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const
int ComponentList::rowCount(const QModelIndex &parent) const
{
- return m_patches.size();
+ return d->components.size();
}
int ComponentList::columnCount(const QModelIndex &parent) const
@@ -291,18 +885,6 @@ int ComponentList::columnCount(const QModelIndex &parent) const
return 2;
}
-void ComponentList::saveCurrentOrder() const
-{
- ProfileUtils::PatchOrder order;
- for(auto item: m_patches)
- {
- if(!item->isMoveable())
- continue;
- order.append(item->getID());
- }
- saveOrder_internal(order);
-}
-
void ComponentList::move(const int index, const MoveDirection direction)
{
int theirIndex;
@@ -315,7 +897,7 @@ void ComponentList::move(const int index, const MoveDirection direction)
theirIndex = index + 1;
}
- if (index < 0 || index >= m_patches.size())
+ if (index < 0 || index >= d->components.size())
return;
if (theirIndex >= rowCount())
theirIndex = rowCount() - 1;
@@ -325,43 +907,23 @@ void ComponentList::move(const int index, const MoveDirection direction)
return;
int togap = theirIndex > index ? theirIndex + 1 : theirIndex;
- auto from = versionPatch(index);
- auto to = versionPatch(theirIndex);
+ auto from = getComponent(index);
+ auto to = getComponent(theirIndex);
if (!from || !to || !to->isMoveable() || !from->isMoveable())
{
return;
}
beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
- m_patches.swap(index, theirIndex);
+ d->components.swap(index, theirIndex);
endMoveRows();
- reapplyPatches();
- saveCurrentOrder();
-}
-void ComponentList::resetOrder()
-{
- resetOrder_internal();
- reload();
+ invalidateLaunchProfile();
+ scheduleSave();
}
-bool ComponentList::reapplyPatches()
+void ComponentList::invalidateLaunchProfile()
{
- try
- {
- m_profile.reset(new LaunchProfile);
- for(auto file: m_patches)
- {
- qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
- file->applyTo(m_profile.get());
- }
- }
- catch (Exception & error)
- {
- m_profile.reset();
- qWarning() << "Couldn't apply profile patches because: " << error.cause();
- return false;
- }
- return true;
+ d->m_profile.reset();
}
void ComponentList::installJarMods(QStringList selectedFiles)
@@ -374,224 +936,7 @@ void ComponentList::installCustomJar(QString selectedFile)
installCustomJar_internal(selectedFile);
}
-
-/*
- * TODO: get rid of this. Get rid of all order numbers.
- */
-int ComponentList::getFreeOrderNumber()
-{
- int largest = 100;
- // yes, I do realize this is dumb. The order thing itself is dumb. and to be removed next.
- for(auto thing: m_patches)
- {
- int order = thing->getOrder();
- if(order > largest)
- largest = order;
- }
- return largest + 1;
-}
-
-void ComponentList::upgradeDeprecatedFiles_internal()
-{
- auto versionJsonPath = FS::PathCombine(m_instance->instanceRoot(), "version.json");
- auto customJsonPath = FS::PathCombine(m_instance->instanceRoot(), "custom.json");
- auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json");
-
- QString sourceFile;
- QString renameFile;
-
- // convert old crap.
- if(QFile::exists(customJsonPath))
- {
- sourceFile = customJsonPath;
- renameFile = versionJsonPath;
- }
- else if(QFile::exists(versionJsonPath))
- {
- sourceFile = versionJsonPath;
- }
- if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
- {
- if(!FS::ensureFilePathExists(mcJson))
- {
- qWarning() << "Couldn't create patches folder for" << m_instance->name();
- return;
- }
- if(!renameFile.isEmpty() && QFile::exists(renameFile))
- {
- if(!QFile::rename(renameFile, renameFile + ".old"))
- {
- qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << m_instance->name();
- return;
- }
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
- ProfileUtils::removeLwjglFromPatch(file);
- file->uid = "net.minecraft";
- file->version = file->minecraftVersion;
- file->name = "Minecraft";
- auto data = OneSixVersionFormat::versionFileToJson(file, false).toJson();
- QSaveFile newPatchFile(mcJson);
- if(!newPatchFile.open(QIODevice::WriteOnly))
- {
- newPatchFile.cancelWriting();
- qWarning() << "Couldn't open main patch for writing in" << m_instance->name();
- return;
- }
- newPatchFile.write(data);
- if(!newPatchFile.commit())
- {
- qWarning() << "Couldn't save main patch in" << m_instance->name();
- return;
- }
- if(!QFile::rename(sourceFile, sourceFile + ".old"))
- {
- qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << m_instance->name();
- return;
- }
- }
-}
-
-void ComponentList::loadDefaultBuiltinPatches_internal()
-{
- auto addBuiltinPatch = [&](const QString &uid, const QString intendedVersion, int order)
- {
- auto jsonFilePath = FS::PathCombine(m_instance->instanceRoot(), "patches" , uid + ".json");
- // load up the base minecraft patch
- ProfilePatchPtr profilePatch;
- if(QFile::exists(jsonFilePath))
- {
- auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
- if(file->version.isEmpty())
- {
- file->version = intendedVersion;
- }
- profilePatch = std::make_shared<ProfilePatch>(file, jsonFilePath);
- profilePatch->setVanilla(false);
- profilePatch->setRevertible(true);
- }
- else
- {
- auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion);
- profilePatch = std::make_shared<ProfilePatch>(metaVersion);
- profilePatch->setVanilla(true);
- }
- profilePatch->setOrder(order);
- appendPatch(profilePatch);
- };
- addBuiltinPatch("net.minecraft", m_instance->getComponentVersion("net.minecraft"), -2);
- addBuiltinPatch("org.lwjgl", m_instance->getComponentVersion("org.lwjgl"), -1);
-}
-
-void ComponentList::loadUserPatches_internal()
-{
- // first, collect all patches (that are not builtins of OneSix) and load them
- QMap<QString, ProfilePatchPtr> loadedPatches;
- QDir patchesDir(FS::PathCombine(m_instance->instanceRoot(),"patches"));
- for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
- {
- // parse the file
- qDebug() << "Reading" << info.fileName();
- auto file = ProfileUtils::parseJsonFile(info, true);
- // ignore builtins
- if (file->uid == "net.minecraft")
- continue;
- if (file->uid == "org.lwjgl")
- continue;
- auto patch = std::make_shared<ProfilePatch>(file, info.filePath());
- patch->setRemovable(true);
- patch->setMovable(true);
- if(ENV.metadataIndex()->hasUid(file->uid))
- {
- // FIXME: requesting a uid/list creates it in the index... this allows reverting to possibly invalid versions...
- patch->setRevertible(true);
- }
- loadedPatches[file->uid] = patch;
- }
- // these are 'special'... if not already loaded from instance files, grab them from the metadata repo.
- auto loadSpecial = [&](const QString & uid, int order)
- {
- auto patchVersion = m_instance->getComponentVersion(uid);
- if(!patchVersion.isEmpty() && !loadedPatches.contains(uid))
- {
- auto patch = std::make_shared<ProfilePatch>(ENV.metadataIndex()->get(uid, patchVersion));
- patch->setOrder(order);
- patch->setVanilla(true);
- patch->setRemovable(true);
- patch->setMovable(true);
- loadedPatches[uid] = patch;
- }
- };
- loadSpecial("net.minecraftforge", 5);
- loadSpecial("com.mumfrey.liteloader", 10);
-
- // now add all the patches by user sort order
- ProfileUtils::PatchOrder userOrder;
- ProfileUtils::readOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), userOrder);
- for (auto uid : userOrder)
- {
- // ignore builtins
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
- // ordering has a patch that is gone?
- if(!loadedPatches.contains(uid))
- {
- continue;
- }
- appendPatch(loadedPatches.take(uid));
- }
-
- // is there anything left to sort?
- if(loadedPatches.isEmpty())
- {
- // TODO: save the order here?
- return;
- }
-
- // inserting into multimap by order number as key sorts the patches and detects duplicates
- QMultiMap<int, ProfilePatchPtr> files;
- auto iter = loadedPatches.begin();
- while(iter != loadedPatches.end())
- {
- files.insert((*iter)->getOrder(), *iter);
- iter++;
- }
-
- // then just extract the patches and put them in the list
- for (auto order : files.keys())
- {
- const auto &values = files.values(order);
- for(auto &value: values)
- {
- // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
- appendPatch(value);
- }
- }
- // TODO: save the order here?
-}
-
-
-void ComponentList::load_internal()
-{
- clearPatches();
- upgradeDeprecatedFiles_internal();
- loadDefaultBuiltinPatches_internal();
- loadUserPatches_internal();
-}
-
-bool ComponentList::saveOrder_internal(ProfileUtils::PatchOrder order) const
-{
- return ProfileUtils::writeOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), order);
-}
-
-bool ComponentList::resetOrder_internal()
-{
- return QDir(m_instance->instanceRoot()).remove("order.json");
-}
-
-bool ComponentList::removePatch_internal(ProfilePatchPtr patch)
+bool ComponentList::removeComponent_internal(ComponentPtr patch)
{
bool ok = true;
// first, remove the patch file. this ensures it's not used anymore
@@ -605,10 +950,6 @@ bool ComponentList::removePatch_internal(ProfilePatchPtr patch)
return false;
}
}
- if(!m_instance->getComponentVersion(patch->getID()).isEmpty())
- {
- m_instance->setComponentVersion(patch->getID(), QString());
- }
// FIXME: we need a generic way of removing local resources, not just jar mods...
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool
@@ -618,7 +959,7 @@ bool ComponentList::removePatch_internal(ProfilePatchPtr patch)
return true;
}
QStringList jar, temp1, temp2, temp3;
- jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, m_instance->jarmodsPath().absolutePath());
+ jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
QFileInfo finfo (jar[0]);
if(finfo.exists())
{
@@ -633,90 +974,27 @@ bool ComponentList::removePatch_internal(ProfilePatchPtr patch)
return true;
};
- auto &jarMods = patch->getVersionFile()->jarMods;
- for(auto &jarmod: jarMods)
- {
- ok &= preRemoveJarMod(jarmod);
- }
- return ok;
-}
-
-bool ComponentList::customizePatch_internal(ProfilePatchPtr patch)
-{
- if(patch->isCustom())
- {
- return false;
- }
-
- auto filename = FS::PathCombine(m_instance->instanceRoot(), "patches" , patch->getID() + ".json");
- if(!FS::ensureFilePathExists(filename))
- {
- return false;
- }
- // FIXME: get rid of this try-catch.
- try
+ auto vFile = patch->getVersionFile();
+ if(vFile)
{
- QSaveFile jsonFile(filename);
- if(!jsonFile.open(QIODevice::WriteOnly))
- {
- return false;
- }
- auto vfile = patch->getVersionFile();
- if(!vfile)
+ auto &jarMods = vFile->jarMods;
+ for(auto &jarmod: jarMods)
{
- return false;
- }
- auto document = OneSixVersionFormat::versionFileToJson(vfile, true);
- jsonFile.write(document.toJson());
- if(!jsonFile.commit())
- {
- return false;
+ ok &= preRemoveJarMod(jarmod);
}
- load_internal();
- }
- catch (Exception &error)
- {
- qWarning() << "Version could not be loaded:" << error.cause();
- }
- return true;
-}
-
-bool ComponentList::revertPatch_internal(ProfilePatchPtr patch)
-{
- if(!patch->isCustom())
- {
- // already not custom
- return true;
- }
- auto filename = patch->getFilename();
- if(!QFile::exists(filename))
- {
- // already gone / not custom
- return true;
}
- // just kill the file and reload
- bool result = QFile::remove(filename);
- // FIXME: get rid of this try-catch.
- try
- {
- load_internal();
- }
- catch (Exception &error)
- {
- qWarning() << "Version could not be loaded:" << error.cause();
- }
- return result;
+ return ok;
}
bool ComponentList::installJarMods_internal(QStringList filepaths)
{
- QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches");
+ QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if(!FS::ensureFolderPathExists(patchDir))
{
return false;
}
- if (!FS::ensureFolderPathExists(m_instance->jarModsDir()))
+ if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir()))
{
return false;
}
@@ -729,7 +1007,7 @@ bool ComponentList::installJarMods_internal(QStringList filepaths)
QString target_filename = id + ".jar";
QString target_id = "org.multimc.jarmod." + id;
QString target_name = sourceInfo.completeBaseName() + " (jar mod)";
- QString finalPath = FS::PathCombine(m_instance->jarModsDir(), target_filename);
+ QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
QFileInfo targetInfo(finalPath);
if(targetInfo.exists())
@@ -751,7 +1029,6 @@ bool ComponentList::installJarMods_internal(QStringList filepaths)
f->jarMods.append(jarMod);
f->name = target_name;
f->uid = target_id;
- f->order = getFreeOrderNumber();
QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
QFile file(patchFileName);
@@ -761,28 +1038,25 @@ bool ComponentList::installJarMods_internal(QStringList filepaths)
<< "for reading:" << file.errorString();
return false;
}
- file.write(OneSixVersionFormat::versionFileToJson(f, true).toJson());
+ file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close();
- auto patch = std::make_shared<ProfilePatch>(f, patchFileName);
- patch->setMovable(true);
- patch->setRemovable(true);
- appendPatch(patch);
+ appendComponent(new Component(this, f->uid, f));
}
- saveCurrentOrder();
- reapplyPatches();
+ scheduleSave();
+ invalidateLaunchProfile();
return true;
}
bool ComponentList::installCustomJar_internal(QString filepath)
{
- QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches");
+ QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if(!FS::ensureFolderPathExists(patchDir))
{
return false;
}
- QString libDir = m_instance->getLocalLibraryPath();
+ QString libDir = d->m_instance->getLocalLibraryPath();
if (!FS::ensureFolderPathExists(libDir))
{
return false;
@@ -816,7 +1090,6 @@ bool ComponentList::installCustomJar_internal(QString filepath)
f->mainJar = jarMod;
f->name = target_name;
f->uid = target_id;
- f->order = getFreeOrderNumber();
QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
QFile file(patchFileName);
@@ -826,25 +1099,74 @@ bool ComponentList::installCustomJar_internal(QString filepath)
<< "for reading:" << file.errorString();
return false;
}
- file.write(OneSixVersionFormat::versionFileToJson(f, true).toJson());
+ file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close();
- auto patch = std::make_shared<ProfilePatch>(f, patchFileName);
- patch->setMovable(true);
- patch->setRemovable(true);
- appendPatch(patch);
+ appendComponent(new Component(this, f->uid, f));
- saveCurrentOrder();
- reapplyPatches();
+ scheduleSave();
+ invalidateLaunchProfile();
return true;
}
std::shared_ptr<LaunchProfile> ComponentList::getProfile() const
{
- return m_profile;
+ if(!d->m_profile)
+ {
+ try
+ {
+ auto profile = std::make_shared<LaunchProfile>();
+ for(auto file: d->components)
+ {
+ qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
+ file->applyTo(profile.get());
+ }
+ d->m_profile = profile;
+ }
+ catch (Exception & error)
+ {
+ qWarning() << "Couldn't apply profile patches because: " << error.cause();
+ }
+ }
+ return d->m_profile;
+}
+
+void ComponentList::setOldConfigVersion(const QString& uid, const QString& version)
+{
+ if(version.isEmpty())
+ {
+ return;
+ }
+ d->m_oldConfigVersions[uid] = version;
+}
+
+bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important)
+{
+ auto iter = d->componentIndex.find(uid);
+ if(iter != d->componentIndex.end())
+ {
+ // set existing
+ (*iter)->setVersion(version);
+ (*iter)->setImportant(important);
+ return true;
+ }
+ else
+ {
+ // add new
+ auto component = new Component(this, uid);
+ component->m_version = version;
+ component->m_important = important;
+ appendComponent(component);
+ return true;
+ }
}
-void ComponentList::clearProfile()
+QString ComponentList::getComponentVersion(const QString& uid) const
{
- m_profile.reset();
+ const auto iter = d->componentIndex.find(uid);
+ if (iter != d->componentIndex.end())
+ {
+ return (*iter)->getVersion();
+ }
+ return QString();
}
diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h
index 0811a829..7971650d 100644
--- a/api/logic/minecraft/ComponentList.h
+++ b/api/logic/minecraft/ComponentList.h
@@ -23,19 +23,21 @@
#include "Library.h"
#include "LaunchProfile.h"
-#include "ProfilePatch.h"
+#include "Component.h"
#include "ProfileUtils.h"
#include "BaseVersion.h"
#include "MojangDownloadInfo.h"
#include "multimc_logic_export.h"
+#include "net/Mode.h"
class MinecraftInstance;
-
+struct ComponentListData;
+class ComponentUpdateTask;
class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
{
Q_OBJECT
-
+ friend ComponentUpdateTask;
public:
explicit ComponentList(MinecraftInstance * instance);
virtual ~ComponentList();
@@ -46,6 +48,9 @@ public:
virtual int columnCount(const QModelIndex &parent) const override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+ /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
+ void buildingFromScratch();
+
/// is this version unchanged by the user?
bool isVanilla();
@@ -58,68 +63,76 @@ public:
/// install a jar/zip as a replacement for the main jar
void installCustomJar(QString selectedFile);
- /// DEPRECATED, remove ASAP
- int getFreeOrderNumber();
-
enum MoveDirection { MoveUp, MoveDown };
- /// move patch file # up or down the list
+ /// move component file # up or down the list
void move(const int index, const MoveDirection direction);
- /// remove patch file # - including files/records
+ /// remove component file # - including files/records
bool remove(const int index);
- /// remove patch file by id - including files/records
+ /// remove component file by id - including files/records
bool remove(const QString id);
bool customize(int index);
bool revertToBase(int index);
- void resetOrder();
+ /// reload the list, reload all components, resolve dependencies
+ void reload(Net::Mode netmode);
- /// reload all profile patches from storage, clear the profile and apply the patches
- void reload();
+ // reload all components, resolve dependencies
+ void resolve(Net::Mode netmode);
- /// apply the patches. Catches all the errors and returns true/false for success/failure
- bool reapplyPatches();
+ /// get current running task...
+ shared_qobject_ptr<Task> getCurrentTask();
std::shared_ptr<LaunchProfile> getProfile() const;
- void clearProfile();
+
+ // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
+ void setOldConfigVersion(const QString &uid, const QString &version);
+
+ QString getComponentVersion(const QString &uid) const;
+
+ bool setComponentVersion(const QString &uid, const QString &version, bool important = false);
+
+ QString patchFilePathForUid(const QString &uid) const;
public:
- /// get the profile patch by id
- ProfilePatchPtr versionPatch(const QString &id);
+ /// get the profile component by id
+ ComponentPtr getComponent(const QString &id);
+
+ /// get the profile component by index
+ ComponentPtr getComponent(int index);
+
+private:
+ void scheduleSave();
+ bool saveIsScheduled() const;
- /// get the profile patch by index
- ProfilePatchPtr versionPatch(int index);
+ /// apply the component patches. Catches all the errors and returns true/false for success/failure
+ void invalidateLaunchProfile();
- /// save the current patch order
- void saveCurrentOrder() const;
+ /// Add the component to the internal list of patches
+ void appendComponent(ComponentPtr component);
+ /// insert component so that its index is ideally the specified one (returns real index)
+ void insertComponent(size_t index, ComponentPtr component);
- /// Remove all the patches
- void clearPatches();
+ QString componentsFilePath() const;
+ QString patchesPattern() const;
- /// Add the patch object to the internal list of patches
- void appendPatch(ProfilePatchPtr patch);
+private slots:
+ void save();
+ void updateSucceeded();
+ void updateFailed(const QString & error);
+ void componentDataChanged();
private:
- void load_internal();
- bool resetOrder_internal();
- bool saveOrder_internal(ProfileUtils::PatchOrder order) const;
+ bool load();
bool installJarMods_internal(QStringList filepaths);
bool installCustomJar_internal(QString filepath);
- bool removePatch_internal(ProfilePatchPtr patch);
- bool customizePatch_internal(ProfilePatchPtr patch);
- bool revertPatch_internal(ProfilePatchPtr patch);
- void loadDefaultBuiltinPatches_internal();
- void loadUserPatches_internal();
- void upgradeDeprecatedFiles_internal();
+ bool removeComponent_internal(ComponentPtr patch);
-private: /* data */
- /// list of attached profile patches
- QList<ProfilePatchPtr> m_patches;
+ bool migratePreComponentConfig();
- // the instance this belongs to
- MinecraftInstance *m_instance;
+private: /* data */
- std::shared_ptr<LaunchProfile> m_profile;
+ std::unique_ptr<ComponentListData> d;
};
diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h
new file mode 100644
index 00000000..26ca5049
--- /dev/null
+++ b/api/logic/minecraft/ComponentList_p.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Component.h"
+#include <map>
+#include <QTimer>
+#include <QList>
+#include <QMap>
+
+class MinecraftInstance;
+using ComponentContainer = QList<ComponentPtr>;
+using ComponentIndex = QMap<QString, ComponentPtr>;
+using ConnectionList = QList<QMetaObject::Connection>;
+
+struct ComponentListData
+{
+ // the instance this belongs to
+ MinecraftInstance *m_instance;
+
+ // the launch profile (volatile, temporary thing created on demand)
+ std::shared_ptr<LaunchProfile> m_profile;
+
+ // version information migrated from instance.cfg file. Single use on migration!
+ std::map<QString, QString> m_oldConfigVersions;
+ QString getOldConfigVersion(const QString& uid) const
+ {
+ const auto iter = m_oldConfigVersions.find(uid);
+ if(iter != m_oldConfigVersions.cend())
+ {
+ return (*iter).second;
+ }
+ return QString();
+ }
+
+ // persistent list of components and related machinery
+ ComponentContainer components;
+ ComponentIndex componentIndex;
+ bool dirty = false;
+ QTimer m_saveTimer;
+ shared_qobject_ptr<Task> m_updateTask;
+ bool loaded = false;
+};
+
diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp
new file mode 100644
index 00000000..e03318c5
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask.cpp
@@ -0,0 +1,688 @@
+#include "ComponentUpdateTask.h"
+
+#include "ComponentList_p.h"
+#include "ComponentList.h"
+#include "Component.h"
+#include <Env.h>
+#include <meta/Index.h>
+#include <meta/VersionList.h>
+#include <meta/Version.h>
+#include "ComponentUpdateTask_p.h"
+#include <cassert>
+#include <Version.h>
+#include "net/Mode.h"
+#include "OneSixVersionFormat.h"
+
+/*
+ * This is responsible for loading the components of a component list AND resolving dependency issues between them
+ */
+
+/*
+ * FIXME: the 'one shot async task' nature of this does not fit the intended usage
+ * Really, it should be a reactor/state machine that receives input from the application
+ * and dynamically adapts to changing requirements...
+ *
+ * The reactor should be the only entry into manipulating the ComponentList.
+ * See: https://en.wikipedia.org/wiki/Reactor_pattern
+ */
+
+/*
+ * Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change?
+ * If the component list changes, start over.
+ */
+
+ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent)
+ : Task(parent)
+{
+ d.reset(new ComponentUpdateTaskData);
+ d->m_list = list;
+ d->mode = mode;
+ d->netmode = netmode;
+}
+
+ComponentUpdateTask::~ComponentUpdateTask()
+{
+}
+
+void ComponentUpdateTask::executeTask()
+{
+ qDebug() << "Loading components";
+ loadComponents();
+}
+
+namespace
+{
+enum class LoadResult
+{
+ LoadedLocal,
+ RequiresRemote,
+ Failed
+};
+
+LoadResult composeLoadResult(LoadResult a, LoadResult b)
+{
+ if (a < b)
+ {
+ return b;
+ }
+ return a;
+}
+
+static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ if(component->m_loaded)
+ {
+ qDebug() << component->getName() << "is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+
+ LoadResult result = LoadResult::Failed;
+ auto customPatchFilename = component->getFilename();
+ if(QFile::exists(customPatchFilename))
+ {
+ // if local file exists...
+
+ // check for uid problems inside...
+ bool fileChanged = false;
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false);
+ if(file->uid != component->m_uid)
+ {
+ file->uid = component->m_uid;
+ fileChanged = true;
+ }
+ if(fileChanged)
+ {
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename);
+ }
+
+ component->m_file = file;
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version);
+ component->m_metaVersion = metaVersion;
+ if(metaVersion->isLoaded())
+ {
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ metaVersion->load(netmode);
+ loadTask = metaVersion->getCurrentTask();
+ if(loadTask)
+ result = LoadResult::RequiresRemote;
+ else if (metaVersion->isLoaded())
+ result = LoadResult::LoadedLocal;
+ else
+ result = LoadResult::Failed;
+ }
+ }
+ return result;
+}
+
+static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ if(component->m_loaded)
+ {
+ qDebug() << component->getName() << "is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+
+ LoadResult result = LoadResult::Failed;
+ auto metaList = ENV.metadataIndex()->get(component->m_uid);
+ if(metaList->isLoaded())
+ {
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ metaList->load(netmode);
+ loadTask = metaList->getCurrentTask();
+ result = LoadResult::RequiresRemote;
+ }
+ return result;
+}
+
+static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ // FIXME: DECIDE. do we want to run the update task anyway?
+ if(ENV.metadataIndex()->isLoaded())
+ {
+ qDebug() << "Index is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+ ENV.metadataIndex()->load(netmode);
+ loadTask = ENV.metadataIndex()->getCurrentTask();
+ if(loadTask)
+ {
+ return LoadResult::RequiresRemote;
+ }
+ // FIXME: this is assuming the load succeeded... did it really?
+ return LoadResult::LoadedLocal;
+}
+}
+
+void ComponentUpdateTask::loadComponents()
+{
+ LoadResult result = LoadResult::LoadedLocal;
+ size_t taskIndex = 0;
+ size_t componentIndex = 0;
+ d->remoteLoadSuccessful = true;
+ // load the main index (it is needed to determine if components can revert)
+ {
+ // FIXME: tear out as a method? or lambda?
+ shared_qobject_ptr<Task> indexLoadTask;
+ auto singleResult = loadIndex(indexLoadTask, d->netmode);
+ result = composeLoadResult(result, singleResult);
+ if(indexLoadTask)
+ {
+ qDebug() << "Remote loading is being run for metadata index";
+ RemoteLoadStatus status;
+ status.type = RemoteLoadStatus::Type::Index;
+ d->remoteLoadStatusList.append(status);
+ connect(indexLoadTask.get(), &Task::succeeded, [=]()
+ {
+ remoteLoadSucceeded(taskIndex);
+ });
+ connect(indexLoadTask.get(), &Task::failed, [=](const QString & error)
+ {
+ remoteLoadFailed(taskIndex, error);
+ });
+ taskIndex++;
+ }
+ }
+ // load all the components OR their lists...
+ for (auto component: d->m_list->d->components)
+ {
+ shared_qobject_ptr<Task> loadTask;
+ LoadResult singleResult;
+ RemoteLoadStatus::Type loadType;
+ // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that...
+#if 0
+ switch(d->mode)
+ {
+ case Mode::Launch:
+ {
+ singleResult = loadComponent(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::Version;
+ break;
+ }
+ case Mode::Resolution:
+ {
+ singleResult = loadComponentList(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::List;
+ break;
+ }
+ }
+#else
+ singleResult = loadComponent(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::Version;
+#endif
+ if(singleResult == LoadResult::LoadedLocal)
+ {
+ component->updateCachedData();
+ }
+ result = composeLoadResult(result, singleResult);
+ if (loadTask)
+ {
+ qDebug() << "Remote loading is being run for" << component->getName();
+ connect(loadTask.get(), &Task::succeeded, [=]()
+ {
+ remoteLoadSucceeded(taskIndex);
+ });
+ connect(loadTask.get(), &Task::failed, [=](const QString & error)
+ {
+ remoteLoadFailed(taskIndex, error);
+ });
+ RemoteLoadStatus status;
+ status.type = loadType;
+ status.componentListIndex = componentIndex;
+ d->remoteLoadStatusList.append(status);
+ taskIndex++;
+ }
+ componentIndex++;
+ }
+ d->remoteTasksInProgress = taskIndex;
+ switch(result)
+ {
+ case LoadResult::LoadedLocal:
+ {
+ // Everything got loaded. Advance to dependency resolution.
+ resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
+ break;
+ }
+ case LoadResult::RequiresRemote:
+ {
+ // we wait for signals.
+ break;
+ }
+ case LoadResult::Failed:
+ {
+ emitFailed(tr("Some component metadata load tasks failed."));
+ break;
+ }
+ }
+}
+
+namespace
+{
+ struct RequireEx : public Meta::Require
+ {
+ size_t indexOfFirstDependee = 0;
+ };
+ struct RequireCompositionResult
+ {
+ bool ok;
+ RequireEx outcome;
+ };
+ using RequireExSet = std::set<RequireEx>;
+}
+
+static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b)
+{
+ assert(a.uid == b.uid);
+ RequireEx out;
+ out.uid = a.uid;
+ out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee);
+ if(a.equalsVersion.isEmpty())
+ {
+ out.equalsVersion = b.equalsVersion;
+ }
+ else if (b.equalsVersion.isEmpty())
+ {
+ out.equalsVersion = a.equalsVersion;
+ }
+ else if (a.equalsVersion == b.equalsVersion)
+ {
+ out.equalsVersion = a.equalsVersion;
+ }
+ else
+ {
+ // FIXME: mark error as explicit version conflict
+ return {false, out};
+ }
+
+ if(a.suggests.isEmpty())
+ {
+ out.suggests = b.suggests;
+ }
+ else if (b.suggests.isEmpty())
+ {
+ out.suggests = a.suggests;
+ }
+ else
+ {
+ Version aVer(a.suggests);
+ Version bVer(b.suggests);
+ out.suggests = (aVer < bVer ? b.suggests : a.suggests);
+ }
+ return {true, out};
+}
+
+// gather the requirements from all components, finding any obvious conflicts
+static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output)
+{
+ bool succeeded = true;
+ size_t componentNum = 0;
+ for(auto component: input)
+ {
+ auto &componentRequires = component->m_cachedRequires;
+ for(const auto & componentRequire: componentRequires)
+ {
+ auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){
+ return req.uid == componentRequire.uid;
+ });
+
+ RequireEx componenRequireEx;
+ componenRequireEx.uid = componentRequire.uid;
+ componenRequireEx.suggests = componentRequire.suggests;
+ componenRequireEx.equalsVersion = componentRequire.equalsVersion;
+ componenRequireEx.indexOfFirstDependee = componentNum;
+
+ if(found != output.cend())
+ {
+ // found... process it further
+ auto result = composeRequirement(componenRequireEx, *found);
+ if(result.ok)
+ {
+ output.erase(componenRequireEx);
+ output.insert(result.outcome);
+ }
+ else
+ {
+ qCritical()
+ << "Conflicting requirements:"
+ << componentRequire.uid
+ << "versions:"
+ << componentRequire.equalsVersion
+ << ";"
+ << (*found).equalsVersion;
+ }
+ succeeded &= result.ok;
+ }
+ else
+ {
+ // not found, accumulate
+ output.insert(componenRequireEx);
+ }
+ }
+ componentNum++;
+ }
+ return succeeded;
+}
+
+/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps)
+static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove)
+{
+ for(const auto & component: components)
+ {
+ if(!component->m_dependencyOnly)
+ continue;
+ if(!component->m_cachedVolatile)
+ continue;
+ RequireEx reqNeedle;
+ reqNeedle.uid = component->m_uid;
+ const auto iter = reqs.find(reqNeedle);
+ if(iter == reqs.cend())
+ {
+ toRemove.append(component->m_uid);
+ }
+ }
+}
+
+/**
+ * handles:
+ * - trivial addition (there is an unmet requirement and it can be trivially met by adding something)
+ * - trivial version conflict of dependencies == explicit version required and installed is different
+ *
+ * toAdd - set of requirements than mean adding a new component
+ * toChange - set of requirements that mean changing version of an existing component
+ */
+static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange)
+{
+ enum class Decision
+ {
+ Undetermined,
+ Met,
+ Missing,
+ VersionNotSame,
+ LockedVersionNotSame
+ } decision = Decision::Undetermined;
+
+ QString reqStr;
+ bool succeeded = true;
+ // list the composed requirements and say if they are met or unmet
+ for(auto & req: input)
+ {
+ do
+ {
+ if(req.equalsVersion.isEmpty())
+ {
+ reqStr = QString("Req: %1").arg(req.uid);
+ if(index.contains(req.uid))
+ {
+ decision = Decision::Met;
+ }
+ else
+ {
+ toAdd.insert(req);
+ decision = Decision::Missing;
+ }
+ break;
+ }
+ else
+ {
+ reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion);
+ const auto & compIter = index.find(req.uid);
+ if(compIter == index.cend())
+ {
+ toAdd.insert(req);
+ decision = Decision::Missing;
+ break;
+ }
+ auto & comp = (*compIter);
+ if(comp->getVersion() != req.equalsVersion)
+ {
+ if(comp->m_dependencyOnly)
+ {
+ decision = Decision::VersionNotSame;
+ }
+ else
+ {
+ decision = Decision::LockedVersionNotSame;
+ }
+ break;
+ }
+ decision = Decision::Met;
+ }
+ } while(false);
+ switch(decision)
+ {
+ case Decision::Undetermined:
+ qCritical() << "No decision for" << reqStr;
+ succeeded = false;
+ break;
+ case Decision::Met:
+ qDebug() << reqStr << "Is met.";
+ break;
+ case Decision::Missing:
+ qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
+ toAdd.insert(req);
+ break;
+ case Decision::VersionNotSame:
+ qDebug() << reqStr << "already has different version that can be changed.";
+ toChange.insert(req);
+ break;
+ case Decision::LockedVersionNotSame:
+ qDebug() << reqStr << "already has different version that cannot be changed.";
+ succeeded = false;
+ break;
+ }
+ }
+ return succeeded;
+}
+
+// FIXME, TODO: decouple dependency resolution from loading
+// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses.
+// FIXME: throw all this away and use a graph
+void ComponentUpdateTask::resolveDependencies(bool checkOnly)
+{
+ qDebug() << "Resolving dependencies";
+ /*
+ * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways:
+ * 1. There are conflicting dependencies on the same uid with different exact version numbers
+ * -> hard error
+ * 2. A dependency has non-matching exact version number
+ * -> hard error
+ * 3. A dependency is entirely missing and needs to be injected before the dependee(s)
+ * -> requirements are injected
+ *
+ * NOTE: this is a placeholder and should eventually be replaced with something 'serious'
+ */
+ auto & components = d->m_list->d->components;
+ auto & componentIndex = d->m_list->d->componentIndex;
+
+ RequireExSet allRequires;
+ QStringList toRemove;
+ do
+ {
+ allRequires.clear();
+ toRemove.clear();
+ if(!gatherRequirementsFromComponents(components, allRequires))
+ {
+ emitFailed(tr("Conflicting requirements detected during dependency checking!"));
+ return;
+ }
+ getTrivialRemovals(components, allRequires, toRemove);
+ if(!toRemove.isEmpty())
+ {
+ qDebug() << "Removing obsolete components...";
+ for(auto & remove : toRemove)
+ {
+ qDebug() << "Removing" << remove;
+ d->m_list->remove(remove);
+ }
+ }
+ } while (!toRemove.isEmpty());
+ RequireExSet toAdd;
+ RequireExSet toChange;
+ bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
+ if(!succeeded)
+ {
+ emitFailed(tr("Instance has conflicting dependencies."));
+ return;
+ }
+ if(checkOnly)
+ {
+ if(toAdd.size() || toChange.size())
+ {
+ emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
+ }
+ else
+ {
+ emitSucceeded();
+ }
+ return;
+ }
+
+ bool recursionNeeded = false;
+ if(toAdd.size())
+ {
+ // add stuff...
+ for(auto &add: toAdd)
+ {
+ ComponentPtr component = new Component(d->m_list, add.uid);
+ if(!add.equalsVersion.isEmpty())
+ {
+ // exact version
+ qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
+ component->m_version = add.equalsVersion;
+ }
+ else
+ {
+ // version needs to be decided
+ qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
+// ############################################################################################################
+// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
+ if(!add.suggests.isEmpty())
+ {
+ component->m_version = add.suggests;
+ }
+ else
+ {
+ if(add.uid == "org.lwjgl")
+ {
+ component->m_version = "2.9.1";
+ }
+ else if (add.uid == "org.lwjgl3")
+ {
+ component->m_version = "3.1.2";
+ }
+ }
+// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
+// ############################################################################################################
+ }
+ component->m_dependencyOnly = true;
+ // FIXME: this should not work directly with the component list
+ d->m_list->insertComponent(add.indexOfFirstDependee, component);
+ componentIndex[add.uid] = component;
+ }
+ recursionNeeded = true;
+ }
+ if(toChange.size())
+ {
+ // change a version of something that exists
+ for(auto &change: toChange)
+ {
+ // FIXME: this should not work directly with the component list
+ qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
+ auto component = componentIndex[change.uid];
+ component->setVersion(change.equalsVersion);
+ }
+ recursionNeeded = true;
+ }
+
+ if(recursionNeeded)
+ {
+ loadComponents();
+ }
+ else
+ {
+ emitSucceeded();
+ }
+}
+
+void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
+{
+ auto &taskSlot = d->remoteLoadStatusList[taskIndex];
+ if(taskSlot.finished)
+ {
+ qWarning() << "Got multiple results from remote load task" << taskIndex;
+ return;
+ }
+ qDebug() << "Remote task" << taskIndex << "succeeded";
+ taskSlot.succeeded = false;
+ taskSlot.finished = true;
+ d->remoteTasksInProgress --;
+ // update the cached data of the component from the downloaded version file.
+ if (taskSlot.type == RemoteLoadStatus::Type::Version)
+ {
+ auto component = d->m_list->getComponent(taskSlot.componentListIndex);
+ component->m_loaded = true;
+ component->updateCachedData();
+ }
+ checkIfAllFinished();
+}
+
+
+void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
+{
+ auto &taskSlot = d->remoteLoadStatusList[taskIndex];
+ if(taskSlot.finished)
+ {
+ qWarning() << "Got multiple results from remote load task" << taskIndex;
+ return;
+ }
+ qDebug() << "Remote task" << taskIndex << "failed: " << msg;
+ d->remoteLoadSuccessful = false;
+ taskSlot.succeeded = false;
+ taskSlot.finished = true;
+ taskSlot.error = msg;
+ d->remoteTasksInProgress --;
+ checkIfAllFinished();
+}
+
+void ComponentUpdateTask::checkIfAllFinished()
+{
+ if(d->remoteTasksInProgress)
+ {
+ // not yet...
+ return;
+ }
+ if(d->remoteLoadSuccessful)
+ {
+ // nothing bad happened... clear the temp load status and proceed with looking at dependencies
+ d->remoteLoadStatusList.clear();
+ resolveDependencies(d->mode == Mode::Launch);
+ }
+ else
+ {
+ // remote load failed... report error and bail
+ QStringList allErrorsList;
+ for(auto & item: d->remoteLoadStatusList)
+ {
+ if(!item.succeeded)
+ {
+ allErrorsList.append(item.error);
+ }
+ }
+ auto allErrors = allErrorsList.join("\n");
+ emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors));
+ d->remoteLoadStatusList.clear();
+ }
+}
diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h
new file mode 100644
index 00000000..11d122b6
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "tasks/Task.h"
+#include "net/Mode.h"
+
+#include <memory>
+class ComponentList;
+struct ComponentUpdateTaskData;
+
+class ComponentUpdateTask : public Task
+{
+ Q_OBJECT
+public:
+ enum class Mode
+ {
+ Launch,
+ Resolution
+ };
+
+public:
+ explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
+ virtual ~ComponentUpdateTask();
+
+protected:
+ void executeTask();
+
+private:
+ void loadComponents();
+ void resolveDependencies(bool checkOnly);
+
+ void remoteLoadSucceeded(size_t index);
+ void remoteLoadFailed(size_t index, const QString &msg);
+ void checkIfAllFinished();
+
+private:
+ std::unique_ptr<ComponentUpdateTaskData> d;
+};
diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h
new file mode 100644
index 00000000..a5216506
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask_p.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <cstddef>
+#include <QString>
+#include <QList>
+#include "net/Mode.h"
+
+class ComponentList;
+
+struct RemoteLoadStatus
+{
+ enum class Type
+ {
+ Index,
+ List,
+ Version
+ } type = Type::Version;
+ size_t componentListIndex = 0;
+ bool finished = false;
+ bool succeeded = false;
+ QString error;
+};
+
+struct ComponentUpdateTaskData
+{
+ ComponentList * m_list = nullptr;
+ QList<RemoteLoadStatus> remoteLoadStatusList;
+ bool remoteLoadSuccessful = true;
+ size_t remoteTasksInProgress = 0;
+ ComponentUpdateTask::Mode mode;
+ Net::Mode netmode;
+};
diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp
index 76e70e5b..b7deed77 100644
--- a/api/logic/minecraft/MinecraftInstance.cpp
+++ b/api/logic/minecraft/MinecraftInstance.cpp
@@ -34,6 +34,7 @@
#include "ComponentList.h"
#include "AssetsUtils.h"
#include "MinecraftUpdate.h"
+#include "MinecraftLoadAndCheck.h"
#define IBUS "@im=ibus"
@@ -63,12 +64,6 @@ private:
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: BaseInstance(globalSettings, settings, rootDir)
{
- // FIXME: remove these
- m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
- m_settings->registerSetting("LWJGLVersion", "2.9.1");
- m_settings->registerSetting("ForgeVersion", "");
- m_settings->registerSetting("LiteloaderVersion", "");
-
// Java Settings
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
@@ -101,51 +96,28 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
-}
-
-void MinecraftInstance::init()
-{
- createProfile();
-}
-
-QString MinecraftInstance::typeName() const
-{
- return "Minecraft";
-}
+ // DEPRECATED: Read what versions the user configuration thinks should be used
+ m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
+ m_settings->registerSetting("LWJGLVersion", "");
+ m_settings->registerSetting("ForgeVersion", "");
+ m_settings->registerSetting("LiteloaderVersion", "");
-bool MinecraftInstance::reload()
-{
- if (BaseInstance::reload())
- {
- try
- {
- reloadProfile();
- return true;
- }
- catch (...)
- {
- return false;
- }
- }
- return false;
-}
-
-void MinecraftInstance::createProfile()
-{
m_components.reset(new ComponentList(this));
+ m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
+ auto setting = m_settings->getSetting("LWJGLVersion");
+ m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
+ m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
+ m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
}
-void MinecraftInstance::reloadProfile()
+void MinecraftInstance::init()
{
- m_components->reload();
- emit versionReloaded();
}
-void MinecraftInstance::clearProfile()
+QString MinecraftInstance::typeName() const
{
- m_components->clearProfile();
- emit versionReloaded();
+ return "Minecraft";
}
std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const
@@ -771,7 +743,7 @@ QString MinecraftInstance::getStatusbarDescription()
}
QString description;
- description.append(tr("Minecraft %1 (%2)").arg(getComponentVersion("net.minecraft")).arg(typeName()));
+ description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
if(totalTimePlayed() > 0)
{
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
@@ -783,9 +755,20 @@ QString MinecraftInstance::getStatusbarDescription()
return description;
}
-shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask()
+shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
{
- return shared_qobject_ptr<Task>(new OneSixUpdate(this));
+ switch (mode)
+ {
+ case Net::Mode::Offline:
+ {
+ return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this));
+ }
+ case Net::Mode::Online:
+ {
+ return shared_qobject_ptr<Task>(new OneSixUpdate(this));
+ }
+ }
+ return nullptr;
}
std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
@@ -827,11 +810,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
if(session->status != AuthSession::PlayableOffline)
{
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
- process->appendStep(std::make_shared<Update>(pptr));
+ process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
+ }
+ else
+ {
+ process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
}
// if there are any jar mods
- if(getJarMods().size())
{
auto step = std::make_shared<ModMinecraftJar>(pptr);
process->appendStep(step);
@@ -900,53 +886,6 @@ JavaVersion MinecraftInstance::getJavaVersion() const
return JavaVersion(settings()->get("JavaVersion").toString());
}
-bool MinecraftInstance::setComponentVersion(const QString& uid, const QString& version)
-{
- if(uid == "net.minecraft")
- {
- settings()->set("IntendedVersion", version);
- }
- else if (uid == "org.lwjgl")
- {
- settings()->set("LWJGLVersion", version);
- }
- else if (uid == "net.minecraftforge")
- {
- settings()->set("ForgeVersion", version);
- }
- else if (uid == "com.mumfrey.liteloader")
- {
- settings()->set("LiteloaderVersion", version);
- }
- if(getComponentList())
- {
- clearProfile();
- }
- emit propertiesChanged(this);
- return true;
-}
-
-QString MinecraftInstance::getComponentVersion(const QString& uid) const
-{
- if(uid == "net.minecraft")
- {
- return settings()->get("IntendedVersion").toString();
- }
- else if(uid == "org.lwjgl")
- {
- return settings()->get("LWJGLVersion").toString();
- }
- else if(uid == "net.minecraftforge")
- {
- return settings()->get("ForgeVersion").toString();
- }
- else if(uid == "com.mumfrey.liteloader")
- {
- return settings()->get("LiteloaderVersion").toString();
- }
- return QString();
-}
-
std::shared_ptr<ModList> MinecraftInstance::loaderModList() const
{
if (!m_loader_mod_list)
diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h
index cea7c60a..13a3753e 100644
--- a/api/logic/minecraft/MinecraftInstance.h
+++ b/api/logic/minecraft/MinecraftInstance.h
@@ -53,12 +53,7 @@ public:
////// Profile management //////
- void createProfile();
std::shared_ptr<ComponentList> getComponentList() const;
- void reloadProfile();
- void clearProfile();
- bool reload() override;
-
////// Mod Lists //////
std::shared_ptr<ModList> loaderModList() const;
@@ -69,7 +64,7 @@ public:
////// Launch stuff //////
- shared_qobject_ptr<Task> createUpdateTask() override;
+ shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
QStringList extraArguments() const override;
QStringList verboseDescription(AuthSessionPtr session) override;
@@ -105,11 +100,6 @@ public:
virtual JavaVersion getJavaVersion() const;
- // FIXME: remove
- QString getComponentVersion(const QString &uid) const;
- // FIXME: remove
- bool setComponentVersion(const QString &uid, const QString &version);
-
signals:
void versionReloaded();
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
new file mode 100644
index 00000000..c64bbddf
--- /dev/null
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
@@ -0,0 +1,45 @@
+#include "MinecraftLoadAndCheck.h"
+#include "MinecraftInstance.h"
+#include "ComponentList.h"
+
+MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+{
+}
+
+void MinecraftLoadAndCheck::executeTask()
+{
+ // add offline metadata load task
+ auto components = m_inst->getComponentList();
+ components->reload(Net::Mode::Offline);
+ m_task = components->getCurrentTask();
+
+ if(!m_task)
+ {
+ emitSucceeded();
+ return;
+ }
+ connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
+ connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
+ connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
+ connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
+}
+
+void MinecraftLoadAndCheck::subtaskSucceeded()
+{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
+ return;
+ }
+ emitSucceeded();
+}
+
+void MinecraftLoadAndCheck::subtaskFailed(QString error)
+{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
+ return;
+ }
+ emitFailed(error);
+}
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h
new file mode 100644
index 00000000..f41c3d2f
--- /dev/null
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.h
@@ -0,0 +1,47 @@
+/* Copyright 2013-2017 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QList>
+#include <QUrl>
+
+#include "tasks/Task.h"
+#include <quazip.h>
+
+#include "QObjectPtr.h"
+
+class MinecraftVersion;
+class MinecraftInstance;
+
+class MinecraftLoadAndCheck : public Task
+{
+ Q_OBJECT
+public:
+ explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
+ void executeTask() override;
+
+private slots:
+ void subtaskSucceeded();
+ void subtaskFailed(QString error);
+
+private:
+ MinecraftInstance *m_inst = nullptr;
+ shared_qobject_ptr<Task> m_task;
+ QString m_preFailure;
+ QString m_fail_reason;
+};
+
diff --git a/api/logic/minecraft/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp
index 529cf13e..12b11544 100644
--- a/api/logic/minecraft/MinecraftUpdate.cpp
+++ b/api/logic/minecraft/MinecraftUpdate.cpp
@@ -39,40 +39,24 @@
OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
+}
+
+void OneSixUpdate::executeTask()
+{
+ m_tasks.clear();
// create folders
{
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
}
- // add metadata update tasks, if necessary
+ // add metadata update task if necessary
{
- /*
- * FIXME: there are some corner cases here that remain unhandled:
- * what if local load succeeds but remote fails? The version is still usable...
- * We should not rely on the remote to be there... and prefer local files if it does not respond.
- */
- qDebug() << "Updating patches...";
- auto profile = m_inst->getComponentList();
- m_inst->reloadProfile();
- for(int i = 0; i < profile->rowCount(); i++)
+ auto components = m_inst->getComponentList();
+ components->reload(Net::Mode::Online);
+ auto task = components->getCurrentTask();
+ if(task)
{
- auto patch = profile->versionPatch(i);
- auto id = patch->getID();
- auto metadata = patch->getMeta();
- if(metadata)
- {
- metadata->load();
- auto task = metadata->getCurrentTask();
- if(task)
- {
- qDebug() << "Loading remote meta patch" << id;
- m_tasks.append(task.unwrap());
- }
- }
- else
- {
- qDebug() << "Ignoring local patch" << id;
- }
+ m_tasks.append(task.unwrap());
}
}
@@ -90,10 +74,7 @@ OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(pare
{
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
}
-}
-void OneSixUpdate::executeTask()
-{
if(!m_preFailure.isEmpty())
{
emitFailed(m_preFailure);
diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp
index 7ebf514f..d91eae58 100644
--- a/api/logic/minecraft/OneSixVersionFormat.cpp
+++ b/api/logic/minecraft/OneSixVersionFormat.cpp
@@ -192,6 +192,19 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
out->mainJar = lib;
}
+ if (root.contains("requires"))
+ {
+ Meta::parseRequires(root, &out->requires);
+ }
+ if (root.contains("conflicts"))
+ {
+ Meta::parseRequires(root, &out->conflicts);
+ }
+ if (root.contains("volatile"))
+ {
+ out->m_volatile = requireBoolean(root, "volatile");
+ }
+
/* removed features that shouldn't be used */
if (root.contains("tweakers"))
{
@@ -216,13 +229,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
return out;
}
-QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder)
+QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch)
{
QJsonObject root;
- if (saveOrder)
- {
- root.insert("order", patch->order);
- }
writeString(root, "name", patch->name);
writeString(root, "uid", patch->uid);
@@ -266,6 +275,18 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
root.insert("mods", array);
}
+ if(!patch->requires.empty())
+ {
+ Meta::serializeRequires(root, &patch->requires, "requires");
+ }
+ if(!patch->conflicts.empty())
+ {
+ Meta::serializeRequires(root, &patch->conflicts, "conflicts");
+ }
+ if(patch->m_volatile)
+ {
+ root.insert("volatile", true);
+ }
// write the contents to a json document.
{
QJsonDocument out;
diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h
index 2306ac8e..8b782db0 100644
--- a/api/logic/minecraft/OneSixVersionFormat.h
+++ b/api/logic/minecraft/OneSixVersionFormat.h
@@ -10,7 +10,7 @@ class OneSixVersionFormat
public:
// version files / profile patches
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder);
- static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder);
+ static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
diff --git a/api/logic/minecraft/ProfilePatch.cpp b/api/logic/minecraft/ProfilePatch.cpp
deleted file mode 100644
index b7a5a37a..00000000
--- a/api/logic/minecraft/ProfilePatch.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-#include <meta/VersionList.h>
-#include <meta/Index.h>
-#include <Env.h>
-#include "ProfilePatch.h"
-
-#include "meta/Version.h"
-#include "VersionFile.h"
-#include "minecraft/ComponentList.h"
-
-ProfilePatch::ProfilePatch(std::shared_ptr<Meta::Version> version)
- :m_metaVersion(version)
-{
-}
-
-ProfilePatch::ProfilePatch(std::shared_ptr<VersionFile> file, const QString& filename)
- :m_file(file), m_filename(filename)
-{
-}
-
-std::shared_ptr<Meta::Version> ProfilePatch::getMeta()
-{
- return m_metaVersion;
-}
-
-void ProfilePatch::applyTo(LaunchProfile* profile)
-{
- auto vfile = getVersionFile();
- if(vfile)
- {
- vfile->applyTo(profile);
- }
- else
- {
- profile->applyProblemSeverity(getProblemSeverity());
- }
-}
-
-std::shared_ptr<class VersionFile> ProfilePatch::getVersionFile() const
-{
- if(m_metaVersion)
- {
- if(!m_metaVersion->isLoaded())
- {
- m_metaVersion->load();
- }
- return m_metaVersion->data();
- }
- else
- {
- return m_file;
- }
-}
-
-std::shared_ptr<class Meta::VersionList> ProfilePatch::getVersionList() const
-{
- if(m_metaVersion)
- {
- return ENV.metadataIndex()->get(m_metaVersion->uid());
- }
- return nullptr;
-}
-
-int ProfilePatch::getOrder()
-{
- if(m_orderOverride)
- return m_order;
-
- auto vfile = getVersionFile();
- if(vfile)
- {
- return vfile->order;
- }
- return 0;
-}
-void ProfilePatch::setOrder(int order)
-{
- m_orderOverride = true;
- m_order = order;
-}
-QString ProfilePatch::getID()
-{
- if(m_metaVersion)
- return m_metaVersion->uid();
- return getVersionFile()->uid;
-}
-QString ProfilePatch::getName()
-{
- if(m_metaVersion)
- return m_metaVersion->name();
- return getVersionFile()->name;
-}
-QString ProfilePatch::getVersion()
-{
- if(m_metaVersion)
- return m_metaVersion->version();
- return getVersionFile()->version;
-}
-QString ProfilePatch::getFilename()
-{
- return m_filename;
-}
-QDateTime ProfilePatch::getReleaseDateTime()
-{
- if(m_metaVersion)
- {
- return m_metaVersion->time();
- }
- return getVersionFile()->releaseTime;
-}
-
-bool ProfilePatch::isCustom()
-{
- return !m_isVanilla;
-};
-
-bool ProfilePatch::isCustomizable()
-{
- if(m_metaVersion)
- {
- if(getVersionFile())
- {
- return true;
- }
- }
- return false;
-}
-bool ProfilePatch::isRemovable()
-{
- return m_isRemovable;
-}
-bool ProfilePatch::isRevertible()
-{
- return m_isRevertible;
-}
-bool ProfilePatch::isMoveable()
-{
- return m_isMovable;
-}
-bool ProfilePatch::isVersionChangeable()
-{
- auto list = getVersionList();
- if(list)
- {
- if(!list->isLoaded())
- {
- list->load();
- }
- return list->count() != 0;
- }
- return false;
-}
-
-void ProfilePatch::setVanilla (bool state)
-{
- m_isVanilla = state;
-}
-void ProfilePatch::setRemovable (bool state)
-{
- m_isRemovable = state;
-}
-void ProfilePatch::setRevertible (bool state)
-{
- m_isRevertible = state;
-}
-void ProfilePatch::setMovable (bool state)
-{
- m_isMovable = state;
-}
-
-ProblemSeverity ProfilePatch::getProblemSeverity() const
-{
- auto file = getVersionFile();
- if(file)
- {
- return file->getProblemSeverity();
- }
- return ProblemSeverity::Error;
-}
-
-const QList<PatchProblem> ProfilePatch::getProblems() const
-{
- auto file = getVersionFile();
- if(file)
- {
- return file->getProblems();
- }
- return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
-}
diff --git a/api/logic/minecraft/ProfilePatch.h b/api/logic/minecraft/ProfilePatch.h
deleted file mode 100644
index 80825342..00000000
--- a/api/logic/minecraft/ProfilePatch.h
+++ /dev/null
@@ -1,71 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <QList>
-#include <QJsonDocument>
-#include <QDateTime>
-#include "ProblemProvider.h"
-
-class ComponentList;
-class LaunchProfile;
-namespace Meta
-{
- class Version;
- class VersionList;
-}
-class VersionFile;
-
-class ProfilePatch : public ProblemProvider
-{
-public:
- ProfilePatch(std::shared_ptr<Meta::Version> version);
- ProfilePatch(std::shared_ptr<VersionFile> file, const QString &filename = QString());
-
- virtual ~ProfilePatch(){};
- virtual void applyTo(LaunchProfile *profile);
-
- virtual bool isMoveable();
- virtual bool isCustomizable();
- virtual bool isRevertible();
- virtual bool isRemovable();
- virtual bool isCustom();
- virtual bool isVersionChangeable();
-
- virtual void setOrder(int order);
- virtual int getOrder();
-
- virtual QString getID();
- virtual QString getName();
- virtual QString getVersion();
- virtual std::shared_ptr<Meta::Version> getMeta();
- virtual QDateTime getReleaseDateTime();
-
- virtual QString getFilename();
-
- virtual std::shared_ptr<class VersionFile> getVersionFile() const;
- virtual std::shared_ptr<class Meta::VersionList> getVersionList() const;
-
- void setVanilla (bool state);
- void setRemovable (bool state);
- void setRevertible (bool state);
- void setMovable (bool state);
-
- const QList<PatchProblem> getProblems() const override;
- ProblemSeverity getProblemSeverity() const override;
-
-protected:
- // Properties for UI and version manipulation from UI in general
- bool m_isMovable = false;
- bool m_isRevertible = false;
- bool m_isRemovable = false;
- bool m_isVanilla = false;
-
- bool m_orderOverride = false;
- int m_order = 0;
-
- std::shared_ptr<Meta::Version> m_metaVersion;
- std::shared_ptr<VersionFile> m_file;
- QString m_filename;
-};
-
-typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr;
diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp
index c8722f7e..a6d2028d 100644
--- a/api/logic/minecraft/ProfileUtils.cpp
+++ b/api/logic/minecraft/ProfileUtils.cpp
@@ -14,38 +14,6 @@ namespace ProfileUtils
static const int currentOrderFileVersion = 1;
-bool writeOverrideOrders(QString path, const PatchOrder &order)
-{
- QJsonObject obj;
- obj.insert("version", currentOrderFileVersion);
- QJsonArray orderArray;
- for(auto str: order)
- {
- orderArray.append(str);
- }
- obj.insert("order", orderArray);
- QSaveFile orderFile(path);
- if (!orderFile.open(QFile::WriteOnly))
- {
- qCritical() << "Couldn't open" << orderFile.fileName()
- << "for writing:" << orderFile.errorString();
- return false;
- }
- auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
- if(orderFile.write(data) != data.size())
- {
- qCritical() << "Couldn't write all the data into" << orderFile.fileName()
- << "because:" << orderFile.errorString();
- return false;
- }
- if(!orderFile.commit())
- {
- qCritical() << "Couldn't save" << orderFile.fileName()
- << "because:" << orderFile.errorString();
- }
- return true;
-}
-
bool readOverrideOrders(QString path, PatchOrder &order)
{
QFile orderFile(path);
@@ -154,6 +122,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
}
+bool saveJsonFile(const QJsonDocument doc, const QString & filename)
+{
+ auto data = doc.toJson();
+ QSaveFile jsonFile(filename);
+ if(!jsonFile.open(QIODevice::WriteOnly))
+ {
+ jsonFile.cancelWriting();
+ qWarning() << "Couldn't open" << filename << "for writing";
+ return false;
+ }
+ jsonFile.write(data);
+ if(!jsonFile.commit())
+ {
+ qWarning() << "Couldn't save" << filename;
+ return false;
+ }
+ return true;
+}
+
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo)
{
QFile file(fileInfo.absoluteFilePath());
diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h
index 267fd42b..351c36cb 100644
--- a/api/logic/minecraft/ProfileUtils.h
+++ b/api/logic/minecraft/ProfileUtils.h
@@ -16,6 +16,9 @@ bool writeOverrideOrders(QString path, const PatchOrder &order);
/// Parse a version file in JSON format
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder);
+/// Save a JSON file (in any format)
+bool saveJsonFile(const QJsonDocument doc, const QString & filename);
+
/// Parse a version file in binary JSON format
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo);
diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h
index 60acaad6..c032f7ea 100644
--- a/api/logic/minecraft/VersionFile.h
+++ b/api/logic/minecraft/VersionFile.h
@@ -10,6 +10,7 @@
#include "minecraft/Rule.h"
#include "ProblemProvider.h"
#include "Library.h"
+#include <meta/JsonFormat.h>
class ComponentList;
class VersionFile;
@@ -29,9 +30,6 @@ public: /* data */
/// MultiMC: order hint for this version file if no explicit order is set
int order = 0;
- /// MultiMC: filename of the file this was loaded from
- // QString filename;
-
/// MultiMC: human readable name of this package
QString name;
@@ -77,7 +75,7 @@ public: /* data */
/// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries;
- // The main jar (Minecraft version library, normally)
+ /// The main jar (Minecraft version library, normally)
LibraryPtr mainJar;
/// MultiMC: list of attached traits of this version file - used to enable features
@@ -89,6 +87,21 @@ public: /* data */
/// MultiMC: list of mods added to this version
QList<LibraryPtr> mods;
+ /**
+ * MultiMC: set of packages this depends on
+ * NOTE: this is shared with the meta format!!!
+ */
+ Meta::RequireSet requires;
+
+ /**
+ * MultiMC: set of packages this conflicts with
+ * NOTE: this is shared with the meta format!!!
+ */
+ Meta::RequireSet conflicts;
+
+ /// is volatile -- may be removed as soon as it is no longer needed by something else
+ bool m_volatile = false;
+
public:
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp
index 6407ade0..9b5d3b20 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.cpp
+++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp
@@ -25,6 +25,11 @@ void ModMinecraftJar::executeTask()
{
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+ if(!m_inst->getJarMods().size())
+ {
+ emitSucceeded();
+ return;
+ }
// nuke obsolete stripped jar(s) if needed
if(!FS::ensureFolderPathExists(m_inst->binRoot()))
{
diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp
index 0b69acf2..a6b745d8 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.cpp
+++ b/api/logic/minecraft/legacy/LegacyInstance.cpp
@@ -71,7 +71,7 @@ bool LegacyInstance::shouldUseCustomBaseJar() const
}
-shared_qobject_ptr<Task> LegacyInstance::createUpdateTask()
+shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode)
{
return nullptr;
}
diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h
index 64564591..ef590cae 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.h
+++ b/api/logic/minecraft/legacy/LegacyInstance.h
@@ -93,7 +93,7 @@ public:
};
virtual bool shouldUpdate() const;
- virtual shared_qobject_ptr<Task> createUpdateTask() override;
+ virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
virtual QString typeName() const override;
diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
index fab48005..6cda3e4d 100644
--- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
+++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
@@ -67,69 +67,70 @@ void LegacyUpgradeTask::copyFinished()
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
- std::shared_ptr<MinecraftInstance> inst(new MinecraftInstance(m_globalSettings, instanceSettings, m_stagingPath));
- inst->setName(m_newName);
- inst->init();
-
- QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
- if(preferredVersionNumber.isNull())
+ // NOTE: this scope ensures the instance is fully saved before we emitSucceeded
{
- // try to decide version based on the jar(s?)
- preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
+ MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
+ inst.setName(m_newName);
+ inst.init();
+
+ QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
if(preferredVersionNumber.isNull())
{
- preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
+ // try to decide version based on the jar(s?)
+ preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
if(preferredVersionNumber.isNull())
{
- emitFailed(tr("Could not decide Minecraft version."));
- return;
+ preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
+ if(preferredVersionNumber.isNull())
+ {
+ emitFailed(tr("Could not decide Minecraft version."));
+ return;
+ }
}
}
- }
- inst->setComponentVersion("net.minecraft", preferredVersionNumber);
+ auto components = inst.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
- // BUG: reloadProfile should not be necessary, but setComponentVersion voids the profile created by init()!
- inst->reloadProfile();
- auto profile = inst->getComponentList();
-
- if(legacyInst->shouldUseCustomBaseJar())
- {
- QString jarPath = legacyInst->customBaseJar();
- qDebug() << "Base jar is custom! : " << jarPath;
- // FIXME: handle case when the jar is unreadable?
- // TODO: check the hash, if it's the same as the upstream jar, do not do this
- profile->installCustomJar(jarPath);
- }
+ if(legacyInst->shouldUseCustomBaseJar())
+ {
+ QString jarPath = legacyInst->customBaseJar();
+ qDebug() << "Base jar is custom! : " << jarPath;
+ // FIXME: handle case when the jar is unreadable?
+ // TODO: check the hash, if it's the same as the upstream jar, do not do this
+ components->installCustomJar(jarPath);
+ }
- auto jarMods = legacyInst->getJarMods();
- for(auto & jarMod: jarMods)
- {
- QString modPath = jarMod.filename().absoluteFilePath();
- qDebug() << "jarMod: " << modPath;
- profile->installJarMods({modPath});
- }
+ auto jarMods = legacyInst->getJarMods();
+ for(auto & jarMod: jarMods)
+ {
+ QString modPath = jarMod.filename().absoluteFilePath();
+ qDebug() << "jarMod: " << modPath;
+ components->installJarMods({modPath});
+ }
- // remove all the extra garbage we no longer need
- auto removeAll = [&](const QString &root, const QStringList &things)
- {
- for(auto &thing : things)
+ // remove all the extra garbage we no longer need
+ auto removeAll = [&](const QString &root, const QStringList &things)
{
- auto removePath = FS::PathCombine(root, thing);
- QFileInfo stat(removePath);
- if(stat.isDir())
+ for(auto &thing : things)
{
- FS::deletePath(removePath);
+ auto removePath = FS::PathCombine(root, thing);
+ QFileInfo stat(removePath);
+ if(stat.isDir())
+ {
+ FS::deletePath(removePath);
+ }
+ else
+ {
+ QFile::remove(removePath);
+ }
}
- else
- {
- QFile::remove(removePath);
- }
- }
- };
- QStringList rootRemovables = {"modlist", "version", "instMods"};
- QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
- removeAll(inst->instanceRoot(), rootRemovables);
- removeAll(inst->minecraftRoot(), mcRemovables);
+ };
+ QStringList rootRemovables = {"modlist", "version", "instMods"};
+ QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
+ removeAll(inst.instanceRoot(), rootRemovables);
+ removeAll(inst.minecraftRoot(), mcRemovables);
+ }
emitSucceeded();
}
diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp
index 56ecee43..1bd339e4 100644
--- a/api/logic/minecraft/update/FMLLibrariesTask.cpp
+++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp
@@ -15,7 +15,6 @@ void FMLLibrariesTask::executeTask()
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto components = inst->getComponentList();
auto profile = components->getProfile();
- bool forge_present = false;
if (!profile->hasTrait("legacyFML"))
{
@@ -23,7 +22,7 @@ void FMLLibrariesTask::executeTask()
return;
}
- QString version = inst->getComponentVersion("net.minecraft");
+ QString version = components->getComponentVersion("net.minecraft");
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
if (!fmlLibsMapping.contains(version))
{
@@ -35,9 +34,7 @@ void FMLLibrariesTask::executeTask()
// determine if we need some libs for FML or forge
setStatus(tr("Checking for FML libraries..."));
- forge_present = (components->versionPatch("net.minecraftforge") != nullptr);
- // we don't...
- if (!forge_present)
+ if(!components->getComponent("net.minecraftforge"))
{
emitSucceeded();
return;
diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp
index 80d45d97..0bec61c1 100644
--- a/api/logic/minecraft/update/LibrariesTask.cpp
+++ b/api/logic/minecraft/update/LibrariesTask.cpp
@@ -13,12 +13,6 @@ void LibrariesTask::executeTask()
setStatus(tr("Getting the library files from Mojang..."));
qDebug() << m_inst->name() << ": downloading libraries";
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
- inst->reloadProfile();
- if(inst->hasVersionBroken())
- {
- 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.
auto components = inst->getComponentList();
diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h
new file mode 100644
index 00000000..62e26d92
--- /dev/null
+++ b/api/logic/net/Mode.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace Net
+{
+enum class Mode
+{
+ Offline,
+ Online
+};
+}
diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp
index d451e652..70b71eaf 100644
--- a/application/LaunchController.cpp
+++ b/application/LaunchController.cpp
@@ -192,7 +192,7 @@ void LaunchController::launchInstance()
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
- if(!m_instance->reload())
+ if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index 908a126c..9f9772c8 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -1303,7 +1303,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
if (MMC->accounts()->anyAccountIsValid())
{
ProgressDialog loadDialog(this);
- auto update = inst->createUpdateTask();
+ auto update = inst->createUpdateTask(Net::Mode::Online);
connect(update.get(), &Task::failed, [this](QString reason)
{
QString error = QString("Instance load failed: %1").arg(reason);
diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp
index d1a2bbfa..9fa20856 100644
--- a/application/dialogs/NewInstanceDialog.cpp
+++ b/application/dialogs/NewInstanceDialog.cpp
@@ -71,7 +71,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
}
else
{
- vlist->load();
+ vlist->load(Net::Mode::Online);
auto task = vlist->getLoadTask();
if(vlist->isLoaded())
{
diff --git a/application/pages/ModFolderPage.cpp b/application/pages/ModFolderPage.cpp
index be1c8289..4ef5d71c 100644
--- a/application/pages/ModFolderPage.cpp
+++ b/application/pages/ModFolderPage.cpp
@@ -106,15 +106,15 @@ bool CoreModFolderPage::shouldDisplay() const
auto version = inst->getComponentList();
if (!version)
return true;
- if(!version->versionPatch("net.minecraftforge"))
+ if(!version->getComponent("net.minecraftforge"))
{
return false;
}
- if(!version->versionPatch("net.minecraft"))
+ if(!version->getComponent("net.minecraft"))
{
return false;
}
- if(version->versionPatch("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
+ if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
{
return true;
}
diff --git a/application/pages/VersionPage.cpp b/application/pages/VersionPage.cpp
index d65d6bc7..c9f3453f 100644
--- a/application/pages/VersionPage.cpp
+++ b/application/pages/VersionPage.cpp
@@ -103,10 +103,10 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
+ m_profile = m_inst->getComponentList();
reloadComponentList();
- m_profile = m_inst->getComponentList();
if (m_profile)
{
auto proxy = new IconProxy(ui->packageView);
@@ -142,7 +142,7 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
return;
}
int row = current.row();
- auto patch = m_profile->versionPatch(row);
+ auto patch = m_profile->getComponent(row);
auto severity = patch->getProblemSeverity();
switch(severity)
{
@@ -196,7 +196,7 @@ bool VersionPage::reloadComponentList()
{
try
{
- m_inst->reloadProfile();
+ m_profile->reload(Net::Mode::Online);
return true;
}
catch (Exception &e)
@@ -262,19 +262,6 @@ void VersionPage::on_jarBtn_clicked()
updateButtons();
}
-void VersionPage::on_resetOrderBtn_clicked()
-{
- try
- {
- m_profile->resetOrder();
- }
- catch (Exception &e)
- {
- QMessageBox::critical(this, tr("Error"), e.cause());
- }
- updateButtons();
-}
-
void VersionPage::on_moveUpBtn_clicked()
{
try
@@ -308,7 +295,7 @@ void VersionPage::on_changeVersionBtn_clicked()
{
return;
}
- auto patch = m_profile->versionPatch(versionRow);
+ auto patch = m_profile->getComponent(versionRow);
auto name = patch->getName();
auto list = patch->getVersionList();
if(!list)
@@ -331,8 +318,10 @@ void VersionPage::on_changeVersionBtn_clicked()
}
qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
+ bool important = false;
if(uid == "net.minecraft")
{
+ important = true;
if (!m_profile->isVanilla())
{
auto result = CustomMessageBox::selectable(
@@ -348,14 +337,14 @@ void VersionPage::on_changeVersionBtn_clicked()
reloadComponentList();
}
}
- m_inst->setComponentVersion(uid, vselect.selectedVersion()->descriptor());
+ m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
doUpdate();
m_container->refreshContainer();
}
int VersionPage::doUpdate()
{
- auto updateTask = m_inst->createUpdateTask();
+ auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
if (!updateTask)
{
return 1;
@@ -376,20 +365,58 @@ void VersionPage::on_forgeBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->getComponentVersion("net.minecraft"));
- vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_inst->getComponentVersion("net.minecraft"));
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+ vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
- m_inst->setComponentVersion("net.minecraftforge", vsn->descriptor());
- m_profile->reload();
+ m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor());
+ m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion();
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
}
}
+// TODO: use something like this... except the final decision of what to show has to be deferred until the lists are known
+/*
+void VersionPage::on_liteloaderBtn_clicked()
+{
+ QString uid = "com.mumfrey.liteloader";
+ auto vlist = ENV.metadataIndex()->get(uid);
+ if(!vlist)
+ {
+ return;
+ }
+ VersionSelectDialog vselect(vlist.get(), tr("Select %1 version").arg(vlist->name()), this);
+ auto parentUid = vlist->parentUid();
+ if(!parentUid.isEmpty())
+ {
+ auto parentvlist = ENV.metadataIndex()->get(parentUid);
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion(parentUid));
+ vselect.setEmptyString(
+ tr("No %1 versions are currently available for %2 %3")
+ .arg(vlist->name())
+ .arg(parentvlist->name())
+ .arg(m_profile->getComponentVersion(parentUid)));
+ }
+ else
+ {
+ vselect.setEmptyString(tr("No %1 versions are currently available"));
+ }
+ vselect.setEmptyErrorString(tr("Couldn't load or download the %1 version lists!").arg(vlist->name()));
+ if (vselect.exec() && vselect.selectedVersion())
+ {
+ auto vsn = vselect.selectedVersion();
+ m_profile->setComponentVersion(uid, vsn->descriptor());
+ m_profile->resolve();
+ preselect(m_profile->rowCount(QModelIndex())-1);
+ m_container->refreshContainer();
+ }
+}
+*/
+
void VersionPage::on_liteloaderBtn_clicked()
{
auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader");
@@ -398,14 +425,14 @@ void VersionPage::on_liteloaderBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this);
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->getComponentVersion("net.minecraft"));
- vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_inst->getComponentVersion("net.minecraft"));
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+ vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
- m_inst->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
- m_profile->reload();
+ m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
+ m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion(vselect.selectedVersion());
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
@@ -441,7 +468,7 @@ void VersionPage::updateButtons(int row)
{
if(row == -1)
row = currentRow();
- auto patch = m_profile->versionPatch(row);
+ auto patch = m_profile->getComponent(row);
if (!patch)
{
ui->removeBtn->setDisabled(true);
@@ -470,14 +497,14 @@ void VersionPage::onGameUpdateError(QString error)
QMessageBox::Warning)->show();
}
-ProfilePatchPtr VersionPage::current()
+ComponentPtr VersionPage::current()
{
auto row = currentRow();
if(row < 0)
{
return nullptr;
}
- return m_profile->versionPatch(row);
+ return m_profile->getComponent(row);
}
int VersionPage::currentRow()
@@ -496,7 +523,7 @@ void VersionPage::on_customizeBtn_clicked()
{
return;
}
- auto patch = m_profile->versionPatch(version);
+ auto patch = m_profile->getComponent(version);
if(!patch->getVersionFile())
{
// TODO: wait for the update task to finish here...
diff --git a/application/pages/VersionPage.h b/application/pages/VersionPage.h
index 0ac10a14..809b2c0c 100644
--- a/application/pages/VersionPage.h
+++ b/application/pages/VersionPage.h
@@ -53,7 +53,6 @@ private slots:
void on_liteloaderBtn_clicked();
void on_reloadBtn_clicked();
void on_removeBtn_clicked();
- void on_resetOrderBtn_clicked();
void on_moveUpBtn_clicked();
void on_moveDownBtn_clicked();
void on_jarmodBtn_clicked();
@@ -68,7 +67,7 @@ private slots:
void on_changeVersionBtn_clicked();
private:
- ProfilePatchPtr current();
+ ComponentPtr current();
int currentRow();
void updateButtons(int row = -1);
void preselect(int row = 0);
diff --git a/application/pages/VersionPage.ui b/application/pages/VersionPage.ui
index afb33164..b6da3294 100644
--- a/application/pages/VersionPage.ui
+++ b/application/pages/VersionPage.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>693</width>
- <height>788</height>
+ <height>833</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -218,16 +218,6 @@
</widget>
</item>
<item>
- <widget class="QPushButton" name="resetOrderBtn">
- <property name="toolTip">
- <string>Reset apply order of packages.</string>
- </property>
- <property name="text">
- <string>Reset order</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QPushButton" name="reloadBtn">
<property name="toolTip">
<string>Reload all packages.</string>
@@ -300,7 +290,6 @@
<tabstop>liteloaderBtn</tabstop>
<tabstop>modBtn</tabstop>
<tabstop>jarmodBtn</tabstop>
- <tabstop>resetOrderBtn</tabstop>
<tabstop>reloadBtn</tabstop>
</tabstops>
<resources/>
diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp
index e15ddbab..7b117d0b 100644
--- a/application/pages/global/PackagesPage.cpp
+++ b/application/pages/global/PackagesPage.cpp
@@ -38,10 +38,17 @@ static QString formatRequires(const VersionPtr &version)
auto iter = reqs.begin();
while (iter != reqs.end())
{
- auto &uid = iter.key();
- auto &version = iter.value();
+ auto &uid = iter->uid;
+ auto &version = iter->equalsVersion;
const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
- lines.append(QString("%1 (%2)").arg(readable, version));
+ if(!version.isEmpty())
+ {
+ lines.append(QString("%1 (%2)").arg(readable, version));
+ }
+ else
+ {
+ lines.append(QString("%1").arg(readable));
+ }
iter++;
}
return lines.join('\n');
@@ -95,7 +102,7 @@ QIcon PackagesPage::icon() const
void PackagesPage::on_refreshIndexBtn_clicked()
{
- ENV.metadataIndex()->load();
+ ENV.metadataIndex()->load(Net::Mode::Online);
}
void PackagesPage::on_refreshFileBtn_clicked()
{
@@ -104,7 +111,7 @@ void PackagesPage::on_refreshFileBtn_clicked()
{
return;
}
- list->load();
+ list->load(Net::Mode::Online);
}
void PackagesPage::on_refreshVersionBtn_clicked()
{
@@ -113,7 +120,7 @@ void PackagesPage::on_refreshVersionBtn_clicked()
{
return;
}
- version->load();
+ version->load(Net::Mode::Online);
}
void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
@@ -156,7 +163,7 @@ void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
ui->fileName->setText(list->name());
m_versionProxy->setSourceModel(list.get());
ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
- list->load();
+ list->load(Net::Mode::Offline);
}
else
{
@@ -213,5 +220,5 @@ void PackagesPage::updateVersion()
void PackagesPage::opened()
{
- ENV.metadataIndex()->load();
+ ENV.metadataIndex()->load(Net::Mode::Offline);
}