GH-2026 implement changes necessary to support 1.13 snapshots

This commit is contained in:
Petr Mrázek 2017-11-11 01:38:31 +01:00
parent 17c8f31a09
commit 85ae710d40
51 changed files with 2632 additions and 1058 deletions

View File

@ -197,7 +197,7 @@ bool BaseInstance::canLaunch() const
return (!hasVersionBroken() && !isRunning());
}
bool BaseInstance::reload()
bool BaseInstance::reloadSettings()
{
return m_settings->reload();
}

View File

@ -30,6 +30,8 @@
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "multimc_logic_export.h"
class QDir;
@ -148,7 +150,7 @@ public:
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
@ -224,7 +226,7 @@ public:
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
virtual bool reload();
bool reloadSettings();
/**
* 'print' a verbose desription of the instance into a QStringList

View File

@ -239,8 +239,14 @@ set(MINECRAFT_SOURCES
minecraft/MinecraftInstance.h
minecraft/LaunchProfile.cpp
minecraft/LaunchProfile.h
minecraft/Component.cpp
minecraft/Component.h
minecraft/ComponentList.cpp
minecraft/ComponentList.h
minecraft/ComponentUpdateTask.cpp
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftUpdate.h
minecraft/MinecraftUpdate.cpp
minecraft/MojangVersionFormat.cpp
@ -260,8 +266,6 @@ set(MINECRAFT_SOURCES
minecraft/MojangDownloadInfo.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
minecraft/ProfilePatch.cpp
minecraft/ProfilePatch.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h

View File

@ -5,6 +5,7 @@
//FIXME: remove this
#include "minecraft/MinecraftInstance.h"
#include "minecraft/ComponentList.h"
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version,
const QString& instName, const QString& instIcon, const QString& instGroup)
@ -25,11 +26,13 @@ void InstanceCreationTask::executeTask()
instanceSettings->suspendSave();
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
auto inst = new MinecraftInstance(m_globalSettings, instanceSettings, m_stagingPath);
inst->setComponentVersion("net.minecraft", m_version->descriptor());
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
inst->init();
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
auto components = inst.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
inst.setName(m_instName);
inst.setIconKey(m_instIcon);
inst.init();
instanceSettings->resumeSave();
}
emitSucceeded();

View File

@ -242,7 +242,9 @@ void InstanceImportTask::processFlame()
mcVersion.remove(QRegExp("[.]+$"));
qWarning() << "Mysterious trailing dots removed from Minecraft version while importing pack.";
}
instance.setComponentVersion("net.minecraft", mcVersion);
auto components = instance.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
{
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
@ -257,7 +259,7 @@ void InstanceImportTask::processFlame()
qWarning() << "Could not map recommended forge version for" << mcVersion;
}
}
instance.setComponentVersion("net.minecraftforge", forgeVersion);
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (m_instIcon != "default")
{

View File

@ -29,7 +29,7 @@ public:
{
return nullptr;
}
virtual shared_qobject_ptr< Task > createUpdateTask() override
virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
{
return nullptr;
}

View File

@ -1,5 +1,7 @@
#pragma once
#include "multimc_logic_export.h"
enum class ProblemSeverity
{
None,
@ -13,7 +15,7 @@ struct PatchProblem
QString m_description;
};
class ProblemProvider
class MULTIMC_LOGIC_EXPORT ProblemProvider
{
public:
virtual ~ProblemProvider() {};
@ -21,7 +23,7 @@ public:
virtual ProblemSeverity getProblemSeverity() const = 0;
};
class ProblemContainer : public ProblemProvider
class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
{
public:
const QList<PatchProblem> getProblems() const override

View File

@ -23,7 +23,7 @@ void Update::executeTask()
emitFailed(tr("Task aborted."));
return;
}
m_updateTask.reset(m_parent->instance()->createUpdateTask());
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));

View File

@ -19,13 +19,14 @@
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
#include <net/Mode.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep
{
Q_OBJECT
public:
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {};
virtual ~Update() {};
void executeTask() override;
@ -40,4 +41,5 @@ private slots:
private:
shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false;
Net::Mode m_mode = Net::Mode::Offline;
};

View File

@ -99,7 +99,7 @@ bool Meta::BaseEntity::loadLocalFile()
}
}
void Meta::BaseEntity::load()
void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if(!isLoaded())
@ -110,7 +110,7 @@ void Meta::BaseEntity::load()
}
}
// if we need remote update, run the update task
if(!shouldStartRemoteUpdate())
if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate())
{
return;
}

View File

@ -20,6 +20,7 @@
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
#include "net/Mode.h"
class Task;
namespace Meta
@ -54,7 +55,7 @@ public:
bool isLoaded() const;
bool shouldStartRemoteUpdate() const;
void load();
void load(Net::Mode loadType);
shared_qobject_ptr<Task> getCurrentTask();
protected: /* methods */

