diff options
Diffstat (limited to 'libmultimc')
-rw-r--r-- | libmultimc/CMakeLists.txt | 14 | ||||
-rw-r--r-- | libmultimc/include/instance.h | 26 | ||||
-rw-r--r-- | libmultimc/include/instanceloader.h | 66 | ||||
-rw-r--r-- | libmultimc/include/instancetypeinterface.h | 4 | ||||
-rw-r--r-- | libmultimc/include/instversion.h | 86 | ||||
-rw-r--r-- | libmultimc/include/instversionlist.h | 17 | ||||
-rw-r--r-- | libmultimc/include/minecraftversion.h | 95 | ||||
-rw-r--r-- | libmultimc/include/minecraftversionlist.h | 105 | ||||
-rw-r--r-- | libmultimc/src/instance.cpp | 34 | ||||
-rw-r--r-- | libmultimc/src/instancelist.cpp | 10 | ||||
-rw-r--r-- | libmultimc/src/instanceloader.cpp | 78 | ||||
-rw-r--r-- | libmultimc/src/instversion.cpp | 37 | ||||
-rw-r--r-- | libmultimc/src/minecraftversion.cpp | 142 | ||||
-rw-r--r-- | libmultimc/src/minecraftversionlist.cpp | 489 |
14 files changed, 1031 insertions, 172 deletions
diff --git a/libmultimc/CMakeLists.txt b/libmultimc/CMakeLists.txt index 06102d9f..4ffa3173 100644 --- a/libmultimc/CMakeLists.txt +++ b/libmultimc/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_AUTOMOC ON) # Find Qt find_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) +find_package(Qt5Xml REQUIRED) # Include Qt headers. include_directories(${Qt5Base_INCLUDE_DIRS}) @@ -28,10 +29,8 @@ include/instanceloader.h include/instversion.h include/instversionlist.h - -# Plugin Stuff -include/pluginmanager.h -include/instancetypeinterface.h +include/minecraftversion.h +include/minecraftversionlist.h # Tasks @@ -60,9 +59,8 @@ src/instanceloader.cpp src/instversion.cpp src/instversionlist.cpp - -# Plugin Stuff -src/pluginmanager.cpp +src/minecraftversion.cpp +src/minecraftversionlist.cpp # Tasks @@ -92,5 +90,5 @@ include_directories(${CMAKE_BINARY_DIR}/include) add_definitions(-DLIBMULTIMC_LIBRARY) add_library(libMultiMC SHARED ${LIBINST_SOURCES} ${LIBINST_HEADERS}) -qt5_use_modules(libMultiMC Core Network) +qt5_use_modules(libMultiMC Core Network Xml) target_link_libraries(libMultiMC libUtil libSettings) diff --git a/libmultimc/include/instance.h b/libmultimc/include/instance.h index 258a0dab..e72a0be3 100644 --- a/libmultimc/include/instance.h +++ b/libmultimc/include/instance.h @@ -103,6 +103,13 @@ class LIBMULTIMC_EXPORT Instance : public QObject */ Q_PROPERTY(qint64 lastLaunch READ lastLaunch WRITE setLastLaunch) + /*! + * Gets the last time that the current version was checked. + * This is checked against the last modified time on the jar file to see if + * the current version needs to be checked again. + */ + Q_PROPERTY(qint64 lastCurrentVersionUpdate READ lastCurrentVersionUpdate WRITE setLastCurrentVersionUpdate) + // Dirs @@ -225,6 +232,9 @@ public: emit propertiesChanged(this); } + virtual qint64 lastCurrentVersionUpdate() { return settings().get("lastVersionUpdate").value<qint64>(); } + virtual void setLastCurrentVersionUpdate(qint64 val) { settings().set("lastVersionUpdate", val); } + ////// Directories ////// QString minecraftDir() const; @@ -250,17 +260,7 @@ public: * \brief Gets a pointer to this instance's version list. * \return A pointer to the available version list for this instance. */ - virtual InstVersionList *versionList() const = 0; - - - - //////// INSTANCE TYPE STUFF //////// - - /*! - * \brief Returns a pointer to this instance's type. - * \return A pointer to this instance's type interface. - */ - virtual const InstanceTypeInterface *instanceType() const = 0; + virtual InstVersionList *versionList() const; //////// OTHER FUNCTIONS //////// @@ -274,7 +274,7 @@ public: * stored in the instance config file against the last modified time of Minecraft.jar. * \return True if updateCurrentVersion() should be called. */ - virtual bool shouldUpdateCurrentVersion() = 0; + virtual bool shouldUpdateCurrentVersion(); /*! * \brief Updates the current version. @@ -286,7 +286,7 @@ public: * instance is loaded if shouldUpdateCurrentVersion returns true. * \param keepCurrent If true, only the version timestamp will be updated. */ - virtual void updateCurrentVersion(bool keepCurrent = false) = 0; + virtual void updateCurrentVersion(bool keepCurrent = false); //// Settings System //// diff --git a/libmultimc/include/instanceloader.h b/libmultimc/include/instanceloader.h index 3326d7d0..fd6d04d6 100644 --- a/libmultimc/include/instanceloader.h +++ b/libmultimc/include/instanceloader.h @@ -22,15 +22,10 @@ #include "libmmc_config.h" -class InstanceTypeInterface; class Instance; -typedef QList<const InstanceTypeInterface *> InstTypeList; - /*! - * \brief The InstanceLoader is a singleton that manages all of the instance types and handles loading and creating instances. - * Instance types are registered with the instance loader through its registerInstType() function. - * Creating instances is done through the InstanceLoader's createInstance() function. This function takes + * The InstanceLoader is a singleton that manages loading and creating instances. */ class LIBMULTIMC_EXPORT InstanceLoader : public QObject { @@ -46,94 +41,45 @@ public: * * - NoError indicates that no error occurred. * - OtherError indicates that an unspecified error occurred. - * - TypeIDExists is returned by registerInstanceType() if the ID of the type being registered already exists. - * - TypeNotRegistered is returned by createInstance() and loadInstance() when the given type is not registered. * - InstExists is returned by createInstance() if the given instance directory is already an instance. * - NotAnInstance is returned by loadInstance() if the given instance directory is not a valid instance. - * - WrongInstType is returned by loadInstance() if the given instance directory's type doesn't match the given type. * - CantCreateDir is returned by createInstance( if the given instance directory can't be created.) */ - enum InstTypeError + enum InstLoaderError { NoError = 0, OtherError, - TypeIDExists, - - TypeNotRegistered, InstExists, NotAnInstance, - WrongInstType, CantCreateDir }; /*! - * \brief Registers the given InstanceType with the instance loader. - * - * \param type The InstanceType to register. - * \return An InstTypeError error code. - * - TypeIDExists if the given type's is already registered to another instance type. - */ - InstTypeError registerInstanceType(InstanceTypeInterface *type); - - /*! * \brief Creates an instance with the given type and stores it in inst. * * \param inst Pointer to store the created instance in. * \param type The type of instance to create. * \param instDir The instance's directory. - * \return An InstTypeError error code. - * - TypeNotRegistered if the given type is not registered with the InstanceLoader. + * \return An InstLoaderError error code. * - InstExists if the given instance directory is already an instance. * - CantCreateDir if the given instance directory cannot be created. */ - InstTypeError createInstance(Instance *&inst, const InstanceTypeInterface *type, const QString &instDir); - - /*! - * \brief Loads an instance from the given directory. - * - * \param inst Pointer to store the loaded instance in. - * \param type The type of instance to load. - * \param instDir The instance's directory. - * \return An InstTypeError error code. - * - TypeNotRegistered if the given type is not registered with the InstanceLoader. - * - NotAnInstance if the given instance directory isn't a valid instance. - * - WrongInstType if the given instance directory's type isn't the same as the given type. - */ - InstTypeError loadInstance(Instance *&inst, const InstanceTypeInterface *type, const QString &instDir); + InstLoaderError createInstance(Instance *&inst, const QString &instDir); /*! * \brief Loads an instance from the given directory. * Checks the instance's INI file to figure out what the instance's type is first. * \param inst Pointer to store the loaded instance in. * \param instDir The instance's directory. - * \return An InstTypeError error code. - * - TypeNotRegistered if the instance's type is not registered with the InstanceLoader. + * \return An InstLoaderError error code. * - NotAnInstance if the given instance directory isn't a valid instance. */ - InstTypeError loadInstance(Instance *&inst, const QString &instDir); - - /*! - * \brief Finds an instance type with the given ID. - * If one cannot be found, returns NULL. - * - * \param id The ID of the type to find. - * \return The type with the given ID. NULL if none were found. - */ - const InstanceTypeInterface *findType(const QString &id); - - /*! - * \brief Gets a list of the registered instance types. - * - * \return A list of instance types. - */ - InstTypeList typeList(); + InstLoaderError loadInstance(Instance *&inst, const QString &instDir); private: InstanceLoader(); - QMap<QString, InstanceTypeInterface *> m_typeMap; - static InstanceLoader loader; }; diff --git a/libmultimc/include/instancetypeinterface.h b/libmultimc/include/instancetypeinterface.h index ba13f820..4fbc2593 100644 --- a/libmultimc/include/instancetypeinterface.h +++ b/libmultimc/include/instancetypeinterface.h @@ -75,7 +75,7 @@ protected: * TypeNotRegistered if the given type is not registered with the InstanceLoader. * InstExists if the given instance directory is already an instance. */ - virtual InstanceLoader::InstTypeError createInstance(Instance *&inst, const QString &instDir) const = 0; + virtual InstanceLoader::InstLoaderError createInstance(Instance *&inst, const QString &instDir) const = 0; /*! * \brief Loads an instance from the given directory. @@ -86,7 +86,7 @@ protected: * NotAnInstance if the given instance directory isn't a valid instance. * WrongInstType if the given instance directory's type isn't an instance of this type. */ - virtual InstanceLoader::InstTypeError loadInstance(Instance *&inst, const QString &instDir) const = 0; + virtual InstanceLoader::InstLoaderError loadInstance(Instance *&inst, const QString &instDir) const = 0; }; Q_DECLARE_INTERFACE(InstanceTypeInterface, InstanceTypeInterface_IID) diff --git a/libmultimc/include/instversion.h b/libmultimc/include/instversion.h index 9d13dbe4..e91e68ba 100644 --- a/libmultimc/include/instversion.h +++ b/libmultimc/include/instversion.h @@ -22,36 +22,92 @@ class InstVersionList; +/*! + * An abstract base class for instance versions. + * InstVersions hold information about versions such as their names, identifiers, + * types, etc. + */ class LIBMULTIMC_EXPORT InstVersion : public QObject { Q_OBJECT -public: + /*! - * \brief Constructs a new InstVersion with the given parent. - * The parent *must* be the InstVersionList that contains this InstVersion. - * The InstVersion should be added to the list immediately after being created. + * A string used to identify this version in config files. + * This should be unique within the version list or shenanigans will occur. + */ + Q_PROPERTY(QString descriptor READ descriptor CONSTANT) + + /*! + * The name of this version as it is displayed to the user. + * For example: "1.5.1" + */ + Q_PROPERTY(QString name READ name) + + /*! + * The name of this version's type as it is displayed to the user. + * For example: "Latest Version", "Snapshot", or "MCNostalgia" + */ + Q_PROPERTY(QString typeName READ typeName) + + /*! + * Whether or not this is a meta version. + * Meta versions are not real versions, merely versions that act as aliases + * for other versions. + * For example: There could be a meta version called "Latest" that always + * points to the latest version. The user would pick this version and when + * a new version came out, it would point to the new one and update the instance + * automatically. */ - explicit InstVersion(InstVersionList *parent = 0); + Q_PROPERTY(bool isMeta READ isMeta) - //! Gets the string used to identify this version in config files. - virtual QString descriptor() const = 0; /*! - * \breif Returns this InstVersion's name. - * This is displayed to the user in the GUI and is usually just the version number ("1.4.7"), for example. + * Gets the version's timestamp. + * This is primarily used for sorting versions in a list. */ - virtual QString name() const = 0; + Q_PROPERTY(qint64 timestamp READ timestamp) + +public: /*! - * \brief Returns this InstVersion's type name. - * This is usually displayed to the user in the GUI and specifies what - * kind of version this is. For example: it could be "Snapshot", - * "Latest Version", "MCNostalgia", etc. + * \brief Constructs a new InstVersion with the given parent. + * The parent *must* be the InstVersionList that contains this InstVersion. + * The InstVersion will be added to the list immediately after being created. */ + explicit InstVersion(const QString &descriptor, + const QString &name, + qint64 timestamp, + InstVersionList *parent = 0); + + /*! + * Copy constructor. + * If the 'parent' parameter is not NULL, sets this version's parent to the + * specified object, rather than setting it to the same parent as the version + * we're copying from. + * \param other The version to copy. + * \param parent If not NULL, will be set as the new version object's parent. + */ + InstVersion(const InstVersion &other, QObject *parent = 0); + + virtual QString descriptor() const; + virtual QString name() const; virtual QString typeName() const = 0; + virtual qint64 timestamp() const; + virtual bool isMeta() const; - //! Returns the version list that this InstVersion is a part of. virtual InstVersionList *versionList() const; + + /*! + * Creates a copy of this version with a different parent. + * \param newParent The parent QObject of the copy. + * \return A new, identical copy of this version with the given parent set. + */ + virtual InstVersion *copyVersion(InstVersionList *newParent) const = 0; + +protected: + QString m_descriptor; + QString m_name; + qint64 m_timestamp; }; #endif // INSTVERSION_H diff --git a/libmultimc/include/instversionlist.h b/libmultimc/include/instversionlist.h index b5a9f254..1aabc5cf 100644 --- a/libmultimc/include/instversionlist.h +++ b/libmultimc/include/instversionlist.h @@ -49,7 +49,7 @@ public: explicit InstVersionList(QObject *parent = 0); /*! - * \brief Gets a task that will reload the version list. + * \brief Gets a task that will reload the version islt. * Simply execute the task to load the list. * The task returned by this function should reset the model when it's done. * \return A pointer to a task that reloads the version list. @@ -87,6 +87,21 @@ public: * By default, this is simply the first version in the list. */ virtual const InstVersion *getLatestStable(); + +protected slots: + /*! + * Updates this list with the given list of versions. + * This is done by copying each version in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the versions are set to this + * version list. This can't be done in the load task, because the versions the load + * task creates are on the load task's thread and Qt won't allow their parents + * to be set to something created on another thread. + * To get around that problem, we invoke this method on the GUI thread, which + * then copies the versions and sets their parents correctly. + * \param versions List of versions whose parents should be set. + */ + virtual void updateListData(QList<InstVersion *> versions) = 0; }; #endif // INSTVERSIONLIST_H diff --git a/libmultimc/include/minecraftversion.h b/libmultimc/include/minecraftversion.h new file mode 100644 index 00000000..e30582ac --- /dev/null +++ b/libmultimc/include/minecraftversion.h @@ -0,0 +1,95 @@ +/* Copyright 2013 Andrew Okin + * + * 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. + */ + +#ifndef MINECRAFTVERSION_H +#define MINECRAFTVERSION_H + +#include "libmmc_config.h" + +#include "instversion.h" + +class LIBMULTIMC_EXPORT MinecraftVersion : public InstVersion +{ + Q_OBJECT + + /*! + * This version's type. Used internally to identify what kind of version this is. + */ + Q_PROPERTY(VersionType versionType READ versionType WRITE setVersionType) + + /*! + * The URL that this version will be downloaded from. + */ + Q_PROPERTY(QString downloadURL READ downloadURL) + + /*! + * ETag/MD5 Used to verify the integrity of the downloaded minecraft.jar. + */ + Q_PROPERTY(QString etag READ etag) + +public: + explicit MinecraftVersion(QString descriptor, + QString name, + qint64 timestamp, + QString dlUrl, + QString etag, + InstVersionList *parent = 0); + + /*! + * Creates a meta version that links to the given version. + * This is *NOT* a copy constructor. + * \param linkedVersion the version that the meta version will link to. + */ + explicit MinecraftVersion(const MinecraftVersion *linkedVersion); + + MinecraftVersion(const MinecraftVersion &other, QObject *parent); + + static InstVersion *mcnVersion(QString rawName, QString niceName); + + enum VersionType + { + OldSnapshot, + Stable, + CurrentStable, + Snapshot, + MCNostalgia, + MetaCustom, + MetaLatestSnapshot, + MetaLatestStable + }; + + virtual QString descriptor() const; + virtual QString name() const; + virtual QString typeName() const; + virtual qint64 timestamp() const; + + virtual VersionType versionType() const; + virtual void setVersionType(VersionType typeName); + + virtual QString downloadURL() const; + virtual QString etag() const; + virtual bool isMeta() const; + + virtual InstVersion *copyVersion(InstVersionList *newParent) const; + +private: + InstVersion *m_linkedVersion; + + QString m_dlUrl; + QString m_etag; + VersionType m_type; +}; + +#endif // MINECRAFTVERSION_H diff --git a/libmultimc/include/minecraftversionlist.h b/libmultimc/include/minecraftversionlist.h new file mode 100644 index 00000000..18eb4ea6 --- /dev/null +++ b/libmultimc/include/minecraftversionlist.h @@ -0,0 +1,105 @@ +/* Copyright 2013 Andrew Okin + * + * 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. + */ + +#ifndef MINECRAFTVERSIONLIST_H +#define MINECRAFTVERSIONLIST_H + +#include <QObject> + +#include <QNetworkAccessManager> + +#include <QList> + +#include "instversionlist.h" + +#include "task.h" + +#include "minecraftversion.h" + +#include "libmmc_config.h" + +class MCVListLoadTask; + +class LIBMULTIMC_EXPORT MinecraftVersionList : public InstVersionList +{ + Q_OBJECT +public: + friend class MCVListLoadTask; + + explicit MinecraftVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const InstVersion *at(int i) const; + virtual int count() const; + virtual void printToStdOut() const; + + /*! + * Gets the main version list instance. + */ + static MinecraftVersionList &getMainList(); + +protected: + QList<InstVersion *>m_vlist; + + bool m_loaded; + +protected slots: + virtual void updateListData(QList<InstVersion *> versions); +}; + +class MCVListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListLoadTask(MinecraftVersionList *vlist); + ~MCVListLoadTask(); + + virtual void executeTask(); + +protected: + void setSubStatus(const QString msg = ""); + + //! Loads versions from Mojang's official version list. + bool loadFromVList(); + + //! Loads versions from assets.minecraft.net. Any duplicates are ignored. + bool loadFromAssets(); + + //! Loads versions from MCNostalgia. + bool loadMCNostalgia(); + + //! Finalizes loading by updating the version list. + bool finalize(); + + void updateStuff(); + + QNetworkAccessManager *netMgr; + + MinecraftVersionList *m_list; + QList<InstVersion *> tempList; //! < List of loaded versions + QList<InstVersion *> assetsList; //! < List of versions loaded from assets.minecraft.net + QList<InstVersion *> mcnList; //! < List of loaded MCNostalgia versions + + MinecraftVersion *m_currentStable; + + bool processedMCVListReply; + bool processedAssetsReply; + bool processedMCNReply; +}; + + +#endif // MINECRAFTVERSIONLIST_H diff --git a/libmultimc/src/instance.cpp b/libmultimc/src/instance.cpp index f9e105c7..f4a6a3c9 100644 --- a/libmultimc/src/instance.cpp +++ b/libmultimc/src/instance.cpp @@ -22,6 +22,7 @@ #include "overridesetting.h" #include "pathutils.h" +#include <minecraftversionlist.h> Instance::Instance(const QString &rootDir, QObject *parent) : QObject(parent) @@ -151,6 +152,39 @@ QString Instance::modListFile() const return PathCombine(rootDir(), "modlist"); } +InstVersionList *Instance::versionList() const +{ + return &MinecraftVersionList::getMainList(); +} + +bool Instance::shouldUpdateCurrentVersion() +{ + QFileInfo jar(mcJar()); + return jar.lastModified().toUTC().toMSecsSinceEpoch() != lastCurrentVersionUpdate(); +} + +void Instance::updateCurrentVersion(bool keepCurrent) +{ + QFileInfo jar(mcJar()); + + if(!jar.exists()) + { + setLastCurrentVersionUpdate(0); + setCurrentVersion("Unknown"); + return; + } + + qint64 time = jar.lastModified().toUTC().toMSecsSinceEpoch(); + + setLastCurrentVersionUpdate(time); + if (!keepCurrent) + { + // TODO: Implement GetMinecraftJarVersion function. + QString newVersion = "Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath()); + setCurrentVersion(newVersion); + } +} + SettingsObject &Instance::settings() const { return *m_settings; diff --git a/libmultimc/src/instancelist.cpp b/libmultimc/src/instancelist.cpp index f9c525d0..6f5a9f99 100644 --- a/libmultimc/src/instancelist.cpp +++ b/libmultimc/src/instancelist.cpp @@ -147,21 +147,17 @@ InstanceList::InstListError InstanceList::loadList() { Instance *instPtr = NULL; - InstanceLoader::InstTypeError error = InstanceLoader::get(). + InstanceLoader::InstLoaderError error = InstanceLoader::get(). loadInstance(instPtr, subDir); if (error != InstanceLoader::NoError && - error != InstanceLoader::NotAnInstance) + error != InstanceLoader::NotAnInstance) { QString errorMsg = QString("Failed to load instance %1: "). arg(QFileInfo(subDir).baseName()).toUtf8(); switch (error) { - case InstanceLoader::TypeNotRegistered: - errorMsg += "Instance type not found."; - break; - default: errorMsg += QString("Unknown instance loader error %1"). arg(error); @@ -234,4 +230,4 @@ void InstanceList::propertiesChanged(Instance * inst) break; } } -}
\ No newline at end of file +} diff --git a/libmultimc/src/instanceloader.cpp b/libmultimc/src/instanceloader.cpp index 9d98230f..e9924af4 100644 --- a/libmultimc/src/instanceloader.cpp +++ b/libmultimc/src/instanceloader.cpp @@ -15,9 +15,10 @@ #include "include/instanceloader.h" +#include <QDir> #include <QFileInfo> -#include "include/instancetypeinterface.h" +#include "include/instance.h" #include "inifile.h" @@ -31,79 +32,30 @@ InstanceLoader::InstanceLoader() : } - -InstanceLoader::InstTypeError InstanceLoader::registerInstanceType(InstanceTypeInterface *type) +InstanceLoader::InstLoaderError InstanceLoader::loadInstance( + Instance *&inst, const QString &instDir) { - // Check to see if the type ID exists. - if (m_typeMap.contains(type->typeID())) - return TypeIDExists; + Instance *loadedInst = new Instance(instDir, this); - // Set the parent to this. - // ((QObject *)type)->setParent(this); + // TODO: Sanity checks to verify that the instance is valid. - // Add it to the map. - m_typeMap.insert(type->typeID(), type); + inst = loadedInst; - qDebug(QString("Registered instance type %1."). - arg(type->typeID()).toUtf8()); return NoError; } -InstanceLoader::InstTypeError InstanceLoader::createInstance(Instance *&inst, - const InstanceTypeInterface *type, - const QString &instDir) -{ - // Check if the type is registered. - if (!type || findType(type->typeID()) != type) - return TypeNotRegistered; - - // Create the instance. - return type->createInstance(inst, instDir); -} - -InstanceLoader::InstTypeError InstanceLoader::loadInstance(Instance *&inst, - const InstanceTypeInterface *type, - const QString &instDir) -{ - // Check if the type is registered. - if (!type || findType(type->typeID()) != type) - return TypeNotRegistered; - - return type->loadInstance(inst, instDir); -} - -InstanceLoader::InstTypeError InstanceLoader::loadInstance(Instance *&inst, - const QString &instDir) -{ - QFileInfo instConfig(PathCombine(instDir, "instance.cfg")); - - if (!instConfig.exists()) - return NotAnInstance; - - INIFile ini; - ini.loadFile(instConfig.path()); - QString typeName = ini.get("type", "net.forkk.MultiMC.StdInstance").toString(); - const InstanceTypeInterface *type = findType(typeName); - - return loadInstance(inst, type, instDir); -} -const InstanceTypeInterface *InstanceLoader::findType(const QString &id) +InstanceLoader::InstLoaderError InstanceLoader::createInstance(Instance *&inst, const QString &instDir) { - if (!m_typeMap.contains(id)) - return NULL; - else - return m_typeMap[id]; -} - -InstTypeList InstanceLoader::typeList() -{ - InstTypeList typeList; + QDir rootDir(instDir); - for (QMap<QString, InstanceTypeInterface *>::iterator iter = m_typeMap.begin(); iter != m_typeMap.end(); iter++) + qDebug(instDir.toUtf8()); + if (!rootDir.exists() && !rootDir.mkpath(".")) { - typeList.append(*iter); + return InstanceLoader::CantCreateDir; } - return typeList; + inst = new Instance(instDir, this); + + return InstanceLoader::NoError; } diff --git a/libmultimc/src/instversion.cpp b/libmultimc/src/instversion.cpp index cedb61df..d3d078a9 100644 --- a/libmultimc/src/instversion.cpp +++ b/libmultimc/src/instversion.cpp @@ -16,17 +16,48 @@ #include "include/instversion.h" #include "include/instversionlist.h" -InstVersion::InstVersion(InstVersionList *parent) : - QObject(parent) +InstVersion::InstVersion(const QString &descriptor, + const QString &name, + qint64 timestamp, + InstVersionList *parent) : + QObject(parent), m_descriptor(descriptor), m_name(name), m_timestamp(timestamp) +{ + +} + +InstVersion::InstVersion(const InstVersion &other, QObject *parent) : + QObject(parent ? parent : other.parent()), + m_descriptor(other.descriptor()), m_name(other.name()), m_timestamp(other.timestamp()) { } InstVersionList *InstVersion::versionList() const { - // Parent should *always* be an InstVersionList + // Parent should *always* be either an InstVersionList or NULL. if (!parent() || !parent()->inherits("InstVersionList")) return NULL; else return (InstVersionList *)parent(); } + +bool InstVersion::isMeta() const +{ + return false; +} + + +QString InstVersion::descriptor() const +{ + return m_descriptor; +} + +QString InstVersion::name() const +{ + return m_name; +} + +qint64 InstVersion::timestamp() const +{ + return m_timestamp; +} diff --git a/libmultimc/src/minecraftversion.cpp b/libmultimc/src/minecraftversion.cpp new file mode 100644 index 00000000..896c2e18 --- /dev/null +++ b/libmultimc/src/minecraftversion.cpp @@ -0,0 +1,142 @@ +/* Copyright 2013 Andrew Okin + * + * 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. + */ + +#include "minecraftversion.h" + +MinecraftVersion::MinecraftVersion(QString descriptor, + QString name, + qint64 timestamp, + QString dlUrl, + QString etag, + InstVersionList *parent) : + InstVersion(descriptor, name, timestamp, parent), m_dlUrl(dlUrl), m_etag(etag) +{ + m_linkedVersion = NULL; +} + +MinecraftVersion::MinecraftVersion(const MinecraftVersion *linkedVersion) : + InstVersion(linkedVersion->descriptor(), linkedVersion->name(), linkedVersion->timestamp(), + linkedVersion->versionList()) +{ + m_linkedVersion = (MinecraftVersion *)linkedVersion; +} + +MinecraftVersion::MinecraftVersion(const MinecraftVersion &other, QObject *parent) : + InstVersion(other, parent) +{ + if (other.m_linkedVersion) + m_linkedVersion = other.m_linkedVersion; + else + { + m_dlUrl = other.downloadURL(); + m_etag = other.etag(); + } +} + +QString MinecraftVersion::descriptor() const +{ + return m_descriptor; +} + +QString MinecraftVersion::name() const +{ + return m_name; +} + +QString MinecraftVersion::typeName() const +{ + if (m_linkedVersion) + return m_linkedVersion->typeName(); + + switch (versionType()) + { + case OldSnapshot: + return "Old Snapshot"; + + case Stable: + return "Stable"; + + case CurrentStable: + return "Current Stable"; + + case Snapshot: + return "Snapshot"; + + case MCNostalgia: + return "MCNostalgia"; + + case MetaCustom: + // Not really sure what this does, but it was in the code for v4, + // so it must be important... Right? + return "Custom Meta Version"; + + case MetaLatestSnapshot: + return "Latest Snapshot"; + + case MetaLatestStable: + return "Latest Stable"; + + default: + return QString("Unknown Type %1").arg(versionType()); + } +} + +qint64 MinecraftVersion::timestamp() const +{ + return m_timestamp; +} + +MinecraftVersion::VersionType MinecraftVersion::versionType() const +{ + return m_type; +} + +void MinecraftVersion::setVersionType(MinecraftVersion::VersionType typeName) +{ + m_type = typeName; +} + +QString MinecraftVersion::downloadURL() const +{ + return m_dlUrl; +} + +QString MinecraftVersion::etag() const +{ + return m_etag; +} + +bool MinecraftVersion::isMeta() const +{ + return versionType() == MetaCustom || + versionType() == MetaLatestSnapshot || + versionType() == MetaLatestStable; +} + +InstVersion *MinecraftVersion::copyVersion(InstVersionList *newParent) const +{ + if (isMeta()) + { + MinecraftVersion *version = new MinecraftVersion((MinecraftVersion *)m_linkedVersion); + return version; + } + else + { + MinecraftVersion *version = new MinecraftVersion( + descriptor(), name(), timestamp(), downloadURL(), etag(), newParent); + version->setVersionType(versionType()); + return version; + } +} diff --git a/libmultimc/src/minecraftversionlist.cpp b/libmultimc/src/minecraftversionlist.cpp new file mode 100644 index 00000000..ce02a769 --- /dev/null +++ b/libmultimc/src/minecraftversionlist.cpp @@ -0,0 +1,489 @@ +/* Copyright 2013 Andrew Okin + * + * 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. + */ + +#include "include/minecraftversionlist.h" + +#include <QDebug> + +#include <QtXml> + +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValue> +#include <QJsonParseError> + +#include <QtNetwork> + +#define MCVLIST_URLBASE "http://s3.amazonaws.com/Minecraft.Download/versions/" +#define ASSETS_URLBASE "http://assets.minecraft.net/" +#define MCN_URLBASE "http://sonicrules.org/mcnweb.py" + +MinecraftVersionList mcVList; + +MinecraftVersionList::MinecraftVersionList(QObject *parent) : + InstVersionList(parent) +{ + +} + +Task *MinecraftVersionList::getLoadTask() +{ + return new MCVListLoadTask(this); +} + +bool MinecraftVersionList::isLoaded() +{ + return m_loaded; +} + +const InstVersion *MinecraftVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int MinecraftVersionList::count() const +{ + return m_vlist.count(); +} + +void MinecraftVersionList::printToStdOut() const +{ + qDebug() << "---------------- Version List ----------------"; + + for (int i = 0; i < m_vlist.count(); i++) + { + MinecraftVersion *version = qobject_cast<MinecraftVersion *>(m_vlist.at(i)); + + if (!version) + continue; + + qDebug() << "Version " << version->name(); + qDebug() << "\tDownload: " << version->downloadURL(); + qDebug() << "\tTimestamp: " << version->timestamp(); + qDebug() << "\tType: " << version->typeName(); + qDebug() << "----------------------------------------------"; + } +} + +MinecraftVersionList &MinecraftVersionList::getMainList() +{ + return mcVList; +} + +void MinecraftVersionList::updateListData(QList<InstVersion *> versions) +{ + // First, we populate a temporary list with the copies of the versions. + QList<InstVersion *> tempList; + for (int i = 0; i < versions.length(); i++) + { + InstVersion *version = versions[i]->copyVersion(this); + Q_ASSERT(version != NULL); + tempList.append(version); + } + + // Now we swap the temporary list into the actual version list. + // This applies our changes to the version list immediately and still gives us + // access to the old version list so that we can delete the objects in it and + // free their memory. By doing this, we cause the version list to update as + // quickly as possible. + beginResetModel(); + m_vlist.swap(tempList); + m_loaded = true; + endResetModel(); + + // We called swap, so all the data that was in the version list previously is now in + // tempList (and vice-versa). Now we just free the memory. + while (!tempList.isEmpty()) + delete tempList.takeFirst(); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +inline QDateTime timeFromS3Time(QString str) +{ + const QString fmt("yyyy-MM-dd'T'HH:mm:ss'.000Z'"); + return QDateTime::fromString(str, fmt); +} + +inline void waitForNetRequest(QNetworkReply *netReply) +{ + QEventLoop loop; + loop.connect(netReply, SIGNAL(finished()), SLOT(quit())); + loop.exec(); +} + + +MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) +{ + m_list = vlist; + m_currentStable = NULL; +} + +MCVListLoadTask::~MCVListLoadTask() +{ +// delete netMgr; +} + +void MCVListLoadTask::executeTask() +{ + setSubStatus(); + + QNetworkAccessManager networkMgr; + netMgr = &networkMgr; + + if (!loadFromVList()) + { + qDebug() << "Failed to load from Mojang version list."; + } + if (!loadFromAssets()) + { + qDebug() << "Failed to load assets version list."; + } + if (!loadMCNostalgia()) + { + qDebug() << "Failed to load MCNostalgia version list."; + } + finalize(); +} + +void MCVListLoadTask::setSubStatus(const QString msg) +{ + if (msg.isEmpty()) + setStatus("Loading instance version list..."); + else + setStatus("Loading instance version list: " + msg); +} + +bool MCVListLoadTask::loadFromVList() +{ + QNetworkReply *vlistReply = netMgr->get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + + "versions.json"))); + waitForNetRequest(vlistReply); + + switch (vlistReply->error()) + { + case QNetworkReply::NoError: + { + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); + + if (jsonError.error == QJsonParseError::NoError) + { + Q_ASSERT_X(jsonDoc.isObject(), "loadFromVList", "jsonDoc is not an object"); + + QJsonObject root = jsonDoc.object(); + + // Get the ID of the latest release and the latest snapshot. + Q_ASSERT_X(root.value("latest").isObject(), "loadFromVList", + "version list is missing 'latest' object"); + QJsonObject latest = root.value("latest").toObject(); + + QString latestReleaseID = latest.value("release").toString(""); + QString latestSnapshotID = latest.value("snapshot").toString(""); + Q_ASSERT_X(!latestReleaseID.isEmpty(), "loadFromVList", "latest release field is missing"); + Q_ASSERT_X(!latestSnapshotID.isEmpty(), "loadFromVList", "latest snapshot field is missing"); + + // Now, get the array of versions. + Q_ASSERT_X(root.value("versions").isArray(), "loadFromVList", + "version list object is missing 'versions' array"); + QJsonArray versions = root.value("versions").toArray(); + + for (int i = 0; i < versions.count(); i++) + { + // Load the version info. + Q_ASSERT_X(versions[i].isObject(), "loadFromVList", + QString("in versions array, index %1 is not an object"). + arg(i).toUtf8()); + QJsonObject version = versions[i].toObject(); + + QString versionID = version.value("id").toString(""); + QString versionTimeStr = version.value("time").toString(""); + QString versionTypeStr = version.value("type").toString(""); + + Q_ASSERT_X(!versionID.isEmpty(), "loadFromVList", + QString("in versions array, index %1's \"id\" field is not a valid string"). + arg(i).toUtf8()); + Q_ASSERT_X(!versionTimeStr.isEmpty(), "loadFromVList", + QString("in versions array, index %1's \"time\" field is not a valid string"). + arg(i).toUtf8()); + Q_ASSERT_X(!versionTypeStr.isEmpty(), "loadFromVList", + QString("in versions array, index %1's \"type\" field is not a valid string"). + arg(i).toUtf8()); + + + // Now, process that info and add the version to the list. + + // Parse the timestamp. + QDateTime versionTime = timeFromS3Time(versionTimeStr); + + // Parse the type. + MinecraftVersion::VersionType versionType; + if (versionTypeStr == "release") + { + // Check if this version is the current stable version. + if (versionID == latestReleaseID) + versionType = MinecraftVersion::CurrentStable; + else + versionType = MinecraftVersion::Stable; + } + else + { + versionType = MinecraftVersion::Snapshot; + } + + // Get the download URL. + QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/"; + + + // Now, we construct the version object and add it to the list. + MinecraftVersion *mcVersion = new MinecraftVersion( + versionID, versionID, versionTime.toMSecsSinceEpoch(), + dlUrl, ""); + mcVersion->setVersionType(versionType); + tempList.append(mcVersion); + } + } + else + { + qDebug() << "Error parsing version list JSON:" << jsonError.errorString(); + } + + break; + } + + default: + // TODO: Network error handling. + qDebug() << "Failed to load Minecraft main version list" << vlistReply->errorString(); + break; + } + + return true; +} + +bool MCVListLoadTask::loadFromAssets() +{ + setSubStatus("Loading versions from assets.minecraft.net..."); + + bool succeeded = false; + + QNetworkReply *assetsReply = netMgr->get(QNetworkRequest(QUrl(ASSETS_URLBASE))); + waitForNetRequest(assetsReply); + + switch (assetsReply->error()) + { + case QNetworkReply::NoError: + { + // Get the XML string. + QString xmlString = assetsReply->readAll(); + + QString xmlErrorMsg; + + QDomDocument doc; + if (!doc.setContent(xmlString, false, &xmlErrorMsg)) + { + // TODO: Display error message to the user. + qDebug() << "Failed to process assets.minecraft.net. XML error:" << + xmlErrorMsg << xmlString; + } + + QDomNodeList contents = doc.elementsByTagName("Contents"); + + QRegExp mcRegex("/minecraft.jar$"); + QRegExp snapshotRegex("[0-9][0-9]w[0-9][0-9][a-z]|pre|rc"); + + for (int i = 0; i < contents.length(); i++) + { + QDomElement element = contents.at(i).toElement(); + + if (element.isNull()) + continue; + + QDomElement keyElement = getDomElementByTagName(element, "Key"); + QDomElement lastmodElement = getDomElementByTagName(element, "LastModified"); + QDomElement etagElement = getDomElementByTagName(element, "ETag"); + + if (keyElement.isNull() || lastmodElement.isNull() || etagElement.isNull()) + continue; + + QString key = keyElement.text(); + QString lastModStr = lastmodElement.text(); + QString etagStr = etagElement.text(); + + if (!key.contains(mcRegex)) + continue; + + QString versionDirName = key.left(key.length() - 14); + QString dlUrl = QString("http://assets.minecraft.net/%1/").arg(versionDirName); + + QString versionName = versionDirName.replace("_", "."); + + QDateTime versionTimestamp = timeFromS3Time(lastModStr); + if (!versionTimestamp.isValid()) + { + qDebug(QString("Failed to parse timestamp for version %1 %2"). + arg(versionName, lastModStr).toUtf8()); + versionTimestamp = QDateTime::currentDateTime(); + } + + if (m_currentStable) + { + { + bool older = versionTimestamp.toMSecsSinceEpoch() < m_currentStable->timestamp(); + bool newer = versionTimestamp.toMSecsSinceEpoch() > m_currentStable->timestamp(); + bool isSnapshot = versionName.contains(snapshotRegex); + + MinecraftVersion *version = new MinecraftVersion( + versionName, versionName, + versionTimestamp.toMSecsSinceEpoch(), + dlUrl, etagStr); + + if (newer) + { + version->setVersionType(MinecraftVersion::Snapshot); + } + else if (older && isSnapshot) + { + version->setVersionType(MinecraftVersion::OldSnapshot); + } + else if (older) + { + version->setVersionType(MinecraftVersion::Stable); + } + else + { + // Shouldn't happen, but just in case... + version->setVersionType(MinecraftVersion::CurrentStable); + } + + assetsList.push_back(version); + } + } + else // If there isn't a current stable version. + { + bool isSnapshot = versionName.contains(snapshotRegex); + + MinecraftVersion *version = new MinecraftVersion( + versionName, versionName, + versionTimestamp.toMSecsSinceEpoch(), + dlUrl, etagStr); + version->setVersionType(isSnapshot? MinecraftVersion::Snapshot : + MinecraftVersion::Stable); + assetsList.push_back(version); + } + } + + setSubStatus("Loaded assets.minecraft.net"); + succeeded = true; + break; + } + + default: + // TODO: Network error handling. + qDebug() << "Failed to load assets.minecraft.net" << assetsReply->errorString(); + break; + } + + processedAssetsReply = true; + updateStuff(); + return succeeded; +} + +bool MCVListLoadTask::loadMCNostalgia() +{ + QNetworkReply *mcnReply = netMgr->get(QNetworkRequest(QUrl(QString(MCN_URLBASE) + "?pversion=1&list=True"))); + waitForNetRequest(mcnReply); + return true; +} + +bool MCVListLoadTask::finalize() +{ + // First, we need to do some cleanup. We loaded assets versions into assetsList, + // MCNostalgia versions into mcnList and all the others into tempList. MCNostalgia + // provides some versions that are on assets.minecraft.net and we want to ignore + // those, so we remove and delete them from mcnList. assets.minecraft.net also provides + // versions that are on Mojang's version list and we want to ignore those as well. + + // To start, we get a list of the descriptors in tmpList. + QStringList tlistDescriptors; + for (int i = 0; i < tempList.count(); i++) + tlistDescriptors.append(tempList.at(i)->descriptor()); + + // Now, we go through our assets version list and remove anything with + // a descriptor that matches one we already have in tempList. + for (int i = 0; i < assetsList.count(); i++) + if (tlistDescriptors.contains(assetsList.at(i)->descriptor())) + delete assetsList.takeAt(i--); // We need to decrement here because we're removing an item. + + // We also need to rebuild the list of descriptors. + tlistDescriptors.clear(); + for (int i = 0; i < tempList.count(); i++) + tlistDescriptors.append(tempList.at(i)->descriptor()); + + // Next, we go through our MCNostalgia version list and do the same thing. + for (int i = 0; i < mcnList.count(); i++) + if (tlistDescriptors.contains(mcnList.at(i)->descriptor())) + delete mcnList.takeAt(i--); // We need to decrement here because we're removing an item. + + // Now that the duplicates are gone, we need to merge the lists. This is + // simple enough. + tempList.append(assetsList); + tempList.append(mcnList); + + // We're done with these lists now, but the items have been moved over to + // tempList, so we don't need to delete them yet. + + // Now, we invoke the updateListData slot on the GUI thread. This will copy all + // the versions we loaded and set their parents to the version list. + // Then, it will swap the new list with the old one and free the old list's memory. + QMetaObject::invokeMethod(m_list, "updateListData", Qt::BlockingQueuedConnection, + Q_ARG(QList<InstVersion*>, tempList)); + + // Once that's finished, we can delete the versions in our temp list. + while (!tempList.isEmpty()) + delete tempList.takeFirst(); + +#ifdef PRINT_VERSIONS + m_list->printToStdOut(); +#endif + return true; +} + +void MCVListLoadTask::updateStuff() +{ + const int totalReqs = 3; + int reqsComplete = 0; + + if (processedMCVListReply) + reqsComplete++; + if (processedAssetsReply) + reqsComplete++; + if (processedMCNReply) + reqsComplete++; + + calcProgress(reqsComplete, totalReqs); + + if (reqsComplete >= totalReqs) + { + quit(); + } +} |