diff options
Diffstat (limited to 'api/logic/minecraft/ComponentUpdateTask.cpp')
-rw-r--r-- | api/logic/minecraft/ComponentUpdateTask.cpp | 704 |
1 files changed, 0 insertions, 704 deletions
diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp deleted file mode 100644 index 241d9a49..00000000 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ /dev/null @@ -1,704 +0,0 @@ -#include "ComponentUpdateTask.h" - -#include "PackProfile_p.h" -#include "PackProfile.h" -#include "Component.h" -#include <Env.h> -#include <meta/Index.h> -#include <meta/VersionList.h> -#include <meta/Version.h> -#include "ComponentUpdateTask_p.h" -#include <cassert> -#include <Version.h> -#include "net/Mode.h" -#include "OneSixVersionFormat.h" - -/* - * This is responsible for loading the components of a component list AND resolving dependency issues between them - */ - -/* - * FIXME: the 'one shot async task' nature of this does not fit the intended usage - * Really, it should be a reactor/state machine that receives input from the application - * and dynamically adapts to changing requirements... - * - * The reactor should be the only entry into manipulating the PackProfile. - * See: https://en.wikipedia.org/wiki/Reactor_pattern - */ - -/* - * Or make this operate on a snapshot of the PackProfile state, then merge results in as long as the snapshot and PackProfile didn't change? - * If the component list changes, start over. - */ - -ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) - : Task(parent) -{ - d.reset(new ComponentUpdateTaskData); - d->m_list = list; - d->mode = mode; - d->netmode = netmode; -} - -ComponentUpdateTask::~ComponentUpdateTask() -{ -} - -void ComponentUpdateTask::executeTask() -{ - qDebug() << "Loading components"; - loadComponents(); -} - -namespace -{ -enum class LoadResult -{ - LoadedLocal, - RequiresRemote, - Failed -}; - -LoadResult composeLoadResult(LoadResult a, LoadResult b) -{ - if (a < b) - { - return b; - } - return a; -} - -static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto customPatchFilename = component->getFilename(); - if(QFile::exists(customPatchFilename)) - { - // if local file exists... - - // check for uid problems inside... - bool fileChanged = false; - auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); - if(file->uid != component->m_uid) - { - file->uid = component->m_uid; - fileChanged = true; - } - if(fileChanged) - { - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); - } - - component->m_file = file; - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); - component->m_metaVersion = metaVersion; - if(metaVersion->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaVersion->load(netmode); - loadTask = metaVersion->getCurrentTask(); - if(loadTask) - result = LoadResult::RequiresRemote; - else if (metaVersion->isLoaded()) - result = LoadResult::LoadedLocal; - else - result = LoadResult::Failed; - } - } - return result; -} - -// FIXME: dead code. determine if this can still be useful? -/* -static LoadResult loadPackProfile(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto metaList = ENV.metadataIndex()->get(component->m_uid); - if(metaList->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaList->load(netmode); - loadTask = metaList->getCurrentTask(); - result = LoadResult::RequiresRemote; - } - return result; -} -*/ - -static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode) -{ - // FIXME: DECIDE. do we want to run the update task anyway? - if(ENV.metadataIndex()->isLoaded()) - { - qDebug() << "Index is already loaded"; - return LoadResult::LoadedLocal; - } - ENV.metadataIndex()->load(netmode); - loadTask = ENV.metadataIndex()->getCurrentTask(); - if(loadTask) - { - return LoadResult::RequiresRemote; - } - // FIXME: this is assuming the load succeeded... did it really? - return LoadResult::LoadedLocal; -} -} - -void ComponentUpdateTask::loadComponents() -{ - LoadResult result = LoadResult::LoadedLocal; - size_t taskIndex = 0; - size_t componentIndex = 0; - d->remoteLoadSuccessful = true; - // load the main index (it is needed to determine if components can revert) - { - // FIXME: tear out as a method? or lambda? - shared_qobject_ptr<Task> indexLoadTask; - auto singleResult = loadIndex(indexLoadTask, d->netmode); - result = composeLoadResult(result, singleResult); - if(indexLoadTask) - { - qDebug() << "Remote loading is being run for metadata index"; - RemoteLoadStatus status; - status.type = RemoteLoadStatus::Type::Index; - d->remoteLoadStatusList.append(status); - connect(indexLoadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - taskIndex++; - } - } - // load all the components OR their lists... - for (auto component: d->m_list->d->components) - { - shared_qobject_ptr<Task> loadTask; - LoadResult singleResult; - RemoteLoadStatus::Type loadType; - // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... -#if 0 - switch(d->mode) - { - case Mode::Launch: - { - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; - break; - } - case Mode::Resolution: - { - singleResult = loadPackProfile(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::List; - break; - } - } -#else - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; -#endif - if(singleResult == LoadResult::LoadedLocal) - { - component->updateCachedData(); - } - result = composeLoadResult(result, singleResult); - if (loadTask) - { - qDebug() << "Remote loading is being run for" << component->getName(); - connect(loadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(loadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - RemoteLoadStatus status; - status.type = loadType; - status.PackProfileIndex = componentIndex; - d->remoteLoadStatusList.append(status); - taskIndex++; - } - componentIndex++; - } - d->remoteTasksInProgress = taskIndex; - switch(result) - { - case LoadResult::LoadedLocal: - { - // Everything got loaded. Advance to dependency resolution. - resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); - break; - } - case LoadResult::RequiresRemote: - { - // we wait for signals. - break; - } - case LoadResult::Failed: - { - emitFailed(tr("Some component metadata load tasks failed.")); - break; - } - } -} - -namespace -{ - struct RequireEx : public Meta::Require - { - size_t indexOfFirstDependee = 0; - }; - struct RequireCompositionResult - { - bool ok; - RequireEx outcome; - }; - using RequireExSet = std::set<RequireEx>; -} - -static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) -{ - assert(a.uid == b.uid); - RequireEx out; - out.uid = a.uid; - out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); - if(a.equalsVersion.isEmpty()) - { - out.equalsVersion = b.equalsVersion; - } - else if (b.equalsVersion.isEmpty()) - { - out.equalsVersion = a.equalsVersion; - } - else if (a.equalsVersion == b.equalsVersion) - { - out.equalsVersion = a.equalsVersion; - } - else - { - // FIXME: mark error as explicit version conflict - return {false, out}; - } - - if(a.suggests.isEmpty()) - { - out.suggests = b.suggests; - } - else if (b.suggests.isEmpty()) - { - out.suggests = a.suggests; - } - else - { - Version aVer(a.suggests); - Version bVer(b.suggests); - out.suggests = (aVer < bVer ? b.suggests : a.suggests); - } - return {true, out}; -} - -// gather the requirements from all components, finding any obvious conflicts -static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) -{ - bool succeeded = true; - size_t componentNum = 0; - for(auto component: input) - { - auto &componentRequires = component->m_cachedRequires; - for(const auto & componentRequire: componentRequires) - { - auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ - return req.uid == componentRequire.uid; - }); - - RequireEx componenRequireEx; - componenRequireEx.uid = componentRequire.uid; - componenRequireEx.suggests = componentRequire.suggests; - componenRequireEx.equalsVersion = componentRequire.equalsVersion; - componenRequireEx.indexOfFirstDependee = componentNum; - - if(found != output.cend()) - { - // found... process it further - auto result = composeRequirement(componenRequireEx, *found); - if(result.ok) - { - output.erase(componenRequireEx); - output.insert(result.outcome); - } - else - { - qCritical() - << "Conflicting requirements:" - << componentRequire.uid - << "versions:" - << componentRequire.equalsVersion - << ";" - << (*found).equalsVersion; - } - succeeded &= result.ok; - } - else - { - // not found, accumulate - output.insert(componenRequireEx); - } - } - componentNum++; - } - return succeeded; -} - -/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) -static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) -{ - for(const auto & component: components) - { - if(!component->m_dependencyOnly) - continue; - if(!component->m_cachedVolatile) - continue; - RequireEx reqNeedle; - reqNeedle.uid = component->m_uid; - const auto iter = reqs.find(reqNeedle); - if(iter == reqs.cend()) - { - toRemove.append(component->m_uid); - } - } -} - -/** - * handles: - * - trivial addition (there is an unmet requirement and it can be trivially met by adding something) - * - trivial version conflict of dependencies == explicit version required and installed is different - * - * toAdd - set of requirements than mean adding a new component - * toChange - set of requirements that mean changing version of an existing component - */ -static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) -{ - enum class Decision - { - Undetermined, - Met, - Missing, - VersionNotSame, - LockedVersionNotSame - } decision = Decision::Undetermined; - - QString reqStr; - bool succeeded = true; - // list the composed requirements and say if they are met or unmet - for(auto & req: input) - { - do - { - if(req.equalsVersion.isEmpty()) - { - reqStr = QString("Req: %1").arg(req.uid); - if(index.contains(req.uid)) - { - decision = Decision::Met; - } - else - { - toAdd.insert(req); - decision = Decision::Missing; - } - break; - } - else - { - reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); - const auto & compIter = index.find(req.uid); - if(compIter == index.cend()) - { - toAdd.insert(req); - decision = Decision::Missing; - break; - } - auto & comp = (*compIter); - if(comp->getVersion() != req.equalsVersion) - { - if(comp->isCustom()) { - decision = Decision::LockedVersionNotSame; - } else { - if(comp->m_dependencyOnly) - { - decision = Decision::VersionNotSame; - } - else - { - decision = Decision::LockedVersionNotSame; - } - } - break; - } - decision = Decision::Met; - } - } while(false); - switch(decision) - { - case Decision::Undetermined: - qCritical() << "No decision for" << reqStr; - succeeded = false; - break; - case Decision::Met: - qDebug() << reqStr << "Is met."; - break; - case Decision::Missing: - qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; - toAdd.insert(req); - break; - case Decision::VersionNotSame: - qDebug() << reqStr << "already has different version that can be changed."; - toChange.insert(req); - break; - case Decision::LockedVersionNotSame: - qDebug() << reqStr << "already has different version that cannot be changed."; - succeeded = false; - break; - } - } - return succeeded; -} - -// FIXME, TODO: decouple dependency resolution from loading -// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses. -// FIXME: throw all this away and use a graph -void ComponentUpdateTask::resolveDependencies(bool checkOnly) -{ - qDebug() << "Resolving dependencies"; - /* - * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: - * 1. There are conflicting dependencies on the same uid with different exact version numbers - * -> hard error - * 2. A dependency has non-matching exact version number - * -> hard error - * 3. A dependency is entirely missing and needs to be injected before the dependee(s) - * -> requirements are injected - * - * NOTE: this is a placeholder and should eventually be replaced with something 'serious' - */ - auto & components = d->m_list->d->components; - auto & componentIndex = d->m_list->d->componentIndex; - - RequireExSet allRequires; - QStringList toRemove; - do - { - allRequires.clear(); - toRemove.clear(); - if(!gatherRequirementsFromComponents(components, allRequires)) - { - emitFailed(tr("Conflicting requirements detected during dependency checking!")); - return; - } - getTrivialRemovals(components, allRequires, toRemove); - if(!toRemove.isEmpty()) - { - qDebug() << "Removing obsolete components..."; - for(auto & remove : toRemove) - { - qDebug() << "Removing" << remove; - d->m_list->remove(remove); - } - } - } while (!toRemove.isEmpty()); - RequireExSet toAdd; - RequireExSet toChange; - bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); - if(!succeeded) - { - emitFailed(tr("Instance has conflicting dependencies.")); - return; - } - if(checkOnly) - { - if(toAdd.size() || toChange.size()) - { - emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); - } - else - { - emitSucceeded(); - } - return; - } - - bool recursionNeeded = false; - if(toAdd.size()) - { - // add stuff... - for(auto &add: toAdd) - { - ComponentPtr component = new Component(d->m_list, add.uid); - if(!add.equalsVersion.isEmpty()) - { - // exact version - qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; - component->m_version = add.equalsVersion; - } - else - { - // version needs to be decided - qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; -// ############################################################################################################ -// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. - if(!add.suggests.isEmpty()) - { - component->m_version = add.suggests; - } - else - { - if(add.uid == "org.lwjgl") - { - component->m_version = "2.9.1"; - } - else if (add.uid == "org.lwjgl3") - { - component->m_version = "3.1.2"; - } - else if (add.uid == "net.fabricmc.intermediary") - { - auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ - return cmp->getID() == "net.minecraft"; - }); - if(minecraft != components.end()) { - component->m_version = (*minecraft)->getVersion(); - } - } - } -// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. -// ############################################################################################################ - } - component->m_dependencyOnly = true; - // FIXME: this should not work directly with the component list - d->m_list->insertComponent(add.indexOfFirstDependee, component); - componentIndex[add.uid] = component; - } - recursionNeeded = true; - } - if(toChange.size()) - { - // change a version of something that exists - for(auto &change: toChange) - { - // FIXME: this should not work directly with the component list - qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; - auto component = componentIndex[change.uid]; - component->setVersion(change.equalsVersion); - } - recursionNeeded = true; - } - - if(recursionNeeded) - { - loadComponents(); - } - else - { - emitSucceeded(); - } -} - -void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) -{ - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "succeeded"; - taskSlot.succeeded = false; - taskSlot.finished = true; - d->remoteTasksInProgress --; - // update the cached data of the component from the downloaded version file. - if (taskSlot.type == RemoteLoadStatus::Type::Version) - { - auto component = d->m_list->getComponent(taskSlot.PackProfileIndex); - component->m_loaded = true; - component->updateCachedData(); - } - checkIfAllFinished(); -} - - -void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) -{ - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "failed: " << msg; - d->remoteLoadSuccessful = false; - taskSlot.succeeded = false; - taskSlot.finished = true; - taskSlot.error = msg; - d->remoteTasksInProgress --; - checkIfAllFinished(); -} - -void ComponentUpdateTask::checkIfAllFinished() -{ - if(d->remoteTasksInProgress) - { - // not yet... - return; - } - if(d->remoteLoadSuccessful) - { - // nothing bad happened... clear the temp load status and proceed with looking at dependencies - d->remoteLoadStatusList.clear(); - resolveDependencies(d->mode == Mode::Launch); - } - else - { - // remote load failed... report error and bail - QStringList allErrorsList; - for(auto & item: d->remoteLoadStatusList) - { - if(!item.succeeded) - { - allErrorsList.append(item.error); - } - } - auto allErrors = allErrorsList.join("\n"); - emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); - d->remoteLoadStatusList.clear(); - } -} |