diff options
author | Petr Mrázek <peterix@gmail.com> | 2017-11-11 01:38:31 +0100 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2017-12-03 01:22:34 +0100 |
commit | 85ae710d407eb31527183d5f8bec0399eb209f33 (patch) | |
tree | 23647a83506ad3bd052720fb40068240e9768481 /api/logic/minecraft/ComponentList.cpp | |
parent | 17c8f31a09da6bdfc4aa7f67b2ca86b791f2ba96 (diff) | |
download | PrismLauncher-85ae710d407eb31527183d5f8bec0399eb209f33.tar.gz PrismLauncher-85ae710d407eb31527183d5f8bec0399eb209f33.tar.bz2 PrismLauncher-85ae710d407eb31527183d5f8bec0399eb209f33.zip |
GH-2026 implement changes necessary to support 1.13 snapshots
Diffstat (limited to 'api/logic/minecraft/ComponentList.cpp')
-rw-r--r-- | api/logic/minecraft/ComponentList.cpp | 1140 |
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(); } |