From d66fdcd4cc6913508d2987c14cd9fc4d6760b8a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 3 Oct 2016 00:55:54 +0200 Subject: NOISSUE Granular instance reload --- api/logic/InstanceList.cpp | 508 ++++++++++++--------------------------------- 1 file changed, 134 insertions(+), 374 deletions(-) (limited to 'api/logic/InstanceList.cpp') diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp index d89b8ea7..9bdcc113 100644 --- a/api/logic/InstanceList.cpp +++ b/api/logic/InstanceList.cpp @@ -16,39 +16,21 @@ #include #include #include -#include #include #include -#include -#include -#include #include -#include #include #include "InstanceList.h" #include "BaseInstance.h" -//FIXME: this really doesn't belong *here* -#include "minecraft/onesix/OneSixInstance.h" -#include "minecraft/legacy/LegacyInstance.h" -#include "minecraft/ftb/FTBPlugin.h" -#include "minecraft/MinecraftVersion.h" -#include "settings/INISettingsObject.h" -#include "NullInstance.h" -#include "FileSystem.h" -#include "pathmatcher/RegexpMatcher.h" - -const static int GROUP_FILE_FORMAT_VERSION = 1; +#include "FolderInstanceProvider.h" InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent) : QAbstractListModel(parent), m_instDir(instDir) { m_globalSettings = globalSettings; - if (!QDir::current().exists(m_instDir)) - { - QDir::current().mkpath(m_instDir); - } + resumeWatch(); } InstanceList::~InstanceList() @@ -120,34 +102,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const return f; } -void InstanceList::groupChanged() -{ - // save the groups. save all of them. - saveGroupList(); -} - QStringList InstanceList::getGroups() { return m_groups.toList(); } -void InstanceList::suspendGroupSaving() -{ - suspendedGroupSave = true; -} - -void InstanceList::resumeGroupSaving() -{ - if(suspendedGroupSave) - { - suspendedGroupSave = false; - if(queuedGroupSave) - { - saveGroupList(); - } - } -} - void InstanceList::deleteGroup(const QString& name) { for(auto & instance: m_instances) @@ -160,230 +119,180 @@ void InstanceList::deleteGroup(const QString& name) } } -void InstanceList::saveGroupList() +static QMap getIdMapping(const QList &list) { - if(suspendedGroupSave) + QMap out; + int i = 0; + for(auto & item: list) { - queuedGroupSave = true; - return; + auto id = item->id(); + if(out.contains(id)) + { + qWarning() << "Duplicate ID" << id << "in instance list"; + } + out[id] = std::make_pair(item, i); + i++; } + return out; +} - QString groupFileName = m_instDir + "/instgroups.json"; - QMap> groupMap; - for (auto instance : m_instances) - { - QString id = instance->id(); - QString group = instance->group(); - if (group.isEmpty()) - continue; +InstanceList::InstListError InstanceList::loadList(bool complete) +{ + auto existingIds = getIdMapping(m_instances); - // keep a list/set of groups for choosing - m_groups.insert(group); + QList newList; - if (!groupMap.count(group)) + auto processIds = [&](BaseInstanceProvider * provider, QList ids) + { + for(auto & id: ids) { - QSet set; - set.insert(id); - groupMap[group] = set; + if(existingIds.contains(id)) + { + auto instPair = existingIds[id]; + /* + auto & instPtr = instPair.first; + auto & instIdx = instPair.second; + */ + existingIds.remove(id); + qDebug() << "Should keep and soft-reload" << id; + } + else + { + InstancePtr instPtr = provider->loadInstance(id); + newList.append(instPtr); + } } - else + }; + if(complete) + { + for(auto & item: m_providers) { - QSet &set = groupMap[group]; - set.insert(id); + processIds(item.get(), item->discoverInstances()); } } - QJsonObject toplevel; - toplevel.insert("formatVersion", QJsonValue(QString("1"))); - QJsonObject groupsArr; - for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++) + else { - auto list = iter.value(); - auto name = iter.key(); - QJsonObject groupObj; - QJsonArray instanceArr; - groupObj.insert("hidden", QJsonValue(QString("false"))); - for (auto item : list) + for (auto & item: m_updatedProviders) { - instanceArr.append(QJsonValue(item)); + processIds(item, item->discoverInstances()); } - groupObj.insert("instances", instanceArr); - groupsArr.insert(name, groupObj); } - toplevel.insert("groups", groupsArr); - QJsonDocument doc(toplevel); - try + + // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. + if(!existingIds.isEmpty()) { - FS::write(groupFileName, doc.toJson()); + // get the list of removed instances and sort it by their original index, from last to first + auto deadList = existingIds.values(); + auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool + { + return a.second > b.second; + }; + std::sort(deadList.begin(), deadList.end(), orderSortPredicate); + // remove the contiguous ranges of rows + int front_bookmark = -1; + int back_bookmark = -1; + int currentItem = -1; + auto removeNow = [&]() + { + beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); + m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); + endRemoveRows(); + front_bookmark = -1; + back_bookmark = currentItem; + }; + for(auto & removedItem: deadList) + { + auto instPtr = removedItem.first; + if(!complete && !m_updatedProviders.contains(instPtr->provider())) + { + continue; + } + instPtr->invalidate(); + currentItem = removedItem.second; + if(back_bookmark == -1) + { + // no bookmark yet + back_bookmark = currentItem; + } + else if(currentItem == front_bookmark - 1) + { + // part of contiguous sequence, continue + } + else + { + // seam between previous and current item + removeNow(); + } + front_bookmark = currentItem; + } + if(back_bookmark != -1) + { + removeNow(); + } } - catch(FS::FileSystemException & e) + if(newList.size()) { - qCritical() << "Failed to write instance group file :" << e.cause(); + add(newList); } + m_updatedProviders.clear(); + return NoError; } -void InstanceList::loadGroupList(QMap &groupMap) +void InstanceList::add(const QList &t) { - QString groupFileName = m_instDir + "/instgroups.json"; - - // if there's no group file, fail - if (!QFileInfo(groupFileName).exists()) - return; - - QByteArray jsonData; - try - { - jsonData = FS::read(groupFileName); - } - catch (FS::FileSystemException & e) - { - qCritical() << "Failed to read instance group file :" << e.cause(); - return; - } - - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); - - // if the json was bad, fail - if (error.error != QJsonParseError::NoError) + beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); + m_instances.append(t); + for(auto & ptr : t) { - qCritical() << QString("Failed to parse instance group file: %1 at offset %2") - .arg(error.errorString(), QString::number(error.offset)) - .toUtf8(); - return; + connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); } + endInsertRows(); +} - // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) +void InstanceList::resumeWatch() +{ + if(m_watchLevel > 0) { - qWarning() << "Invalid group file. Root entry should be an object."; + qWarning() << "Bad suspend level resume in instance list"; return; } - - QJsonObject rootObj = jsonDoc.object(); - - // Make sure the format version matches, otherwise fail. - if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) - return; - - // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) + m_watchLevel++; + if(m_watchLevel > 0 && !m_updatedProviders.isEmpty()) { - qWarning() << "Invalid group list JSON: 'groups' should be an object."; - return; - } - - // Iterate through all the groups. - QJsonObject groupMapping = rootObj.value("groups").toObject(); - for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) - { - QString groupName = iter.key(); - - // If not an object, complain and skip to the next one. - if (!iter.value().isObject()) - { - qWarning() << QString("Group '%1' in the group list should " - "be an object.") - .arg(groupName) - .toUtf8(); - continue; - } - - QJsonObject groupObj = iter.value().toObject(); - if (!groupObj.value("instances").isArray()) - { - qWarning() << QString("Group '%1' in the group list is invalid. " - "It should contain an array " - "called 'instances'.") - .arg(groupName) - .toUtf8(); - continue; - } - - // keep a list/set of groups for choosing - m_groups.insert(groupName); - - // Iterate through the list of instances in the group. - QJsonArray instancesArray = groupObj.value("instances").toArray(); - - for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); - iter2++) - { - groupMap[(*iter2).toString()] = groupName; - } + loadList(); } } -InstanceList::InstListError InstanceList::loadList() +void InstanceList::suspendWatch() { - // load the instance groups - QMap groupMap; - loadGroupList(groupMap); + m_watchLevel --; +} - QList tempList; +void InstanceList::providerUpdated() +{ + auto provider = dynamic_cast(QObject::sender()); + if(!provider) { - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, - QDirIterator::FollowSymlinks); - while (iter.hasNext()) - { - QString subDir = iter.next(); - if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) - continue; - qDebug() << "Loading MultiMC instance from " << subDir; - InstancePtr instPtr; - auto error = loadInstance(instPtr, subDir); - if(!continueProcessInstance(instPtr, error, subDir, groupMap)) - continue; - tempList.append(instPtr); - } + qWarning() << "InstanceList::providerUpdated triggered by a non-provider"; + return; } - - // FIXME: generalize - FTBPlugin::loadInstances(m_globalSettings, groupMap, tempList); - - beginResetModel(); - m_instances.clear(); - for(auto inst: tempList) + m_updatedProviders.insert(provider); + if(m_watchLevel == 1) { - connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, - SLOT(propertiesChanged(BaseInstance *))); - connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); - connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this, - SLOT(instanceNuked(BaseInstance *))); - m_instances.append(inst); + loadList(); } - endResetModel(); - emit dataIsInvalid(); - return NoError; -} - -/// Clear all instances. Triggers notifications. -void InstanceList::clear() -{ - beginResetModel(); - saveGroupList(); - m_instances.clear(); - endResetModel(); - emit dataIsInvalid(); } -void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) +void InstanceList::groupsPublished(QSet newGroups) { - m_instDir = value.toString(); - loadList(); + m_groups.unite(newGroups); } -/// Add an instance. Triggers notifications, returns the new index -int InstanceList::add(InstancePtr t) +void InstanceList::addInstanceProvider(BaseInstanceProvider* provider) { - beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size()); - m_instances.append(t); - t->setParent(this); - connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, - SLOT(propertiesChanged(BaseInstance *))); - connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); - connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *))); - endInsertRows(); - return count() - 1; + connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated); + connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished); + m_providers.append(provider); } InstancePtr InstanceList::getInstanceById(QString instId) const @@ -418,157 +327,6 @@ int InstanceList::getInstIndex(BaseInstance *inst) const return -1; } -bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error, - const QDir &dir, QMap &groupMap) -{ - if (error != InstanceList::NoLoadError && error != InstanceList::NotAnInstance) - { - QString errorMsg = QString("Failed to load instance %1: ") - .arg(QFileInfo(dir.absolutePath()).baseName()) - .toUtf8(); - - switch (error) - { - default: - errorMsg += QString("Unknown instance loader error %1").arg(error); - break; - } - qCritical() << errorMsg.toUtf8(); - return false; - } - else if (!instPtr) - { - qCritical() << QString("Error loading instance %1. Instance loader returned null.") - .arg(QFileInfo(dir.absolutePath()).baseName()) - .toUtf8(); - return false; - } - else - { - auto iter = groupMap.find(instPtr->id()); - if (iter != groupMap.end()) - { - instPtr->setGroupInitial((*iter)); - } - qDebug() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath(); - return true; - } -} - -InstanceList::InstLoadError -InstanceList::loadInstance(InstancePtr &inst, const QString &instDir) -{ - auto instanceSettings = std::make_shared(FS::PathCombine(instDir, "instance.cfg")); - - instanceSettings->registerSetting("InstanceType", "Legacy"); - - QString inst_type = instanceSettings->get("InstanceType").toString(); - - // FIXME: replace with a map lookup, where instance classes register their types - if (inst_type == "OneSix" || inst_type == "Nostalgia") - { - inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir)); - } - else if (inst_type == "Legacy") - { - inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instDir)); - } - else - { - inst.reset(new NullInstance(m_globalSettings, instanceSettings, instDir)); - } - inst->init(); - return NoLoadError; -} - -InstanceList::InstCreateError -InstanceList::createInstance(InstancePtr &inst, BaseVersionPtr version, const QString &instDir) -{ - QDir rootDir(instDir); - - qDebug() << instDir.toUtf8(); - if (!rootDir.exists() && !rootDir.mkpath(".")) - { - qCritical() << "Can't create instance folder" << instDir; - return InstanceList::CantCreateDir; - } - - if (!version) - { - qCritical() << "Can't create instance for non-existing MC version"; - return InstanceList::NoSuchVersion; - } - - auto instanceSettings = std::make_shared(FS::PathCombine(instDir, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - auto minecraftVersion = std::dynamic_pointer_cast(version); - if(minecraftVersion) - { - auto mcVer = std::dynamic_pointer_cast(version); - instanceSettings->set("InstanceType", "OneSix"); - inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instDir)); - inst->setIntendedVersionId(version->descriptor()); - inst->init(); - return InstanceList::NoCreateError; - } - return InstanceList::NoSuchVersion; -} - -InstanceList::InstCreateError -InstanceList::copyInstance(InstancePtr &newInstance, InstancePtr &oldInstance, const QString &instDir, bool copySaves) -{ - QDir rootDir(instDir); - std::unique_ptr matcher; - if(!copySaves) - { - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); - matcherReal->caseSensitive(false); - matcher.reset(matcherReal); - } - - qDebug() << instDir.toUtf8(); - FS::copy folderCopy(oldInstance->instanceRoot(), instDir); - folderCopy.followSymlinks(false).blacklist(matcher.get()); - if (!folderCopy()) - { - FS::deletePath(instDir); - return InstanceList::CantCreateDir; - } - - INISettingsObject settings_obj(FS::PathCombine(instDir, "instance.cfg")); - settings_obj.registerSetting("InstanceType", "Legacy"); - QString inst_type = settings_obj.get("InstanceType").toString(); - - oldInstance->copy(instDir); - - auto error = loadInstance(newInstance, instDir); - - switch (error) - { - case NoLoadError: - return NoCreateError; - case NotAnInstance: - rootDir.removeRecursively(); - return CantCreateDir; - default: - case UnknownLoadError: - rootDir.removeRecursively(); - return UnknownCreateError; - } -} - -void InstanceList::instanceNuked(BaseInstance *inst) -{ - int i = getInstIndex(inst); - if (i != -1) - { - beginRemoveRows(QModelIndex(), i, i); - m_instances.removeAt(i); - endRemoveRows(); - } -} - void InstanceList::propertiesChanged(BaseInstance *inst) { int i = getInstIndex(inst); @@ -577,3 +335,5 @@ void InstanceList::propertiesChanged(BaseInstance *inst) emit dataChanged(index(i), index(i)); } } + +#include "InstanceList.moc" -- cgit v1.2.3