View File

@ -51,18 +51,11 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
version->setType(ensureString(obj, "type", QString()));
version->setParentUid(ensureString(obj, "parentUid", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
if(obj.contains("requires"))
{
QHash<QString, QString> requires;
auto reqobj = requireObject(obj, "requires");
auto iter = reqobj.begin();
while(iter != reqobj.end())
{
requires[iter.key()] = requireString(iter.value());
iter++;
}
version->setRequires(requires);
}
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
RequireSet requires, conflicts;
parseRequires(obj, &requires, "requires");
parseRequires(obj, &conflicts, "conflicts");
version->setRequires(requires, conflicts);
return version;
}
@ -145,4 +138,53 @@ void parseVersion(const QJsonObject &obj, Version *ptr)
throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
}
}
/*
[
{"uid":"foo", "equals":"version"}
]
*/
void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName)
{
if(obj.contains(keyName))
{
QSet<QString> requires;
auto reqArray = requireArray(obj, keyName);
auto iter = reqArray.begin();
while(iter != reqArray.end())
{
auto reqObject = requireObject(*iter);
auto uid = requireString(reqObject, "uid");
auto equals = ensureString(reqObject, "equals", QString());
auto suggests = ensureString(reqObject, "suggests", QString());
ptr->insert({uid, equals, suggests});
iter++;
}
}
}
void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName)
{
if(!ptr || ptr->empty())
{
return;
}
QJsonArray arrOut;
for(auto &iter: *ptr)
{
QJsonObject reqOut;
reqOut.insert("uid", iter.uid);
if(!iter.equalsVersion.isEmpty())
{
reqOut.insert("equals", iter.equalsVersion);
}
if(!iter.suggests.isEmpty())
{
reqOut.insert("suggests", iter.suggests);
}
arrOut.append(reqOut);
}
obj.insert(keyName, arrOut);
}
}

View File

@ -20,6 +20,7 @@
#include "Exception.h"
#include "meta/BaseEntity.h"
#include <set>
namespace Meta
{
@ -32,9 +33,41 @@ class ParseException : public Exception
public:
using Exception::Exception;
};
struct Require
{
bool operator==(const Require & rhs) const
{
return uid == rhs.uid;
}
bool operator<(const Require & rhs) const
{
return uid < rhs.uid;
}
bool deepEquals(const Require & rhs) const
{
return uid == rhs.uid
&& equalsVersion == rhs.equalsVersion
&& suggests == rhs.suggests;
}
QString uid;
QString equalsVersion;
QString suggests;
};
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
{
return qHash(key.uid, seed);
}
using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
void parseVersion(const QJsonObject &obj, Version *ptr);
void parseVersionList(const QJsonObject &obj, VersionList *ptr);
// FIXME: this has a different shape than the others...FIX IT!?
void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires");
void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires");
}
Q_DECLARE_METATYPE(std::set<Meta::Require>);

View File

