706 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			706 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "ComponentUpdateTask.h"
 | |
| 
 | |
| #include "PackProfile_p.h"
 | |
| #include "PackProfile.h"
 | |
| #include "Component.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"
 | |
| 
 | |
| #include "Application.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, Task::Ptr& 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 = APPLICATION->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, Task::Ptr& loadTask, Net::Mode netmode)
 | |
| {
 | |
|     if(component->m_loaded)
 | |
|     {
 | |
|         qDebug() << component->getName() << "is already loaded";
 | |
|         return LoadResult::LoadedLocal;
 | |
|     }
 | |
| 
 | |
|     LoadResult result = LoadResult::Failed;
 | |
|     auto metaList = APPLICATION->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(Task::Ptr& loadTask, Net::Mode netmode)
 | |
| {
 | |
|     // FIXME: DECIDE. do we want to run the update task anyway?
 | |
|     if(APPLICATION->metadataIndex()->isLoaded())
 | |
|     {
 | |
|         qDebug() << "Index is already loaded";
 | |
|         return LoadResult::LoadedLocal;
 | |
|     }
 | |
|     APPLICATION->metadataIndex()->load(netmode);
 | |
|     loadTask = APPLICATION->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?
 | |
|         Task::Ptr 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)
 | |
|     {
 | |
|         Task::Ptr 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" || add.uid == "org.quiltmc.hashed")
 | |
|                     {
 | |
|                         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();
 | |
|     }
 | |
| }
 |