GH-2026 implement changes necessary to support 1.13 snapshots
This commit is contained in:
408
api/logic/minecraft/Component.cpp
Normal file
408
api/logic/minecraft/Component.cpp
Normal file
@ -0,0 +1,408 @@
|
||||
#include <meta/VersionList.h>
|
||||
#include <meta/Index.h>
|
||||
#include <Env.h>
|
||||
#include "Component.h"
|
||||
|
||||
#include "meta/Version.h"
|
||||
#include "VersionFile.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QSaveFile>
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include <assert.h>
|
||||
|
||||
Component::Component(ComponentList * parent, const QString& uid)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_uid = uid;
|
||||
}
|
||||
|
||||
Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_metaVersion = version;
|
||||
m_uid = version->uid();
|
||||
m_version = m_cachedVersion = version->version();
|
||||
m_cachedName = version->name();
|
||||
m_loaded = version->isLoaded();
|
||||
}
|
||||
|
||||
Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_file = file;
|
||||
m_uid = uid;
|
||||
m_cachedVersion = m_file->version;
|
||||
m_cachedName = m_file->name;
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Meta::Version> Component::getMeta()
|
||||
{
|
||||
return m_metaVersion;
|
||||
}
|
||||
|
||||
void Component::applyTo(LaunchProfile* profile)
|
||||
{
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
vfile->applyTo(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile->applyProblemSeverity(getProblemSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(!m_metaVersion->isLoaded())
|
||||
{
|
||||
m_metaVersion->load(Net::Mode::Online);
|
||||
}
|
||||
return m_metaVersion->data();
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
|
||||
{
|
||||
// FIXME: what if the metadata index isn't loaded yet?
|
||||
if(ENV.metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
return ENV.metadataIndex()->get(m_uid);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Component::getOrder()
|
||||
{
|
||||
if(m_orderOverride)
|
||||
return m_order;
|
||||
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
return vfile->order;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void Component::setOrder(int order)
|
||||
{
|
||||
m_orderOverride = true;
|
||||
m_order = order;
|
||||
}
|
||||
QString Component::getID()
|
||||
{
|
||||
return m_uid;
|
||||
}
|
||||
QString Component::getName()
|
||||
{
|
||||
if (!m_cachedName.isEmpty())
|
||||
return m_cachedName;
|
||||
return m_uid;
|
||||
}
|
||||
QString Component::getVersion()
|
||||
{
|
||||
return m_cachedVersion;
|
||||
}
|
||||
QString Component::getFilename()
|
||||
{
|
||||
return m_parent->patchFilePathForUid(m_uid);
|
||||
}
|
||||
QDateTime Component::getReleaseDateTime()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
return m_metaVersion->time();
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
return vfile->releaseTime;
|
||||
}
|
||||
// FIXME: fake
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
bool Component::isCustom()
|
||||
{
|
||||
return m_file != nullptr;
|
||||
};
|
||||
|
||||
bool Component::isCustomizable()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(getVersionFile())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Component::isRemovable()
|
||||
{
|
||||
return !m_important;
|
||||
}
|
||||
bool Component::isRevertible()
|
||||
{
|
||||
if (isCustom())
|
||||
{
|
||||
if(ENV.metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Component::isMoveable()
|
||||
{
|
||||
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
|
||||
return true;
|
||||
}
|
||||
bool Component::isVersionChangeable()
|
||||
{
|
||||
auto list = getVersionList();
|
||||
if(list)
|
||||
{
|
||||
if(!list->isLoaded())
|
||||
{
|
||||
list->load(Net::Mode::Online);
|
||||
}
|
||||
return list->count() != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Component::setImportant(bool state)
|
||||
{
|
||||
if(m_important != state)
|
||||
{
|
||||
m_important = state;
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
ProblemSeverity Component::getProblemSeverity() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblemSeverity();
|
||||
}
|
||||
return ProblemSeverity::Error;
|
||||
}
|
||||
|
||||
const QList<PatchProblem> Component::getProblems() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblems();
|
||||
}
|
||||
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
|
||||
}
|
||||
|
||||
void Component::setVersion(const QString& version)
|
||||
{
|
||||
if(version == m_version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
if(m_loaded)
|
||||
{
|
||||
// we are loaded and potentially have state to invalidate
|
||||
if(m_file)
|
||||
{
|
||||
// we have a file... explicit version has been changed and there is nothing else to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't have a file, therefore we are loaded with metadata
|
||||
m_cachedVersion = version;
|
||||
// see if the meta version is loaded
|
||||
auto metaVersion = ENV.metadataIndex()->get(m_uid, version);
|
||||
if(metaVersion->isLoaded())
|
||||
{
|
||||
// if yes, we can continue with that.
|
||||
m_metaVersion = metaVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not, we need loading
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
updateCachedData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not loaded... assume it will be sorted out later by the update task
|
||||
}
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
bool Component::customize()
|
||||
{
|
||||
if(isCustom())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto filename = getFilename();
|
||||
if(!FS::ensureFilePathExists(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// FIXME: get rid of this try-catch.
|
||||
try
|
||||
{
|
||||
QSaveFile jsonFile(filename);
|
||||
if(!jsonFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(!vfile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto document = OneSixVersionFormat::versionFileToJson(vfile);
|
||||
jsonFile.write(document.toJson());
|
||||
if(!jsonFile.commit())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_file = vfile;
|
||||
m_metaVersion.reset();
|
||||
emit dataChanged();
|
||||
}
|
||||
catch (Exception &error)
|
||||
{
|
||||
qWarning() << "Version could not be loaded:" << error.cause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Component::revert()
|
||||
{
|
||||
if(!isCustom())
|
||||
{
|
||||
// already not custom
|
||||
return true;
|
||||
}
|
||||
auto filename = getFilename();
|
||||
bool result = true;
|
||||
// just kill the file and reload
|
||||
if(QFile::exists(filename))
|
||||
{
|
||||
result = QFile::remove(filename);
|
||||
}
|
||||
if(result)
|
||||
{
|
||||
// file gone...
|
||||
m_file.reset();
|
||||
|
||||
// check local cache for metadata...
|
||||
auto version = ENV.metadataIndex()->get(m_uid, m_version);
|
||||
if(version->isLoaded())
|
||||
{
|
||||
m_metaVersion = version;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
emit dataChanged();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* deep inspecting compare for requirement sets
|
||||
* By default, only uids are compared for set operations.
|
||||
* This compares all fields of the Require structs in the sets.
|
||||
*/
|
||||
static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
|
||||
{
|
||||
// NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
|
||||
if(a.size() != b.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(const auto & reqA :a)
|
||||
{
|
||||
const auto &iter2 = b.find(reqA);
|
||||
if(iter2 == b.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto & reqB = *iter2;
|
||||
if(!reqA.deepEquals(reqB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Component::updateCachedData()
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
bool changed = false;
|
||||
if(m_cachedName != file->name)
|
||||
{
|
||||
m_cachedName = file->name;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVersion != file->version)
|
||||
{
|
||||
m_cachedVersion = file->version;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVolatile != file->m_volatile)
|
||||
{
|
||||
m_cachedVolatile = file->m_volatile;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedRequires, file->requires))
|
||||
{
|
||||
m_cachedRequires = file->requires;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||
{
|
||||
m_cachedConflicts = file->conflicts;
|
||||
changed = true;
|
||||
}
|
||||
if(changed)
|
||||
{
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// in case we removed all the metadata
|
||||
m_cachedRequires.clear();
|
||||
m_cachedConflicts.clear();
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
104
api/logic/minecraft/Component.h
Normal file
104
api/logic/minecraft/Component.h
Normal file
@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QList>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include "meta/JsonFormat.h"
|
||||
#include "ProblemProvider.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ComponentList;
|
||||
class LaunchProfile;
|
||||
namespace Meta
|
||||
{
|
||||
class Version;
|
||||
class VersionList;
|
||||
}
|
||||
class VersionFile;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Component(ComponentList * parent, const QString &uid);
|
||||
|
||||
// DEPRECATED: remove these constructors?
|
||||
Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
|
||||
Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
|
||||
|
||||
virtual ~Component(){};
|
||||
void applyTo(LaunchProfile *profile);
|
||||
|
||||
bool isMoveable();
|
||||
bool isCustomizable();
|
||||
bool isRevertible();
|
||||
bool isRemovable();
|
||||
bool isCustom();
|
||||
bool isVersionChangeable();
|
||||
|
||||
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
|
||||
void setOrder(int order);
|
||||
int getOrder();
|
||||
|
||||
QString getID();
|
||||
QString getName();
|
||||
QString getVersion();
|
||||
std::shared_ptr<Meta::Version> getMeta();
|
||||
QDateTime getReleaseDateTime();
|
||||
|
||||
QString getFilename();
|
||||
|
||||
std::shared_ptr<class VersionFile> getVersionFile() const;
|
||||
std::shared_ptr<class Meta::VersionList> getVersionList() const;
|
||||
|
||||
void setImportant (bool state);
|
||||
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
|
||||
void setVersion(const QString & version);
|
||||
bool customize();
|
||||
bool revert();
|
||||
|
||||
void updateCachedData();
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
||||
public: /* data */
|
||||
ComponentList * m_parent;
|
||||
|
||||
// BEGIN: persistent component list properties
|
||||
/// ID of the component
|
||||
QString m_uid;
|
||||
/// version of the component - when there's a custom json override, this is also the version the component reverts to
|
||||
QString m_version;
|
||||
/// if true, this has been added automatically to satisfy dependencies and may be automatically removed
|
||||
bool m_dependencyOnly = false;
|
||||
/// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
|
||||
bool m_important = false;
|
||||
|
||||
/// cached name for display purposes, taken from the version file (meta or local override)
|
||||
QString m_cachedName;
|
||||
/// cached version for display AND other purposes, taken from the version file (meta or local override)
|
||||
QString m_cachedVersion;
|
||||
/// cached set of requirements, taken from the version file (meta or local override)
|
||||
Meta::RequireSet m_cachedRequires;
|
||||
Meta::RequireSet m_cachedConflicts;
|
||||
/// if true, the component is volatile and may be automatically removed when no longer needed
|
||||
bool m_cachedVolatile = false;
|
||||
// END: persistent component list properties
|
||||
|
||||
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
|
||||
bool m_orderOverride = false;
|
||||
int m_order = 0;
|
||||
|
||||
// load state
|
||||
std::shared_ptr<Meta::Version> m_metaVersion;
|
||||
std::shared_ptr<VersionFile> m_file;
|
||||
bool m_loaded = false;
|
||||
};
|
||||
|
||||
typedef shared_qobject_ptr<Component> ComponentPtr;
|
File diff suppressed because it is too large
Load Diff
@ -23,19 +23,21 @@
|
||||
|
||||
#include "Library.h"
|
||||
#include "LaunchProfile.h"
|
||||
#include "ProfilePatch.h"
|
||||
#include "Component.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
|
||||
struct ComponentListData;
|
||||
class ComponentUpdateTask;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
friend ComponentUpdateTask;
|
||||
public:
|
||||
explicit ComponentList(MinecraftInstance * instance);
|
||||
virtual ~ComponentList();
|
||||
@ -46,6 +48,9 @@ public:
|
||||
virtual int columnCount(const QModelIndex &parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
/// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
|
||||
void buildingFromScratch();
|
||||
|
||||
/// is this version unchanged by the user?
|
||||
bool isVanilla();
|
||||
|
||||
@ -58,68 +63,76 @@ public:
|
||||
/// install a jar/zip as a replacement for the main jar
|
||||
void installCustomJar(QString selectedFile);
|
||||
|
||||
/// DEPRECATED, remove ASAP
|
||||
int getFreeOrderNumber();
|
||||
|
||||
enum MoveDirection { MoveUp, MoveDown };
|
||||
/// move patch file # up or down the list
|
||||
/// move component file # up or down the list
|
||||
void move(const int index, const MoveDirection direction);
|
||||
|
||||
/// remove patch file # - including files/records
|
||||
/// remove component file # - including files/records
|
||||
bool remove(const int index);
|
||||
|
||||
/// remove patch file by id - including files/records
|
||||
/// remove component file by id - including files/records
|
||||
bool remove(const QString id);
|
||||
|
||||
bool customize(int index);
|
||||
|
||||
bool revertToBase(int index);
|
||||
|
||||
void resetOrder();
|
||||
/// reload the list, reload all components, resolve dependencies
|
||||
void reload(Net::Mode netmode);
|
||||
|
||||
/// reload all profile patches from storage, clear the profile and apply the patches
|
||||
void reload();
|
||||
// reload all components, resolve dependencies
|
||||
void resolve(Net::Mode netmode);
|
||||
|
||||
/// apply the patches. Catches all the errors and returns true/false for success/failure
|
||||
bool reapplyPatches();
|
||||
/// get current running task...
|
||||
shared_qobject_ptr<Task> getCurrentTask();
|
||||
|
||||
std::shared_ptr<LaunchProfile> getProfile() const;
|
||||
void clearProfile();
|
||||
|
||||
// NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
|
||||
void setOldConfigVersion(const QString &uid, const QString &version);
|
||||
|
||||
QString getComponentVersion(const QString &uid) const;
|
||||
|
||||
bool setComponentVersion(const QString &uid, const QString &version, bool important = false);
|
||||
|
||||
QString patchFilePathForUid(const QString &uid) const;
|
||||
public:
|
||||
/// get the profile patch by id
|
||||
ProfilePatchPtr versionPatch(const QString &id);
|
||||
/// get the profile component by id
|
||||
ComponentPtr getComponent(const QString &id);
|
||||
|
||||
/// get the profile patch by index
|
||||
ProfilePatchPtr versionPatch(int index);
|
||||
|
||||
/// save the current patch order
|
||||
void saveCurrentOrder() const;
|
||||
|
||||
/// Remove all the patches
|
||||
void clearPatches();
|
||||
|
||||
/// Add the patch object to the internal list of patches
|
||||
void appendPatch(ProfilePatchPtr patch);
|
||||
/// get the profile component by index
|
||||
ComponentPtr getComponent(int index);
|
||||
|
||||
private:
|
||||
void load_internal();
|
||||
bool resetOrder_internal();
|
||||
bool saveOrder_internal(ProfileUtils::PatchOrder order) const;
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
|
||||
/// apply the component patches. Catches all the errors and returns true/false for success/failure
|
||||
void invalidateLaunchProfile();
|
||||
|
||||
/// Add the component to the internal list of patches
|
||||
void appendComponent(ComponentPtr component);
|
||||
/// insert component so that its index is ideally the specified one (returns real index)
|
||||
void insertComponent(size_t index, ComponentPtr component);
|
||||
|
||||
QString componentsFilePath() const;
|
||||
QString patchesPattern() const;
|
||||
|
||||
private slots:
|
||||
void save();
|
||||
void updateSucceeded();
|
||||
void updateFailed(const QString & error);
|
||||
void componentDataChanged();
|
||||
|
||||
private:
|
||||
bool load();
|
||||
bool installJarMods_internal(QStringList filepaths);
|
||||
bool installCustomJar_internal(QString filepath);
|
||||
bool removePatch_internal(ProfilePatchPtr patch);
|
||||
bool customizePatch_internal(ProfilePatchPtr patch);
|
||||
bool revertPatch_internal(ProfilePatchPtr patch);
|
||||
void loadDefaultBuiltinPatches_internal();
|
||||
void loadUserPatches_internal();
|
||||
void upgradeDeprecatedFiles_internal();
|
||||
bool removeComponent_internal(ComponentPtr patch);
|
||||
|
||||
bool migratePreComponentConfig();
|
||||
|
||||
private: /* data */
|
||||
/// list of attached profile patches
|
||||
QList<ProfilePatchPtr> m_patches;
|
||||
|
||||
// the instance this belongs to
|
||||
MinecraftInstance *m_instance;
|
||||
|
||||
std::shared_ptr<LaunchProfile> m_profile;
|
||||
std::unique_ptr<ComponentListData> d;
|
||||
};
|
||||
|
42
api/logic/minecraft/ComponentList_p.h
Normal file
42
api/logic/minecraft/ComponentList_p.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "Component.h"
|
||||
#include <map>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
class MinecraftInstance;
|
||||
using ComponentContainer = QList<ComponentPtr>;
|
||||
using ComponentIndex = QMap<QString, ComponentPtr>;
|
||||
using ConnectionList = QList<QMetaObject::Connection>;
|
||||
|
||||
struct ComponentListData
|
||||
{
|
||||
// the instance this belongs to
|
||||
MinecraftInstance *m_instance;
|
||||
|
||||
// the launch profile (volatile, temporary thing created on demand)
|
||||
std::shared_ptr<LaunchProfile> m_profile;
|
||||
|
||||
// version information migrated from instance.cfg file. Single use on migration!
|
||||
std::map<QString, QString> m_oldConfigVersions;
|
||||
QString getOldConfigVersion(const QString& uid) const
|
||||
{
|
||||
const auto iter = m_oldConfigVersions.find(uid);
|
||||
if(iter != m_oldConfigVersions.cend())
|
||||
{
|
||||
return (*iter).second;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// persistent list of components and related machinery
|
||||
ComponentContainer components;
|
||||
ComponentIndex componentIndex;
|
||||
bool dirty = false;
|
||||
QTimer m_saveTimer;
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
688
api/logic/minecraft/ComponentUpdateTask.cpp
Normal file
688
api/logic/minecraft/ComponentUpdateTask.cpp
Normal file
@ -0,0 +1,688 @@
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "ComponentList_p.h"
|
||||
#include "ComponentList.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 ComponentList.
|
||||
* See: https://en.wikipedia.org/wiki/Reactor_pattern
|
||||
*/
|
||||
|
||||
/*
|
||||
* Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change?
|
||||
* If the component list changes, start over.
|
||||
*/
|
||||
|
||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* 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;
|
||||
}
|
||||
|
||||
static LoadResult loadComponentList(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 = loadComponentList(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.componentListIndex = 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->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 ComponentList internals. It shouldn't! It needs richer data types than ComponentList 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";
|
||||
}
|
||||
}
|
||||
// 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.componentListIndex);
|
||||
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();
|
||||
}
|
||||
}
|
37
api/logic/minecraft/ComponentUpdateTask.h
Normal file
37
api/logic/minecraft/ComponentUpdateTask.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include <memory>
|
||||
class ComponentList;
|
||||
struct ComponentUpdateTaskData;
|
||||
|
||||
class ComponentUpdateTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Launch,
|
||||
Resolution
|
||||
};
|
||||
|
||||
public:
|
||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
|
||||
virtual ~ComponentUpdateTask();
|
||||
|
||||
protected:
|
||||
void executeTask();
|
||||
|
||||
private:
|
||||
void loadComponents();
|
||||
void resolveDependencies(bool checkOnly);
|
||||
|
||||
void remoteLoadSucceeded(size_t index);
|
||||
void remoteLoadFailed(size_t index, const QString &msg);
|
||||
void checkIfAllFinished();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ComponentUpdateTaskData> d;
|
||||
};
|
32
api/logic/minecraft/ComponentUpdateTask_p.h
Normal file
32
api/logic/minecraft/ComponentUpdateTask_p.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include "net/Mode.h"
|
||||
|
||||
class ComponentList;
|
||||
|
||||
struct RemoteLoadStatus
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
Index,
|
||||
List,
|
||||
Version
|
||||
} type = Type::Version;
|
||||
size_t componentListIndex = 0;
|
||||
bool finished = false;
|
||||
bool succeeded = false;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct ComponentUpdateTaskData
|
||||
{
|
||||
ComponentList * m_list = nullptr;
|
||||
QList<RemoteLoadStatus> remoteLoadStatusList;
|
||||
bool remoteLoadSuccessful = true;
|
||||
size_t remoteTasksInProgress = 0;
|
||||
ComponentUpdateTask::Mode mode;
|
||||
Net::Mode netmode;
|
||||
};
|
@ -34,6 +34,7 @@
|
||||
#include "ComponentList.h"
|
||||
#include "AssetsUtils.h"
|
||||
#include "MinecraftUpdate.h"
|
||||
#include "MinecraftLoadAndCheck.h"
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
@ -63,12 +64,6 @@ private:
|
||||
MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
: BaseInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
// FIXME: remove these
|
||||
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
|
||||
m_settings->registerSetting("LWJGLVersion", "2.9.1");
|
||||
m_settings->registerSetting("ForgeVersion", "");
|
||||
m_settings->registerSetting("LiteloaderVersion", "");
|
||||
|
||||
// Java Settings
|
||||
auto javaOverride = m_settings->registerSetting("OverrideJava", false);
|
||||
auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false);
|
||||
@ -101,11 +96,23 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
|
||||
// Minecraft launch method
|
||||
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
|
||||
|
||||
// DEPRECATED: Read what versions the user configuration thinks should be used
|
||||
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
|
||||
m_settings->registerSetting("LWJGLVersion", "");
|
||||
m_settings->registerSetting("ForgeVersion", "");
|
||||
m_settings->registerSetting("LiteloaderVersion", "");
|
||||
|
||||
m_components.reset(new ComponentList(this));
|
||||
m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
|
||||
auto setting = m_settings->getSetting("LWJGLVersion");
|
||||
m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
|
||||
m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
|
||||
m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
|
||||
}
|
||||
|
||||
void MinecraftInstance::init()
|
||||
{
|
||||
createProfile();
|
||||
}
|
||||
|
||||
QString MinecraftInstance::typeName() const
|
||||
@ -113,41 +120,6 @@ QString MinecraftInstance::typeName() const
|
||||
return "Minecraft";
|
||||
}
|
||||
|
||||
|
||||
bool MinecraftInstance::reload()
|
||||
{
|
||||
if (BaseInstance::reload())
|
||||
{
|
||||
try
|
||||
{
|
||||
reloadProfile();
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MinecraftInstance::createProfile()
|
||||
{
|
||||
m_components.reset(new ComponentList(this));
|
||||
}
|
||||
|
||||
void MinecraftInstance::reloadProfile()
|
||||
{
|
||||
m_components->reload();
|
||||
emit versionReloaded();
|
||||
}
|
||||
|
||||
void MinecraftInstance::clearProfile()
|
||||
{
|
||||
m_components->clearProfile();
|
||||
emit versionReloaded();
|
||||
}
|
||||
|
||||
std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const
|
||||
{
|
||||
return m_components;
|
||||
@ -771,7 +743,7 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
}
|
||||
|
||||
QString description;
|
||||
description.append(tr("Minecraft %1 (%2)").arg(getComponentVersion("net.minecraft")).arg(typeName()));
|
||||
description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
|
||||
if(totalTimePlayed() > 0)
|
||||
{
|
||||
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
|
||||
@ -783,9 +755,20 @@ QString MinecraftInstance::getStatusbarDescription()
|
||||
return description;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask()
|
||||
shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||
{
|
||||
return shared_qobject_ptr<Task>(new OneSixUpdate(this));
|
||||
switch (mode)
|
||||
{
|
||||
case Net::Mode::Offline:
|
||||
{
|
||||
return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this));
|
||||
}
|
||||
case Net::Mode::Online:
|
||||
{
|
||||
return shared_qobject_ptr<Task>(new OneSixUpdate(this));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
@ -827,11 +810,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
if(session->status != AuthSession::PlayableOffline)
|
||||
{
|
||||
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
|
||||
process->appendStep(std::make_shared<Update>(pptr));
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
|
||||
}
|
||||
else
|
||||
{
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
|
||||
}
|
||||
|
||||
// if there are any jar mods
|
||||
if(getJarMods().size())
|
||||
{
|
||||
auto step = std::make_shared<ModMinecraftJar>(pptr);
|
||||
process->appendStep(step);
|
||||
@ -900,53 +886,6 @@ JavaVersion MinecraftInstance::getJavaVersion() const
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
}
|
||||
|
||||
bool MinecraftInstance::setComponentVersion(const QString& uid, const QString& version)
|
||||
{
|
||||
if(uid == "net.minecraft")
|
||||
{
|
||||
settings()->set("IntendedVersion", version);
|
||||
}
|
||||
else if (uid == "org.lwjgl")
|
||||
{
|
||||
settings()->set("LWJGLVersion", version);
|
||||
}
|
||||
else if (uid == "net.minecraftforge")
|
||||
{
|
||||
settings()->set("ForgeVersion", version);
|
||||
}
|
||||
else if (uid == "com.mumfrey.liteloader")
|
||||
{
|
||||
settings()->set("LiteloaderVersion", version);
|
||||
}
|
||||
if(getComponentList())
|
||||
{
|
||||
clearProfile();
|
||||
}
|
||||
emit propertiesChanged(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getComponentVersion(const QString& uid) const
|
||||
{
|
||||
if(uid == "net.minecraft")
|
||||
{
|
||||
return settings()->get("IntendedVersion").toString();
|
||||
}
|
||||
else if(uid == "org.lwjgl")
|
||||
{
|
||||
return settings()->get("LWJGLVersion").toString();
|
||||
}
|
||||
else if(uid == "net.minecraftforge")
|
||||
{
|
||||
return settings()->get("ForgeVersion").toString();
|
||||
}
|
||||
else if(uid == "com.mumfrey.liteloader")
|
||||
{
|
||||
return settings()->get("LiteloaderVersion").toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> MinecraftInstance::loaderModList() const
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
|
@ -53,12 +53,7 @@ public:
|
||||
|
||||
|
||||
////// Profile management //////
|
||||
void createProfile();
|
||||
std::shared_ptr<ComponentList> getComponentList() const;
|
||||
void reloadProfile();
|
||||
void clearProfile();
|
||||
bool reload() override;
|
||||
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModList> loaderModList() const;
|
||||
@ -69,7 +64,7 @@ public:
|
||||
|
||||
|
||||
////// Launch stuff //////
|
||||
shared_qobject_ptr<Task> createUpdateTask() override;
|
||||
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
QStringList extraArguments() const override;
|
||||
QStringList verboseDescription(AuthSessionPtr session) override;
|
||||
@ -105,11 +100,6 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
// FIXME: remove
|
||||
QString getComponentVersion(const QString &uid) const;
|
||||
// FIXME: remove
|
||||
bool setComponentVersion(const QString &uid, const QString &version);
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
|
45
api/logic/minecraft/MinecraftLoadAndCheck.cpp
Normal file
45
api/logic/minecraft/MinecraftLoadAndCheck.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "MinecraftLoadAndCheck.h"
|
||||
#include "MinecraftInstance.h"
|
||||
#include "ComponentList.h"
|
||||
|
||||
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
|
||||
void MinecraftLoadAndCheck::executeTask()
|
||||
{
|
||||
// add offline metadata load task
|
||||
auto components = m_inst->getComponentList();
|
||||
components->reload(Net::Mode::Offline);
|
||||
m_task = components->getCurrentTask();
|
||||
|
||||
if(!m_task)
|
||||
{
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
|
||||
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
||||
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
||||
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
||||
}
|
||||
|
||||
void MinecraftLoadAndCheck::subtaskSucceeded()
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void MinecraftLoadAndCheck::subtaskFailed(QString error)
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
return;
|
||||
}
|
||||
emitFailed(error);
|
||||
}
|
47
api/logic/minecraft/MinecraftLoadAndCheck.h
Normal file
47
api/logic/minecraft/MinecraftLoadAndCheck.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include <quazip.h>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class MinecraftVersion;
|
||||
class MinecraftInstance;
|
||||
|
||||
class MinecraftLoadAndCheck : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
|
||||
void executeTask() override;
|
||||
|
||||
private slots:
|
||||
void subtaskSucceeded();
|
||||
void subtaskFailed(QString error);
|
||||
|
||||
private:
|
||||
MinecraftInstance *m_inst = nullptr;
|
||||
shared_qobject_ptr<Task> m_task;
|
||||
QString m_preFailure;
|
||||
QString m_fail_reason;
|
||||
};
|
||||
|
@ -39,40 +39,24 @@
|
||||
|
||||
OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
|
||||
void OneSixUpdate::executeTask()
|
||||
{
|
||||
m_tasks.clear();
|
||||
// create folders
|
||||
{
|
||||
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
|
||||
}
|
||||
|
||||
// add metadata update tasks, if necessary
|
||||
// add metadata update task if necessary
|
||||
{
|
||||
/*
|
||||
* FIXME: there are some corner cases here that remain unhandled:
|
||||
* what if local load succeeds but remote fails? The version is still usable...
|
||||
* We should not rely on the remote to be there... and prefer local files if it does not respond.
|
||||
*/
|
||||
qDebug() << "Updating patches...";
|
||||
auto profile = m_inst->getComponentList();
|
||||
m_inst->reloadProfile();
|
||||
for(int i = 0; i < profile->rowCount(); i++)
|
||||
auto components = m_inst->getComponentList();
|
||||
components->reload(Net::Mode::Online);
|
||||
auto task = components->getCurrentTask();
|
||||
if(task)
|
||||
{
|
||||
auto patch = profile->versionPatch(i);
|
||||
auto id = patch->getID();
|
||||
auto metadata = patch->getMeta();
|
||||
if(metadata)
|
||||
{
|
||||
metadata->load();
|
||||
auto task = metadata->getCurrentTask();
|
||||
if(task)
|
||||
{
|
||||
qDebug() << "Loading remote meta patch" << id;
|
||||
m_tasks.append(task.unwrap());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Ignoring local patch" << id;
|
||||
}
|
||||
m_tasks.append(task.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,10 +74,7 @@ OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(pare
|
||||
{
|
||||
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
|
||||
}
|
||||
}
|
||||
|
||||
void OneSixUpdate::executeTask()
|
||||
{
|
||||
if(!m_preFailure.isEmpty())
|
||||
{
|
||||
emitFailed(m_preFailure);
|
||||
|
@ -192,6 +192,19 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
out->mainJar = lib;
|
||||
}
|
||||
|
||||
if (root.contains("requires"))
|
||||
{
|
||||
Meta::parseRequires(root, &out->requires);
|
||||
}
|
||||
if (root.contains("conflicts"))
|
||||
{
|
||||
Meta::parseRequires(root, &out->conflicts);
|
||||
}
|
||||
if (root.contains("volatile"))
|
||||
{
|
||||
out->m_volatile = requireBoolean(root, "volatile");
|
||||
}
|
||||
|
||||
/* removed features that shouldn't be used */
|
||||
if (root.contains("tweakers"))
|
||||
{
|
||||
@ -216,13 +229,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
return out;
|
||||
}
|
||||
|
||||
QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder)
|
||||
QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch)
|
||||
{
|
||||
QJsonObject root;
|
||||
if (saveOrder)
|
||||
{
|
||||
root.insert("order", patch->order);
|
||||
}
|
||||
writeString(root, "name", patch->name);
|
||||
|
||||
writeString(root, "uid", patch->uid);
|
||||
@ -266,6 +275,18 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
root.insert("mods", array);
|
||||
}
|
||||
if(!patch->requires.empty())
|
||||
{
|
||||
Meta::serializeRequires(root, &patch->requires, "requires");
|
||||
}
|
||||
if(!patch->conflicts.empty())
|
||||
{
|
||||
Meta::serializeRequires(root, &patch->conflicts, "conflicts");
|
||||
}
|
||||
if(patch->m_volatile)
|
||||
{
|
||||
root.insert("volatile", true);
|
||||
}
|
||||
// write the contents to a json document.
|
||||
{
|
||||
QJsonDocument out;
|
||||
|
@ -10,7 +10,7 @@ class OneSixVersionFormat
|
||||
public:
|
||||
// version files / profile patches
|
||||
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder);
|
||||
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
|
||||
|
||||
// libraries
|
||||
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
|
||||
|
@ -1,188 +0,0 @@
|
||||
#include <meta/VersionList.h>
|
||||
#include <meta/Index.h>
|
||||
#include <Env.h>
|
||||
#include "ProfilePatch.h"
|
||||
|
||||
#include "meta/Version.h"
|
||||
#include "VersionFile.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
|
||||
ProfilePatch::ProfilePatch(std::shared_ptr<Meta::Version> version)
|
||||
:m_metaVersion(version)
|
||||
{
|
||||
}
|
||||
|
||||
ProfilePatch::ProfilePatch(std::shared_ptr<VersionFile> file, const QString& filename)
|
||||
:m_file(file), m_filename(filename)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Meta::Version> ProfilePatch::getMeta()
|
||||
{
|
||||
return m_metaVersion;
|
||||
}
|
||||
|
||||
void ProfilePatch::applyTo(LaunchProfile* profile)
|
||||
{
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
vfile->applyTo(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile->applyProblemSeverity(getProblemSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class VersionFile> ProfilePatch::getVersionFile() const
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(!m_metaVersion->isLoaded())
|
||||
{
|
||||
m_metaVersion->load();
|
||||
}
|
||||
return m_metaVersion->data();
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class Meta::VersionList> ProfilePatch::getVersionList() const
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
return ENV.metadataIndex()->get(m_metaVersion->uid());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ProfilePatch::getOrder()
|
||||
{
|
||||
if(m_orderOverride)
|
||||
return m_order;
|
||||
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
return vfile->order;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void ProfilePatch::setOrder(int order)
|
||||
{
|
||||
m_orderOverride = true;
|
||||
m_order = order;
|
||||
}
|
||||
QString ProfilePatch::getID()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
return m_metaVersion->uid();
|
||||
return getVersionFile()->uid;
|
||||
}
|
||||
QString ProfilePatch::getName()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
return m_metaVersion->name();
|
||||
return getVersionFile()->name;
|
||||
}
|
||||
QString ProfilePatch::getVersion()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
return m_metaVersion->version();
|
||||
return getVersionFile()->version;
|
||||
}
|
||||
QString ProfilePatch::getFilename()
|
||||
{
|
||||
return m_filename;
|
||||
}
|
||||
QDateTime ProfilePatch::getReleaseDateTime()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
return m_metaVersion->time();
|
||||
}
|
||||
return getVersionFile()->releaseTime;
|
||||
}
|
||||
|
||||
bool ProfilePatch::isCustom()
|
||||
{
|
||||
return !m_isVanilla;
|
||||
};
|
||||
|
||||
bool ProfilePatch::isCustomizable()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(getVersionFile())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool ProfilePatch::isRemovable()
|
||||
{
|
||||
return m_isRemovable;
|
||||
}
|
||||
bool ProfilePatch::isRevertible()
|
||||
{
|
||||
return m_isRevertible;
|
||||
}
|
||||
bool ProfilePatch::isMoveable()
|
||||
{
|
||||
return m_isMovable;
|
||||
}
|
||||
bool ProfilePatch::isVersionChangeable()
|
||||
{
|
||||
auto list = getVersionList();
|
||||
if(list)
|
||||
{
|
||||
if(!list->isLoaded())
|
||||
{
|
||||
list->load();
|
||||
}
|
||||
return list->count() != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProfilePatch::setVanilla (bool state)
|
||||
{
|
||||
m_isVanilla = state;
|
||||
}
|
||||
void ProfilePatch::setRemovable (bool state)
|
||||
{
|
||||
m_isRemovable = state;
|
||||
}
|
||||
void ProfilePatch::setRevertible (bool state)
|
||||
{
|
||||
m_isRevertible = state;
|
||||
}
|
||||
void ProfilePatch::setMovable (bool state)
|
||||
{
|
||||
m_isMovable = state;
|
||||
}
|
||||
|
||||
ProblemSeverity ProfilePatch::getProblemSeverity() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblemSeverity();
|
||||
}
|
||||
return ProblemSeverity::Error;
|
||||
}
|
||||
|
||||
const QList<PatchProblem> ProfilePatch::getProblems() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblems();
|
||||
}
|
||||
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QList>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include "ProblemProvider.h"
|
||||
|
||||
class ComponentList;
|
||||
class LaunchProfile;
|
||||
namespace Meta
|
||||
{
|
||||
class Version;
|
||||
class VersionList;
|
||||
}
|
||||
class VersionFile;
|
||||
|
||||
class ProfilePatch : public ProblemProvider
|
||||
{
|
||||
public:
|
||||
ProfilePatch(std::shared_ptr<Meta::Version> version);
|
||||
ProfilePatch(std::shared_ptr<VersionFile> file, const QString &filename = QString());
|
||||
|
||||
virtual ~ProfilePatch(){};
|
||||
virtual void applyTo(LaunchProfile *profile);
|
||||
|
||||
virtual bool isMoveable();
|
||||
virtual bool isCustomizable();
|
||||
virtual bool isRevertible();
|
||||
virtual bool isRemovable();
|
||||
virtual bool isCustom();
|
||||
virtual bool isVersionChangeable();
|
||||
|
||||
virtual void setOrder(int order);
|
||||
virtual int getOrder();
|
||||
|
||||
virtual QString getID();
|
||||
virtual QString getName();
|
||||
virtual QString getVersion();
|
||||
virtual std::shared_ptr<Meta::Version> getMeta();
|
||||
virtual QDateTime getReleaseDateTime();
|
||||
|
||||
virtual QString getFilename();
|
||||
|
||||
virtual std::shared_ptr<class VersionFile> getVersionFile() const;
|
||||
virtual std::shared_ptr<class Meta::VersionList> getVersionList() const;
|
||||
|
||||
void setVanilla (bool state);
|
||||
void setRemovable (bool state);
|
||||
void setRevertible (bool state);
|
||||
void setMovable (bool state);
|
||||
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
|
||||
protected:
|
||||
// Properties for UI and version manipulation from UI in general
|
||||
bool m_isMovable = false;
|
||||
bool m_isRevertible = false;
|
||||
bool m_isRemovable = false;
|
||||
bool m_isVanilla = false;
|
||||
|
||||
bool m_orderOverride = false;
|
||||
int m_order = 0;
|
||||
|
||||
std::shared_ptr<Meta::Version> m_metaVersion;
|
||||
std::shared_ptr<VersionFile> m_file;
|
||||
QString m_filename;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr;
|
@ -14,38 +14,6 @@ namespace ProfileUtils
|
||||
|
||||
static const int currentOrderFileVersion = 1;
|
||||
|
||||
bool writeOverrideOrders(QString path, const PatchOrder &order)
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj.insert("version", currentOrderFileVersion);
|
||||
QJsonArray orderArray;
|
||||
for(auto str: order)
|
||||
{
|
||||
orderArray.append(str);
|
||||
}
|
||||
obj.insert("order", orderArray);
|
||||
QSaveFile orderFile(path);
|
||||
if (!orderFile.open(QFile::WriteOnly))
|
||||
{
|
||||
qCritical() << "Couldn't open" << orderFile.fileName()
|
||||
<< "for writing:" << orderFile.errorString();
|
||||
return false;
|
||||
}
|
||||
auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
|
||||
if(orderFile.write(data) != data.size())
|
||||
{
|
||||
qCritical() << "Couldn't write all the data into" << orderFile.fileName()
|
||||
<< "because:" << orderFile.errorString();
|
||||
return false;
|
||||
}
|
||||
if(!orderFile.commit())
|
||||
{
|
||||
qCritical() << "Couldn't save" << orderFile.fileName()
|
||||
<< "because:" << orderFile.errorString();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readOverrideOrders(QString path, PatchOrder &order)
|
||||
{
|
||||
QFile orderFile(path);
|
||||
@ -154,6 +122,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
|
||||
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
|
||||
}
|
||||
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString & filename)
|
||||
{
|
||||
auto data = doc.toJson();
|
||||
QSaveFile jsonFile(filename);
|
||||
if(!jsonFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
jsonFile.cancelWriting();
|
||||
qWarning() << "Couldn't open" << filename << "for writing";
|
||||
return false;
|
||||
}
|
||||
jsonFile.write(data);
|
||||
if(!jsonFile.commit())
|
||||
{
|
||||
qWarning() << "Couldn't save" << filename;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo)
|
||||
{
|
||||
QFile file(fileInfo.absoluteFilePath());
|
||||
|
@ -16,6 +16,9 @@ bool writeOverrideOrders(QString path, const PatchOrder &order);
|
||||
/// Parse a version file in JSON format
|
||||
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder);
|
||||
|
||||
/// Save a JSON file (in any format)
|
||||
bool saveJsonFile(const QJsonDocument doc, const QString & filename);
|
||||
|
||||
/// Parse a version file in binary JSON format
|
||||
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo);
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "minecraft/Rule.h"
|
||||
#include "ProblemProvider.h"
|
||||
#include "Library.h"
|
||||
#include <meta/JsonFormat.h>
|
||||
|
||||
class ComponentList;
|
||||
class VersionFile;
|
||||
@ -29,9 +30,6 @@ public: /* data */
|
||||
/// MultiMC: order hint for this version file if no explicit order is set
|
||||
int order = 0;
|
||||
|
||||
/// MultiMC: filename of the file this was loaded from
|
||||
// QString filename;
|
||||
|
||||
/// MultiMC: human readable name of this package
|
||||
QString name;
|
||||
|
||||
@ -77,7 +75,7 @@ public: /* data */
|
||||
/// Mojang: list of libraries to add to the version
|
||||
QList<LibraryPtr> libraries;
|
||||
|
||||
// The main jar (Minecraft version library, normally)
|
||||
/// The main jar (Minecraft version library, normally)
|
||||
LibraryPtr mainJar;
|
||||
|
||||
/// MultiMC: list of attached traits of this version file - used to enable features
|
||||
@ -89,6 +87,21 @@ public: /* data */
|
||||
/// MultiMC: list of mods added to this version
|
||||
QList<LibraryPtr> mods;
|
||||
|
||||
/**
|
||||
* MultiMC: set of packages this depends on
|
||||
* NOTE: this is shared with the meta format!!!
|
||||
*/
|
||||
Meta::RequireSet requires;
|
||||
|
||||
/**
|
||||
* MultiMC: set of packages this conflicts with
|
||||
* NOTE: this is shared with the meta format!!!
|
||||
*/
|
||||
Meta::RequireSet conflicts;
|
||||
|
||||
/// is volatile -- may be removed as soon as it is no longer needed by something else
|
||||
bool m_volatile = false;
|
||||
|
||||
public:
|
||||
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
|
||||
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
|
||||
|
@ -25,6 +25,11 @@ void ModMinecraftJar::executeTask()
|
||||
{
|
||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
||||
|
||||
if(!m_inst->getJarMods().size())
|
||||
{
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
// nuke obsolete stripped jar(s) if needed
|
||||
if(!FS::ensureFolderPathExists(m_inst->binRoot()))
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ bool LegacyInstance::shouldUseCustomBaseJar() const
|
||||
}
|
||||
|
||||
|
||||
shared_qobject_ptr<Task> LegacyInstance::createUpdateTask()
|
||||
shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public:
|
||||
};
|
||||
|
||||
virtual bool shouldUpdate() const;
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask() override;
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||
|
||||
virtual QString typeName() const override;
|
||||
|
||||
|
@ -67,69 +67,70 @@ void LegacyUpgradeTask::copyFinished()
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
std::shared_ptr<MinecraftInstance> inst(new MinecraftInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
||||
inst->setName(m_newName);
|
||||
inst->init();
|
||||
|
||||
QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
|
||||
if(preferredVersionNumber.isNull())
|
||||
// NOTE: this scope ensures the instance is fully saved before we emitSucceeded
|
||||
{
|
||||
// try to decide version based on the jar(s?)
|
||||
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
|
||||
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
inst.setName(m_newName);
|
||||
inst.init();
|
||||
|
||||
QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
|
||||
if(preferredVersionNumber.isNull())
|
||||
{
|
||||
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
|
||||
// try to decide version based on the jar(s?)
|
||||
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
|
||||
if(preferredVersionNumber.isNull())
|
||||
{
|
||||
emitFailed(tr("Could not decide Minecraft version."));
|
||||
return;
|
||||
preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
|
||||
if(preferredVersionNumber.isNull())
|
||||
{
|
||||
emitFailed(tr("Could not decide Minecraft version."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inst->setComponentVersion("net.minecraft", preferredVersionNumber);
|
||||
auto components = inst.getComponentList();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
|
||||
|
||||
// BUG: reloadProfile should not be necessary, but setComponentVersion voids the profile created by init()!
|
||||
inst->reloadProfile();
|
||||
auto profile = inst->getComponentList();
|
||||
|
||||
if(legacyInst->shouldUseCustomBaseJar())
|
||||
{
|
||||
QString jarPath = legacyInst->customBaseJar();
|
||||
qDebug() << "Base jar is custom! : " << jarPath;
|
||||
// FIXME: handle case when the jar is unreadable?
|
||||
// TODO: check the hash, if it's the same as the upstream jar, do not do this
|
||||
profile->installCustomJar(jarPath);
|
||||
}
|
||||
|
||||
auto jarMods = legacyInst->getJarMods();
|
||||
for(auto & jarMod: jarMods)
|
||||
{
|
||||
QString modPath = jarMod.filename().absoluteFilePath();
|
||||
qDebug() << "jarMod: " << modPath;
|
||||
profile->installJarMods({modPath});
|
||||
}
|
||||
|
||||
// remove all the extra garbage we no longer need
|
||||
auto removeAll = [&](const QString &root, const QStringList &things)
|
||||
{
|
||||
for(auto &thing : things)
|
||||
if(legacyInst->shouldUseCustomBaseJar())
|
||||
{
|
||||
auto removePath = FS::PathCombine(root, thing);
|
||||
QFileInfo stat(removePath);
|
||||
if(stat.isDir())
|
||||
{
|
||||
FS::deletePath(removePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
QFile::remove(removePath);
|
||||
}
|
||||
QString jarPath = legacyInst->customBaseJar();
|
||||
qDebug() << "Base jar is custom! : " << jarPath;
|
||||
// FIXME: handle case when the jar is unreadable?
|
||||
// TODO: check the hash, if it's the same as the upstream jar, do not do this
|
||||
components->installCustomJar(jarPath);
|
||||
}
|
||||
};
|
||||
QStringList rootRemovables = {"modlist", "version", "instMods"};
|
||||
QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
|
||||
removeAll(inst->instanceRoot(), rootRemovables);
|
||||
removeAll(inst->minecraftRoot(), mcRemovables);
|
||||
|
||||
auto jarMods = legacyInst->getJarMods();
|
||||
for(auto & jarMod: jarMods)
|
||||
{
|
||||
QString modPath = jarMod.filename().absoluteFilePath();
|
||||
qDebug() << "jarMod: " << modPath;
|
||||
components->installJarMods({modPath});
|
||||
}
|
||||
|
||||
// remove all the extra garbage we no longer need
|
||||
auto removeAll = [&](const QString &root, const QStringList &things)
|
||||
{
|
||||
for(auto &thing : things)
|
||||
{
|
||||
auto removePath = FS::PathCombine(root, thing);
|
||||
QFileInfo stat(removePath);
|
||||
if(stat.isDir())
|
||||
{
|
||||
FS::deletePath(removePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
QFile::remove(removePath);
|
||||
}
|
||||
}
|
||||
};
|
||||
QStringList rootRemovables = {"modlist", "version", "instMods"};
|
||||
QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
|
||||
removeAll(inst.instanceRoot(), rootRemovables);
|
||||
removeAll(inst.minecraftRoot(), mcRemovables);
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ void FMLLibrariesTask::executeTask()
|
||||
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
|
||||
auto components = inst->getComponentList();
|
||||
auto profile = components->getProfile();
|
||||
bool forge_present = false;
|
||||
|
||||
if (!profile->hasTrait("legacyFML"))
|
||||
{
|
||||
@ -23,7 +22,7 @@ void FMLLibrariesTask::executeTask()
|
||||
return;
|
||||
}
|
||||
|
||||
QString version = inst->getComponentVersion("net.minecraft");
|
||||
QString version = components->getComponentVersion("net.minecraft");
|
||||
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
|
||||
if (!fmlLibsMapping.contains(version))
|
||||
{
|
||||
@ -35,9 +34,7 @@ void FMLLibrariesTask::executeTask()
|
||||
|
||||
// determine if we need some libs for FML or forge
|
||||
setStatus(tr("Checking for FML libraries..."));
|
||||
forge_present = (components->versionPatch("net.minecraftforge") != nullptr);
|
||||
// we don't...
|
||||
if (!forge_present)
|
||||
if(!components->getComponent("net.minecraftforge"))
|
||||
{
|
||||
emitSucceeded();
|
||||
return;
|
||||
|
@ -13,12 +13,6 @@ void LibrariesTask::executeTask()
|
||||
setStatus(tr("Getting the library files from Mojang..."));
|
||||
qDebug() << m_inst->name() << ": downloading libraries";
|
||||
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
|
||||
inst->reloadProfile();
|
||||
if(inst->hasVersionBroken())
|
||||
{
|
||||
emitFailed(tr("Failed to load the version description files - check the instance for errors."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a list of URLs that will need to be downloaded.
|
||||
auto components = inst->getComponentList();
|
||||
|
Reference in New Issue
Block a user