@ -74,12 +74,20 @@ void Meta::Version::merge(const std::shared_ptr<BaseEntity> &other)
}
if (m_requires != version->m_requires)
{
setRequires(version->m_requires);
m_requires = version->m_requires;
}
if (m_conflicts != version->m_conflicts)
{
m_conflicts = version->m_conflicts;
}
if (m_parentUid != version->m_parentUid)
{
setParentUid(version->m_parentUid);
}
if(m_volatile != version->m_volatile)
{
setVolatile(version->m_volatile);
}
if(version->m_data)
{
setData(version->m_data);
@ -109,12 +117,19 @@ void Meta::Version::setTime(const qint64 time)
emit timeChanged();
}
void Meta::Version::setRequires(const QHash<QString, QString> &requires)
void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
{
m_requires = requires;
m_conflicts = conflicts;
emit requiresChanged();
}
void Meta::Version::setVolatile(bool volatile_)
{
m_volatile = volatile_;
}
void Meta::Version::setData(const VersionFilePtr &data)
{
m_data = data;

View File

@ -28,6 +28,8 @@
#include "multimc_logic_export.h"
#include "JsonFormat.h"
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
@ -65,7 +67,7 @@ public: /* con/des */
{
return m_time;
}
const QHash<QString, QString> &requires() const
const Meta::RequireSet &requires() const
{
return m_requires;
}
@ -77,6 +79,10 @@ public: /* con/des */
{
return m_recommended;
}
bool isLoaded() const
{
return m_data != nullptr;
}
void merge(const std::shared_ptr<BaseEntity> &other) override;
void parse(const QJsonObject &obj) override;
@ -87,7 +93,8 @@ public: // for usage by format parsers only
void setParentUid(const QString &parentUid);
void setType(const QString &type);
void setTime(const qint64 time);
void setRequires(const QHash<QString, QString> &requires);
void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
void setVolatile(bool volatile_);
void setRecommended(bool recommended);
void setProvidesRecommendations();
void setData(const VersionFilePtr &data);
@ -106,7 +113,9 @@ private:
QString m_version;
QString m_type;
qint64 m_time = 0;
QHash<QString, QString> m_requires;
Meta::RequireSet m_requires;
Meta::RequireSet m_conflicts;
bool m_volatile = false;
VersionFilePtr m_data;
};
}

View File

@ -30,7 +30,7 @@ VersionList::VersionList(const QString &uid, QObject *parent)
shared_qobject_ptr<Task> VersionList::getLoadTask()
{
load();
load(Net::Mode::Online);
return getCurrentTask();
}
@ -81,10 +81,13 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
return QVariant();
}
auto & reqs = version->requires();
auto iter = reqs.find(parentUid);
auto iter = std::find_if(reqs.begin(), reqs.end(), [&parentUid](const Require & req)
{
return req.uid == parentUid;
});
if (iter != reqs.end())
{
return iter.value();
return (*iter).equalsVersion;
}
}
case TypeRole: return version->type();
@ -159,6 +162,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions)
setupAddedVersion(i, m_versions.at(i));
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel();
@ -169,6 +173,22 @@ void VersionList::parse(const QJsonObject& obj)
parseVersionList(obj, this);
}
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
{
if(!a)
return b;
if(!b)
return a;
if(a->type() == b->type())
{
// newer of same type wins
return (a->rawTime() > b->rawTime() ? a : b);
}
// 'release' type wins
return (a->type() == "release" ? a : b);
}
void VersionList::merge(const BaseEntity::Ptr &other)
{
const VersionListPtr list = std::dynamic_pointer_cast<VersionList>(other);
@ -199,10 +219,7 @@ void VersionList::merge(const BaseEntity::Ptr &other)
// connect it.
setupAddedVersion(m_versions.size(), version);
m_versions.append(version);
if (!m_recommended || (version->type() == "release" && version->rawTime() > m_recommended->rawTime()))
{
m_recommended = version;
}
m_recommended = getBetterVersion(m_recommended, version);
}
endResetModel();
}

View 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();
}
}

View 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

View File

@ -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;
};

View 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;
};

View 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();
}
}

View 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;
};

View 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;
};

View File

@ -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)

View File

@ -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();

View 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);
}

View 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;
};

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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.")}};
}

View File

@ -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;

View File

@ -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());

View File

@ -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);

View File

@ -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;

View File

@ -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()))
{

View File

@ -71,7 +71,7 @@ bool LegacyInstance::shouldUseCustomBaseJar() const
}
shared_qobject_ptr<Task> LegacyInstance::createUpdateTask()
shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode)
{
return nullptr;
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();

10
api/logic/net/Mode.h Normal file
View File

@ -0,0 +1,10 @@
#pragma once
namespace Net
{
enum class Mode
{
Offline,
Online
};
}

View File

@ -192,7 +192,7 @@ void LaunchController::launchInstance()
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
if(!m_instance->reload())
if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));

View File

