summaryrefslogtreecommitdiffstats
path: root/api/logic/minecraft/ComponentList.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'api/logic/minecraft/ComponentList.cpp')
-rw-r--r--api/logic/minecraft/ComponentList.cpp1140
1 files changed, 731 insertions, 409 deletions
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();
}