2017-11-11 00:38:31 +00:00
|
|
|
#include "ComponentUpdateTask.h"
|
|
|
|
|
2022-04-14 15:50:04 +01:00
|
|
|
#include "Component.h"
|
2017-11-11 00:38:31 +00:00
|
|
|
#include "ComponentUpdateTask_p.h"
|
2023-08-02 17:35:35 +01:00
|
|
|
#include "OneSixVersionFormat.h"
|
|
|
|
#include "PackProfile.h"
|
|
|
|
#include "PackProfile_p.h"
|
2021-11-21 22:21:12 +00:00
|
|
|
#include "Version.h"
|
2023-08-02 17:35:35 +01:00
|
|
|
#include "cassert"
|
|
|
|
#include "meta/Index.h"
|
|
|
|
#include "meta/Version.h"
|
|
|
|
#include "meta/VersionList.h"
|
2017-11-11 00:38:31 +00:00
|
|
|
#include "net/Mode.h"
|
|
|
|
|
2021-11-21 22:21:12 +00:00
|
|
|
#include "Application.h"
|
|
|
|
|
2017-11-11 00:38:31 +00:00
|
|
|
/*
|
|
|
|
* 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...
|
|
|
|
*
|
2020-06-27 11:02:31 +01:00
|
|
|
* The reactor should be the only entry into manipulating the PackProfile.
|
2017-11-11 00:38:31 +00:00
|
|
|
* See: https://en.wikipedia.org/wiki/Reactor_pattern
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2020-06-27 11:02:31 +01:00
|
|
|
* 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?
|
2017-11-11 00:38:31 +00:00
|
|
|
* If the component list changes, start over.
|
|
|
|
*/
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
d.reset(new ComponentUpdateTaskData);
|
|
|
|
d->m_list = list;
|
|
|
|
d->mode = mode;
|
|
|
|
d->netmode = netmode;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
ComponentUpdateTask::~ComponentUpdateTask() {}
|
2017-11-11 00:38:31 +00:00
|
|
|
|
|
|
|
void ComponentUpdateTask::executeTask()
|
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Loading components";
|
|
|
|
loadComponents();
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
namespace {
|
|
|
|
enum class LoadResult { LoadedLocal, RequiresRemote, Failed };
|
2017-11-11 00:38:31 +00:00
|
|
|
|
|
|
|
LoadResult composeLoadResult(LoadResult a, LoadResult b)
|
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
if (a < b) {
|
2018-07-15 13:51:05 +01:00
|
|
|
return b;
|
|
|
|
}
|
|
|
|
return a;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
2021-11-21 22:36:55 +00:00
|
|
|
static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
if (component->m_loaded) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << component->getName() << "is already loaded";
|
|
|
|
return LoadResult::LoadedLocal;
|
|
|
|
}
|
|
|
|
|
|
|
|
LoadResult result = LoadResult::Failed;
|
|
|
|
auto customPatchFilename = component->getFilename();
|
2023-08-02 17:35:35 +01:00
|
|
|
if (QFile::exists(customPatchFilename)) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// if local file exists...
|
|
|
|
|
|
|
|
// check for uid problems inside...
|
|
|
|
bool fileChanged = false;
|
|
|
|
auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (file->uid != component->m_uid) {
|
2018-07-15 13:51:05 +01:00
|
|
|
file->uid = component->m_uid;
|
|
|
|
fileChanged = true;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (fileChanged) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// FIXME: @QUALITY do not ignore return value
|
|
|
|
ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename);
|
|
|
|
}
|
|
|
|
|
|
|
|
component->m_file = file;
|
|
|
|
component->m_loaded = true;
|
|
|
|
result = LoadResult::LoadedLocal;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2021-11-21 22:21:12 +00:00
|
|
|
auto metaVersion = APPLICATION->metadataIndex()->get(component->m_uid, component->m_version);
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_metaVersion = metaVersion;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (metaVersion->isLoaded()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_loaded = true;
|
|
|
|
result = LoadResult::LoadedLocal;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
metaVersion->load(netmode);
|
|
|
|
loadTask = metaVersion->getCurrentTask();
|
2023-08-02 17:35:35 +01:00
|
|
|
if (loadTask)
|
2018-07-15 13:51:05 +01:00
|
|
|
result = LoadResult::RequiresRemote;
|
|
|
|
else if (metaVersion->isLoaded())
|
|
|
|
result = LoadResult::LoadedLocal;
|
|
|
|
else
|
|
|
|
result = LoadResult::Failed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
2018-02-11 00:21:32 +00:00
|
|
|
// FIXME: dead code. determine if this can still be useful?
|
|
|
|
/*
|
2021-11-21 22:36:55 +00:00
|
|
|
static LoadResult loadPackProfile(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
if(component->m_loaded)
|
|
|
|
{
|
|
|
|
qDebug() << component->getName() << "is already loaded";
|
|
|
|
return LoadResult::LoadedLocal;
|
|
|
|
}
|
|
|
|
|
|
|
|
LoadResult result = LoadResult::Failed;
|
2021-11-21 22:21:12 +00:00
|
|
|
auto metaList = APPLICATION->metadataIndex()->get(component->m_uid);
|
2018-07-15 13:51:05 +01:00
|
|
|
if(metaList->isLoaded())
|
|
|
|
{
|
|
|
|
component->m_loaded = true;
|
|
|
|
result = LoadResult::LoadedLocal;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
metaList->load(netmode);
|
|
|
|
loadTask = metaList->getCurrentTask();
|
|
|
|
result = LoadResult::RequiresRemote;
|
|
|
|
}
|
|
|
|
return result;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
2018-02-11 00:21:32 +00:00
|
|
|
*/
|
2017-11-11 00:38:31 +00:00
|
|
|
|
2021-11-21 22:36:55 +00:00
|
|
|
static LoadResult loadIndex(Task::Ptr& loadTask, Net::Mode netmode)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
// FIXME: DECIDE. do we want to run the update task anyway?
|
2023-08-02 17:35:35 +01:00
|
|
|
if (APPLICATION->metadataIndex()->isLoaded()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Index is already loaded";
|
|
|
|
return LoadResult::LoadedLocal;
|
|
|
|
}
|
2021-11-21 22:21:12 +00:00
|
|
|
APPLICATION->metadataIndex()->load(netmode);
|
|
|
|
loadTask = APPLICATION->metadataIndex()->getCurrentTask();
|
2023-08-02 17:35:35 +01:00
|
|
|
if (loadTask) {
|
2018-07-15 13:51:05 +01:00
|
|
|
return LoadResult::RequiresRemote;
|
|
|
|
}
|
|
|
|
// FIXME: this is assuming the load succeeded... did it really?
|
|
|
|
return LoadResult::LoadedLocal;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} // namespace
|
2017-11-11 00:38:31 +00:00
|
|
|
|
|
|
|
void ComponentUpdateTask::loadComponents()
|
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
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?
|
2021-11-21 22:36:55 +00:00
|
|
|
Task::Ptr indexLoadTask;
|
2018-07-15 13:51:05 +01:00
|
|
|
auto singleResult = loadIndex(indexLoadTask, d->netmode);
|
|
|
|
result = composeLoadResult(result, singleResult);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (indexLoadTask) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Remote loading is being run for metadata index";
|
|
|
|
RemoteLoadStatus status;
|
|
|
|
status.type = RemoteLoadStatus::Type::Index;
|
|
|
|
d->remoteLoadStatusList.append(status);
|
2023-08-02 17:35:35 +01:00
|
|
|
connect(indexLoadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
|
|
|
connect(indexLoadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
|
|
|
connect(indexLoadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
2018-07-15 13:51:05 +01:00
|
|
|
taskIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// load all the components OR their lists...
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto component : d->m_list->d->components) {
|
2021-11-21 22:36:55 +00:00
|
|
|
Task::Ptr loadTask;
|
2018-07-15 13:51:05 +01:00
|
|
|
LoadResult singleResult;
|
|
|
|
RemoteLoadStatus::Type loadType;
|
2023-08-02 17:35:35 +01:00
|
|
|
// 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...
|
2017-11-11 00:38:31 +00:00
|
|
|
#if 0
|
2018-07-15 13:51:05 +01:00
|
|
|
switch(d->mode)
|
|
|
|
{
|
|
|
|
case Mode::Launch:
|
|
|
|
{
|
|
|
|
singleResult = loadComponent(component, loadTask, d->netmode);
|
|
|
|
loadType = RemoteLoadStatus::Type::Version;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Mode::Resolution:
|
|
|
|
{
|
2020-06-27 11:02:31 +01:00
|
|
|
singleResult = loadPackProfile(component, loadTask, d->netmode);
|
2018-07-15 13:51:05 +01:00
|
|
|
loadType = RemoteLoadStatus::Type::List;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-11-11 00:38:31 +00:00
|
|
|
#else
|
2018-07-15 13:51:05 +01:00
|
|
|
singleResult = loadComponent(component, loadTask, d->netmode);
|
|
|
|
loadType = RemoteLoadStatus::Type::Version;
|
2017-11-11 00:38:31 +00:00
|
|
|
#endif
|
2023-08-02 17:35:35 +01:00
|
|
|
if (singleResult == LoadResult::LoadedLocal) {
|
2018-07-15 13:51:05 +01:00
|
|
|
component->updateCachedData();
|
|
|
|
}
|
|
|
|
result = composeLoadResult(result, singleResult);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (loadTask) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Remote loading is being run for" << component->getName();
|
2023-08-02 17:35:35 +01:00
|
|
|
connect(loadTask.get(), &Task::succeeded, [=]() { remoteLoadSucceeded(taskIndex); });
|
|
|
|
connect(loadTask.get(), &Task::failed, [=](const QString& error) { remoteLoadFailed(taskIndex, error); });
|
|
|
|
connect(loadTask.get(), &Task::aborted, [=]() { remoteLoadFailed(taskIndex, tr("Aborted")); });
|
2018-07-15 13:51:05 +01:00
|
|
|
RemoteLoadStatus status;
|
|
|
|
status.type = loadType;
|
2020-06-27 11:02:31 +01:00
|
|
|
status.PackProfileIndex = componentIndex;
|
2018-07-15 13:51:05 +01:00
|
|
|
d->remoteLoadStatusList.append(status);
|
|
|
|
taskIndex++;
|
|
|
|
}
|
|
|
|
componentIndex++;
|
|
|
|
}
|
|
|
|
d->remoteTasksInProgress = taskIndex;
|
2023-08-02 17:35:35 +01:00
|
|
|
switch (result) {
|
|
|
|
case LoadResult::LoadedLocal: {
|
2018-07-15 13:51:05 +01:00
|
|
|
// Everything got loaded. Advance to dependency resolution.
|
|
|
|
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
case LoadResult::RequiresRemote: {
|
2018-07-15 13:51:05 +01:00
|
|
|
// we wait for signals.
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
case LoadResult::Failed: {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitFailed(tr("Some component metadata load tasks failed."));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
namespace {
|
|
|
|
struct RequireEx : public Meta::Require {
|
|
|
|
size_t indexOfFirstDependee = 0;
|
|
|
|
};
|
|
|
|
struct RequireCompositionResult {
|
|
|
|
bool ok;
|
|
|
|
RequireEx outcome;
|
|
|
|
};
|
|
|
|
using RequireExSet = std::set<RequireEx>;
|
|
|
|
} // namespace
|
2017-11-11 00:38:31 +00:00
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
static RequireCompositionResult composeRequirement(const RequireEx& a, const RequireEx& b)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
assert(a.uid == b.uid);
|
|
|
|
RequireEx out;
|
|
|
|
out.uid = a.uid;
|
|
|
|
out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (a.equalsVersion.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
out.equalsVersion = b.equalsVersion;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (b.equalsVersion.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
out.equalsVersion = a.equalsVersion;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (a.equalsVersion == b.equalsVersion) {
|
2018-07-15 13:51:05 +01:00
|
|
|
out.equalsVersion = a.equalsVersion;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
// FIXME: mark error as explicit version conflict
|
2023-08-02 17:35:35 +01:00
|
|
|
return { false, out };
|
2018-07-15 13:51:05 +01:00
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
if (a.suggests.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
out.suggests = b.suggests;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (b.suggests.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
out.suggests = a.suggests;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
Version aVer(a.suggests);
|
|
|
|
Version bVer(b.suggests);
|
|
|
|
out.suggests = (aVer < bVer ? b.suggests : a.suggests);
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
return { true, out };
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// gather the requirements from all components, finding any obvious conflicts
|
2023-08-02 17:35:35 +01:00
|
|
|
static bool gatherRequirementsFromComponents(const ComponentContainer& input, RequireExSet& output)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
bool succeeded = true;
|
|
|
|
size_t componentNum = 0;
|
2023-08-02 17:35:35 +01:00
|
|
|
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; });
|
2018-07-15 13:51:05 +01:00
|
|
|
|
|
|
|
RequireEx componenRequireEx;
|
|
|
|
componenRequireEx.uid = componentRequire.uid;
|
|
|
|
componenRequireEx.suggests = componentRequire.suggests;
|
|
|
|
componenRequireEx.equalsVersion = componentRequire.equalsVersion;
|
|
|
|
componenRequireEx.indexOfFirstDependee = componentNum;
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
if (found != output.cend()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// found... process it further
|
|
|
|
auto result = composeRequirement(componenRequireEx, *found);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (result.ok) {
|
2018-07-15 13:51:05 +01:00
|
|
|
output.erase(componenRequireEx);
|
|
|
|
output.insert(result.outcome);
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
|
|
|
qCritical() << "Conflicting requirements:" << componentRequire.uid << "versions:" << componentRequire.equalsVersion
|
|
|
|
<< ";" << (*found).equalsVersion;
|
2018-07-15 13:51:05 +01:00
|
|
|
}
|
|
|
|
succeeded &= result.ok;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
// not found, accumulate
|
|
|
|
output.insert(componenRequireEx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
componentNum++;
|
|
|
|
}
|
|
|
|
return succeeded;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps)
|
2023-08-02 17:35:35 +01:00
|
|
|
static void getTrivialRemovals(const ComponentContainer& components, const RequireExSet& reqs, QStringList& toRemove)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
for (const auto& component : components) {
|
|
|
|
if (!component->m_dependencyOnly)
|
2018-07-15 13:51:05 +01:00
|
|
|
continue;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!component->m_cachedVolatile)
|
2018-07-15 13:51:05 +01:00
|
|
|
continue;
|
|
|
|
RequireEx reqNeedle;
|
|
|
|
reqNeedle.uid = component->m_uid;
|
|
|
|
const auto iter = reqs.find(reqNeedle);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (iter == reqs.cend()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
toRemove.append(component->m_uid);
|
|
|
|
}
|
|
|
|
}
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2023-08-02 17:35:35 +01:00
|
|
|
static bool getTrivialComponentChanges(const ComponentIndex& index, const RequireExSet& input, RequireExSet& toAdd, RequireExSet& toChange)
|
2017-11-11 00:38:31 +00:00
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
enum class Decision { Undetermined, Met, Missing, VersionNotSame, LockedVersionNotSame } decision = Decision::Undetermined;
|
2018-07-15 13:51:05 +01:00
|
|
|
|
|
|
|
QString reqStr;
|
|
|
|
bool succeeded = true;
|
|
|
|
// list the composed requirements and say if they are met or unmet
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto& req : input) {
|
|
|
|
do {
|
|
|
|
if (req.equalsVersion.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
reqStr = QString("Req: %1").arg(req.uid);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (index.contains(req.uid)) {
|
2018-07-15 13:51:05 +01:00
|
|
|
decision = Decision::Met;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
toAdd.insert(req);
|
|
|
|
decision = Decision::Missing;
|
|
|
|
}
|
|
|
|
break;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion);
|
2023-08-02 17:35:35 +01:00
|
|
|
const auto& compIter = index.find(req.uid);
|
|
|
|
if (compIter == index.cend()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
toAdd.insert(req);
|
|
|
|
decision = Decision::Missing;
|
|
|
|
break;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
auto& comp = (*compIter);
|
|
|
|
if (comp->getVersion() != req.equalsVersion) {
|
|
|
|
if (comp->isCustom()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
decision = Decision::LockedVersionNotSame;
|
2020-01-08 03:41:47 +00:00
|
|
|
} else {
|
2023-08-02 17:35:35 +01:00
|
|
|
if (comp->m_dependencyOnly) {
|
2020-01-08 03:41:47 +00:00
|
|
|
decision = Decision::VersionNotSame;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2020-01-08 03:41:47 +00:00
|
|
|
decision = Decision::LockedVersionNotSame;
|
|
|
|
}
|
2018-07-15 13:51:05 +01:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
decision = Decision::Met;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
} while (false);
|
|
|
|
switch (decision) {
|
2018-07-15 13:51:05 +01:00
|
|
|
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;
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME, TODO: decouple dependency resolution from loading
|
2020-06-27 11:02:31 +01:00
|
|
|
// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses.
|
2017-11-11 00:38:31 +00:00
|
|
|
// FIXME: throw all this away and use a graph
|
|
|
|
void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
|
|
|
{
|
2018-07-15 13:51:05 +01:00
|
|
|
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'
|
|
|
|
*/
|
2023-08-02 17:35:35 +01:00
|
|
|
auto& components = d->m_list->d->components;
|
|
|
|
auto& componentIndex = d->m_list->d->componentIndex;
|
2018-07-15 13:51:05 +01:00
|
|
|
|
|
|
|
RequireExSet allRequires;
|
|
|
|
QStringList toRemove;
|
2023-08-02 17:35:35 +01:00
|
|
|
do {
|
2018-07-15 13:51:05 +01:00
|
|
|
allRequires.clear();
|
|
|
|
toRemove.clear();
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!gatherRequirementsFromComponents(components, allRequires)) {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitFailed(tr("Conflicting requirements detected during dependency checking!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
getTrivialRemovals(components, allRequires, toRemove);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!toRemove.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Removing obsolete components...";
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto& remove : toRemove) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qDebug() << "Removing" << remove;
|
|
|
|
d->m_list->remove(remove);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (!toRemove.isEmpty());
|
|
|
|
RequireExSet toAdd;
|
|
|
|
RequireExSet toChange;
|
|
|
|
bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!succeeded) {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitFailed(tr("Instance has conflicting dependencies."));
|
|
|
|
return;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (checkOnly) {
|
|
|
|
if (toAdd.size() || toChange.size()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitSucceeded();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool recursionNeeded = false;
|
2023-08-02 17:35:35 +01:00
|
|
|
if (toAdd.size()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// add stuff...
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto& add : toAdd) {
|
2023-01-24 19:52:09 +00:00
|
|
|
auto component = makeShared<Component>(d->m_list, add.uid);
|
2023-08-02 17:35:35 +01:00
|
|
|
if (!add.equalsVersion.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// exact version
|
|
|
|
qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
|
|
|
|
component->m_version = add.equalsVersion;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
// version needs to be decided
|
|
|
|
qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
|
2023-08-02 17:35:35 +01:00
|
|
|
// ############################################################################################################
|
|
|
|
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
|
|
|
if (!add.suggests.isEmpty()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_version = add.suggests;
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
|
|
|
if (add.uid == "org.lwjgl") {
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_version = "2.9.1";
|
2023-08-02 17:35:35 +01:00
|
|
|
} else if (add.uid == "org.lwjgl3") {
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_version = "3.1.2";
|
2023-08-02 17:35:35 +01:00
|
|
|
} 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()) {
|
2022-04-09 13:56:07 +01:00
|
|
|
component->m_version = (*minecraft)->getVersion();
|
|
|
|
}
|
2022-02-20 16:51:26 +00:00
|
|
|
}
|
2022-04-14 15:50:04 +01:00
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
|
|
|
// ############################################################################################################
|
2018-07-15 13:51:05 +01:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (toChange.size()) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// change a version of something that exists
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto& change : toChange) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2023-08-02 17:35:35 +01:00
|
|
|
if (recursionNeeded) {
|
2018-07-15 13:51:05 +01:00
|
|
|
loadComponents();
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
emitSucceeded();
|
|
|
|
}
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
|
|
|
if (taskSlot.finished) {
|
2018-07-15 13:51:05 +01:00
|
|
|
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
qDebug() << "Remote task" << taskIndex << "succeeded";
|
|
|
|
taskSlot.succeeded = false;
|
|
|
|
taskSlot.finished = true;
|
2023-08-02 17:35:35 +01:00
|
|
|
d->remoteTasksInProgress--;
|
2018-07-15 13:51:05 +01:00
|
|
|
// update the cached data of the component from the downloaded version file.
|
2023-08-02 17:35:35 +01:00
|
|
|
if (taskSlot.type == RemoteLoadStatus::Type::Version) {
|
2020-06-27 11:02:31 +01:00
|
|
|
auto component = d->m_list->getComponent(taskSlot.PackProfileIndex);
|
2018-07-15 13:51:05 +01:00
|
|
|
component->m_loaded = true;
|
|
|
|
component->updateCachedData();
|
|
|
|
}
|
|
|
|
checkIfAllFinished();
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
auto& taskSlot = d->remoteLoadStatusList[taskIndex];
|
|
|
|
if (taskSlot.finished) {
|
2018-07-15 13:51:05 +01:00
|
|
|
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;
|
2023-08-02 17:35:35 +01:00
|
|
|
d->remoteTasksInProgress--;
|
2018-07-15 13:51:05 +01:00
|
|
|
checkIfAllFinished();
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ComponentUpdateTask::checkIfAllFinished()
|
|
|
|
{
|
2023-08-02 17:35:35 +01:00
|
|
|
if (d->remoteTasksInProgress) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// not yet...
|
|
|
|
return;
|
|
|
|
}
|
2023-08-02 17:35:35 +01:00
|
|
|
if (d->remoteLoadSuccessful) {
|
2018-07-15 13:51:05 +01:00
|
|
|
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
|
|
|
|
d->remoteLoadStatusList.clear();
|
|
|
|
resolveDependencies(d->mode == Mode::Launch);
|
2023-08-02 17:35:35 +01:00
|
|
|
} else {
|
2018-07-15 13:51:05 +01:00
|
|
|
// remote load failed... report error and bail
|
|
|
|
QStringList allErrorsList;
|
2023-08-02 17:35:35 +01:00
|
|
|
for (auto& item : d->remoteLoadStatusList) {
|
|
|
|
if (!item.succeeded) {
|
2018-07-15 13:51:05 +01:00
|
|
|
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();
|
|
|
|
}
|
2017-11-11 00:38:31 +00:00
|
|
|
}
|