442 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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();
 | |
|     }
 | |
| }
 |