MultiMC5/launcher/minecraft/Component.cpp

442 lines
9.1 KiB
C++

#include <meta/VersionList.h>
#include <meta/Index.h>
#include "Component.h"
#include <QSaveFile>
#include "meta/Version.h"
#include "VersionFile.h"
#include "minecraft/PackProfile.h"
#include "FileSystem.h"
#include "OneSixVersionFormat.h"
#include "Application.h"
#include <assert.h>
Component::Component(PackProfile * parent, const QString& uid)
{
assert(parent);
m_parent = parent;
m_uid = uid;
}
Component::Component(PackProfile * 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(PackProfile * 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)
{
// do not apply disabled components
if(!isEnabled())
{
return;
}
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(APPLICATION->metadataIndex()->hasUid(m_uid))
{
return APPLICATION->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::isEnabled()
{
return !canBeDisabled() || !m_disabled;
}
bool Component::canBeDisabled()
{
return isRemovable() && !m_dependencyOnly;
}
bool Component::setEnabled(bool state)
{
bool intendedDisabled = !state;
if (!canBeDisabled())
{
intendedDisabled = false;
}
if(intendedDisabled != m_disabled)
{
m_disabled = intendedDisabled;
emit dataChanged();
return true;
}
return false;
}
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(APPLICATION->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 = APPLICATION->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 (const 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 = APPLICATION->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();
}
}