@ -1303,7 +1303,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
if (MMC->accounts()->anyAccountIsValid())
{
ProgressDialog loadDialog(this);
auto update = inst->createUpdateTask();
auto update = inst->createUpdateTask(Net::Mode::Online);
connect(update.get(), &Task::failed, [this](QString reason)
{
QString error = QString("Instance load failed: %1").arg(reason);

View File

@ -71,7 +71,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
}
else
{
vlist->load();
vlist->load(Net::Mode::Online);
auto task = vlist->getLoadTask();
if(vlist->isLoaded())
{

View File

@ -106,15 +106,15 @@ bool CoreModFolderPage::shouldDisplay() const
auto version = inst->getComponentList();
if (!version)
return true;
if(!version->versionPatch("net.minecraftforge"))
if(!version->getComponent("net.minecraftforge"))
{
return false;
}
if(!version->versionPatch("net.minecraft"))
if(!version->getComponent("net.minecraft"))
{
return false;
}
if(version->versionPatch("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
{
return true;
}

View File

@ -103,10 +103,10 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
m_profile = m_inst->getComponentList();
reloadComponentList();
m_profile = m_inst->getComponentList();
if (m_profile)
{
auto proxy = new IconProxy(ui->packageView);
@ -142,7 +142,7 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
return;
}
int row = current.row();
auto patch = m_profile->versionPatch(row);
auto patch = m_profile->getComponent(row);
auto severity = patch->getProblemSeverity();
switch(severity)
{
@ -196,7 +196,7 @@ bool VersionPage::reloadComponentList()
{
try
{
m_inst->reloadProfile();
m_profile->reload(Net::Mode::Online);
return true;
}
catch (Exception &e)
@ -262,19 +262,6 @@ void VersionPage::on_jarBtn_clicked()
updateButtons();
}
void VersionPage::on_resetOrderBtn_clicked()
{
try
{
m_profile->resetOrder();
}
catch (Exception &e)
{
QMessageBox::critical(this, tr("Error"), e.cause());
}
updateButtons();
}
void VersionPage::on_moveUpBtn_clicked()
{
try
@ -308,7 +295,7 @@ void VersionPage::on_changeVersionBtn_clicked()
{
return;
}
auto patch = m_profile->versionPatch(versionRow);
auto patch = m_profile->getComponent(versionRow);
auto name = patch->getName();
auto list = patch->getVersionList();
if(!list)
@ -331,8 +318,10 @@ void VersionPage::on_changeVersionBtn_clicked()
}
qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
bool important = false;
if(uid == "net.minecraft")
{
important = true;
if (!m_profile->isVanilla())
{
auto result = CustomMessageBox::selectable(
@ -348,14 +337,14 @@ void VersionPage::on_changeVersionBtn_clicked()
reloadComponentList();
}
}
m_inst->setComponentVersion(uid, vselect.selectedVersion()->descriptor());
m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
doUpdate();
m_container->refreshContainer();
}
int VersionPage::doUpdate()
{
auto updateTask = m_inst->createUpdateTask();
auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
if (!updateTask)
{
return 1;
@ -376,20 +365,58 @@ void VersionPage::on_forgeBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->getComponentVersion("net.minecraft"));
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_inst->getComponentVersion("net.minecraft"));
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
m_inst->setComponentVersion("net.minecraftforge", vsn->descriptor());
m_profile->reload();
m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor());
m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion();
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
}
}
// TODO: use something like this... except the final decision of what to show has to be deferred until the lists are known
/*
void VersionPage::on_liteloaderBtn_clicked()
{
QString uid = "com.mumfrey.liteloader";
auto vlist = ENV.metadataIndex()->get(uid);
if(!vlist)
{
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select %1 version").arg(vlist->name()), this);
auto parentUid = vlist->parentUid();
if(!parentUid.isEmpty())
{
auto parentvlist = ENV.metadataIndex()->get(parentUid);
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion(parentUid));
vselect.setEmptyString(
tr("No %1 versions are currently available for %2 %3")
.arg(vlist->name())
.arg(parentvlist->name())
.arg(m_profile->getComponentVersion(parentUid)));
}
else
{
vselect.setEmptyString(tr("No %1 versions are currently available"));
}
vselect.setEmptyErrorString(tr("Couldn't load or download the %1 version lists!").arg(vlist->name()));
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
m_profile->setComponentVersion(uid, vsn->descriptor());
m_profile->resolve();
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
}
}
*/
void VersionPage::on_liteloaderBtn_clicked()
{
auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader");
@ -398,14 +425,14 @@ void VersionPage::on_liteloaderBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this);
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->getComponentVersion("net.minecraft"));
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_inst->getComponentVersion("net.minecraft"));
vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
m_inst->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
m_profile->reload();
m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion(vselect.selectedVersion());
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
@ -441,7 +468,7 @@ void VersionPage::updateButtons(int row)
{
if(row == -1)
row = currentRow();
auto patch = m_profile->versionPatch(row);
auto patch = m_profile->getComponent(row);
if (!patch)
{
ui->removeBtn->setDisabled(true);
@ -470,14 +497,14 @@ void VersionPage::onGameUpdateError(QString error)
QMessageBox::Warning)->show();
}
ProfilePatchPtr VersionPage::current()
ComponentPtr VersionPage::current()
{
auto row = currentRow();
if(row < 0)
{
return nullptr;
}
return m_profile->versionPatch(row);
return m_profile->getComponent(row);
}
int VersionPage::currentRow()
@ -496,7 +523,7 @@ void VersionPage::on_customizeBtn_clicked()
{
return;
}
auto patch = m_profile->versionPatch(version);
auto patch = m_profile->getComponent(version);
if(!patch->getVersionFile())
{
// TODO: wait for the update task to finish here...

View File

@ -53,7 +53,6 @@ private slots:
void on_liteloaderBtn_clicked();
void on_reloadBtn_clicked();
void on_removeBtn_clicked();
void on_resetOrderBtn_clicked();
void on_moveUpBtn_clicked();
void on_moveDownBtn_clicked();
void on_jarmodBtn_clicked();
@ -68,7 +67,7 @@ private slots:
void on_changeVersionBtn_clicked();
private:
ProfilePatchPtr current();
ComponentPtr current();
int currentRow();
void updateButtons(int row = -1);
void preselect(int row = 0);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>693</width>
<height>788</height>
<height>833</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -217,16 +217,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetOrderBtn">
<property name="toolTip">
<string>Reset apply order of packages.</string>
</property>
<property name="text">
<string>Reset order</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reloadBtn">
<property name="toolTip">
@ -300,7 +290,6 @@
<tabstop>liteloaderBtn</tabstop>
<tabstop>modBtn</tabstop>
<tabstop>jarmodBtn</tabstop>
<tabstop>resetOrderBtn</tabstop>
<tabstop>reloadBtn</tabstop>
</tabstops>
<resources/>

View File

@ -38,10 +38,17 @@ static QString formatRequires(const VersionPtr &version)
auto iter = reqs.begin();
while (iter != reqs.end())
{
auto &uid = iter.key();
auto &version = iter.value();
auto &uid = iter->uid;
auto &version = iter->equalsVersion;
const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
lines.append(QString("%1 (%2)").arg(readable, version));
if(!version.isEmpty())
{
lines.append(QString("%1 (%2)").arg(readable, version));
}
else
{
lines.append(QString("%1").arg(readable));
}
iter++;
}
return lines.join('\n');
@ -95,7 +102,7 @@ QIcon PackagesPage::icon() const
void PackagesPage::on_refreshIndexBtn_clicked()
{
ENV.metadataIndex()->load();
ENV.metadataIndex()->load(Net::Mode::Online);
}
void PackagesPage::on_refreshFileBtn_clicked()
{
@ -104,7 +111,7 @@ void PackagesPage::on_refreshFileBtn_clicked()
{
return;
}
list->load();
list->load(Net::Mode::Online);
}
void PackagesPage::on_refreshVersionBtn_clicked()
{
@ -113,7 +120,7 @@ void PackagesPage::on_refreshVersionBtn_clicked()
{
return;
}
version->load();
version->load(Net::Mode::Online);
}
void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
@ -156,7 +163,7 @@ void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
ui->fileName->setText(list->name());
m_versionProxy->setSourceModel(list.get());
ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
list->load();
list->load(Net::Mode::Offline);
}
else
{
@ -213,5 +220,5 @@ void PackagesPage::updateVersion()
void PackagesPage::opened()
{
ENV.metadataIndex()->load();
ENV.metadataIndex()->load(Net::Mode::Offline);
}