diff --git a/libmultimc/include/instance.h b/libmultimc/include/instance.h index aaefbd6e..41e664ff 100644 --- a/libmultimc/include/instance.h +++ b/libmultimc/include/instance.h @@ -23,6 +23,7 @@ #include "inifile.h" #include "instancetypeinterface.h" +#include "instversionlist.h" #include "libmmc_config.h" @@ -253,6 +254,15 @@ public: + //////// LISTS, LISTS, AND MORE LISTS //////// + /*! + * \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 //////// /*! diff --git a/libmultimc/include/instancetypeinterface.h b/libmultimc/include/instancetypeinterface.h index 30a12d99..ba13f820 100644 --- a/libmultimc/include/instancetypeinterface.h +++ b/libmultimc/include/instancetypeinterface.h @@ -20,6 +20,8 @@ #include "instanceloader.h" +class InstVersionList; + //! The InstanceTypeInterface's interface ID. #define InstanceTypeInterface_IID "net.forkk.MultiMC.InstanceTypeInterface/0.1" @@ -56,7 +58,13 @@ public: * \brief Gets a longer, more detailed description of this instance type. * \return The instance type's description. */ - virtual QString description() const = 0; + virtual QString description() const = 0; + + /*! + * \brief Gets the version list for this instance type. + * \return A pointer to this instance type's version list. + */ + virtual InstVersionList *versionList() const = 0; protected: /*! diff --git a/libmultimc/include/instversion.h b/libmultimc/include/instversion.h index 3c6b7ac9..7de83966 100644 --- a/libmultimc/include/instversion.h +++ b/libmultimc/include/instversion.h @@ -26,27 +26,32 @@ class LIBMULTIMC_EXPORT InstVersion : public QObject { Q_OBJECT public: - // 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 as any calls - // to id() will likely fail unless the InstVersion is in a list. + /*! + * \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. + */ explicit InstVersion(InstVersionList *parent = 0); - // Returns this InstVersion's ID. This is usually just the InstVersion's index - // within its InstVersionList, but not always. - // If this InstVersion is not in an InstVersionList, returns -1. - virtual int id() const = 0; + //! 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. + */ - // 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. virtual QString name() const = 0; - // Returns this InstVersion's 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 Returns this InstVersion's 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. + */ virtual QString type() const = 0; - // Returns the version list that this InstVersion is a part of. + //! Returns the version list that this InstVersion is a part of. virtual InstVersionList *versionList() const; }; diff --git a/libmultimc/include/instversionlist.h b/libmultimc/include/instversionlist.h index d64a286f..4345aaaa 100644 --- a/libmultimc/include/instversionlist.h +++ b/libmultimc/include/instversionlist.h @@ -21,25 +21,44 @@ #include "libmmc_config.h" class InstVersion; +class Task; -// Class that each instance type's version list derives from. Version lists are -// the lists that keep track of the available game versions for that instance. -// This list will not be loaded on startup. It will be loaded when the list's -// load function is called. +/*! + * \brief Class that each instance type's version list derives from. + * Version lists are the lists that keep track of the available game versions + * for that instance. This list will not be loaded on startup. It will be loaded + * when the list's load function is called. Before using the version list, you + * should check to see if it has been loaded yet and if not, load the list. + */ class LIBMULTIMC_EXPORT InstVersionList : public QObject { Q_OBJECT public: - explicit InstVersionList(); + explicit InstVersionList(QObject *parent = 0); - // Reloads the version list. - virtual void loadVersionList() = 0; + /*! + * \brief Gets a task that will reload the version list. + * Simply execute the task to load the list. + * \return A pointer to a task that reloads the version list. + */ + virtual Task *getLoadTask() = 0; - // Gets the version at the given index. + //! Checks whether or not the list is loaded. If this returns false, the list should be loaded. + virtual bool isLoaded() = 0; + + //! Gets the version at the given index. virtual const InstVersion *at(int i) const = 0; - // Returns the number of versions in the list. + //! Returns the number of versions in the list. virtual int count() const = 0; + + /*! + * \brief Finds a version by its descriptor. + * \param The descriptor of the version to find. + * \return A const pointer to the version with the given descriptor. NULL if + * one doesn't exist. + */ + virtual const InstVersion *findVersion(const QString &descriptor); }; #endif // INSTVERSIONLIST_H diff --git a/libmultimc/include/task.h b/libmultimc/include/task.h index b1a3052d..fc5b1d25 100644 --- a/libmultimc/include/task.h +++ b/libmultimc/include/task.h @@ -34,6 +34,15 @@ public: QString getStatus() const; int getProgress() const; + /*! + * \brief Calculates and sets the task's progress based on the number of parts completed out of the total number to complete. + * This is essentially just shorthand for setProgress((parts / whole) * 100); + * \param parts The parts out of the whole completed. This parameter should + * be less than whole. If it is greater than whole, progress is set to 100. + * \param whole The total number of things that need to be completed. + */ + void calcProgress(int parts, int whole); + public slots: void setStatus(const QString& status); void setProgress(int progress); diff --git a/libmultimc/src/instversionlist.cpp b/libmultimc/src/instversionlist.cpp index e171cfa5..301b9969 100644 --- a/libmultimc/src/instversionlist.cpp +++ b/libmultimc/src/instversionlist.cpp @@ -13,9 +13,20 @@ * limitations under the License. */ -#include "include/instversionlist.h" +#include "instversionlist.h" +#include "instversion.h" -InstVersionList::InstVersionList() : - QObject(NULL) +InstVersionList::InstVersionList(QObject *parent) : + QObject(parent) { } + +const InstVersion *InstVersionList::findVersion(const QString &descriptor) +{ + for (int i = 0; i < count(); i++) + { + if (at(i)->descriptor() == descriptor) + return at(i); + } + return NULL; +} diff --git a/libmultimc/src/task.cpp b/libmultimc/src/task.cpp index d581a1dd..3e30827b 100644 --- a/libmultimc/src/task.cpp +++ b/libmultimc/src/task.cpp @@ -37,6 +37,11 @@ int Task::getProgress() const return progress; } +void Task::calcProgress(int parts, int whole) +{ + setProgress((int)((((float)parts) / ((float)whole))*100)); // Not sure if C++ or LISP... +} + void Task::setProgress(int progress) { this->progress = progress; diff --git a/plugins/stdinstance/CMakeLists.txt b/plugins/stdinstance/CMakeLists.txt index 7b1ff192..deebf812 100644 --- a/plugins/stdinstance/CMakeLists.txt +++ b/plugins/stdinstance/CMakeLists.txt @@ -5,6 +5,7 @@ ADD_DEFINITIONS(-DQT_PLUGIN) # Find Qt find_package(Qt5Core REQUIRED) find_package(Qt5Network REQUIRED) +find_package(Qt5Xml REQUIRED) # Include Qt headers. include_directories(${Qt5Base_INCLUDE_DIRS}) @@ -25,11 +26,15 @@ include_directories(${CMAKE_SOURCE_DIR}libinstance/include) SET(STDINST_HEADERS stdinstancetype.h stdinstance.h +stdinstversionlist.h +stdinstversion.h ) SET(STDINST_SOURCES stdinstancetype.cpp stdinstance.cpp +stdinstversionlist.cpp +stdinstversion.cpp ) add_library(stdinstance SHARED ${STDINST_SOURCES} ${STDINST_HEADERS}) @@ -41,7 +46,7 @@ IF(UNIX) set_target_properties(stdinstance PROPERTIES LIBRARY_OUTPUT_DIRECTORY "..") ENDIF() -qt5_use_modules(stdinstance Core Network) +qt5_use_modules(stdinstance Core Network Xml) target_link_libraries(stdinstance quazip patchlib diff --git a/plugins/stdinstance/stdinstance.cpp b/plugins/stdinstance/stdinstance.cpp index 077b6b15..e5d87d23 100644 --- a/plugins/stdinstance/stdinstance.cpp +++ b/plugins/stdinstance/stdinstance.cpp @@ -21,6 +21,8 @@ #include +#include "stdinstversionlist.h" + StdInstance::StdInstance(const QString &rootDir, const InstanceTypeInterface *iType, QObject *parent) : Instance(rootDir, parent) { @@ -61,3 +63,8 @@ const InstanceTypeInterface *StdInstance::instanceType() const { return m_instType; } + +InstVersionList *StdInstance::versionList() const +{ + return &vList; +} diff --git a/plugins/stdinstance/stdinstance.h b/plugins/stdinstance/stdinstance.h index 9c4b6fd6..2058453b 100644 --- a/plugins/stdinstance/stdinstance.h +++ b/plugins/stdinstance/stdinstance.h @@ -30,6 +30,8 @@ public: virtual const InstanceTypeInterface *instanceType() const; + virtual InstVersionList *versionList() const; + ////// TIMESTAMPS ////// virtual qint64 lastVersionUpdate() { return settings().get("lastVersionUpdate").value(); } virtual void setLastVersionUpdate(qint64 val) { settings().set("lastVersionUpdate", val); } diff --git a/plugins/stdinstance/stdinstancetype.cpp b/plugins/stdinstance/stdinstancetype.cpp index 93c5fd16..8ad7fd40 100644 --- a/plugins/stdinstance/stdinstancetype.cpp +++ b/plugins/stdinstance/stdinstancetype.cpp @@ -19,6 +19,7 @@ #include #include "stdinstance.h" +#include "stdinstversionlist.h" StdInstanceType::StdInstanceType(QObject *parent) : QObject(parent) @@ -26,6 +27,11 @@ StdInstanceType::StdInstanceType(QObject *parent) : } +InstVersionList *StdInstanceType::versionList() const +{ + return &vList; +} + InstanceLoader::InstTypeError StdInstanceType::createInstance(Instance *&inst, const QString &instDir) const { diff --git a/plugins/stdinstance/stdinstancetype.h b/plugins/stdinstance/stdinstancetype.h index b8382a97..4840ab51 100644 --- a/plugins/stdinstance/stdinstancetype.h +++ b/plugins/stdinstance/stdinstancetype.h @@ -34,6 +34,9 @@ public: virtual QString description() const { return "A standard Minecraft instance."; } + virtual InstVersionList *versionList() const; + +protected: virtual InstanceLoader::InstTypeError createInstance(Instance *&inst, const QString &instDir) const; virtual InstanceLoader::InstTypeError loadInstance(Instance *&inst, const QString &instDir) const; diff --git a/plugins/stdinstance/stdinstversion.cpp b/plugins/stdinstance/stdinstversion.cpp new file mode 100644 index 00000000..0e582ffc --- /dev/null +++ b/plugins/stdinstance/stdinstversion.cpp @@ -0,0 +1,147 @@ +/* Copyright 2013 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. + */ + +#include "stdinstversion.h" + +StdInstVersion::StdInstVersion(QString descriptor, + QString name, + qint64 timestamp, + QString dlUrl, + bool hasLWJGL, + QString etag, + InstVersionList *parent) : + InstVersion(parent), m_descriptor(descriptor), m_name(name), m_timestamp(timestamp), + m_dlUrl(dlUrl), m_hasLWJGL(hasLWJGL), m_etag(etag) +{ + m_linkedVersion = NULL; +} + +StdInstVersion::StdInstVersion(StdInstVersion *linkedVersion) +{ + m_linkedVersion = linkedVersion; +} + +StdInstVersion::StdInstVersion() +{ + m_timestamp = 0; + m_hasLWJGL = false; + m_linkedVersion = NULL; +} + +StdInstVersion *StdInstVersion::mcnVersion(QString rawName, QString niceName) +{ + StdInstVersion *version = new StdInstVersion; + version->m_descriptor = rawName; + version->m_name = niceName; + version->setVersionType(MCNostalgia); + return version; +} + +QString StdInstVersion::descriptor() const +{ + if (m_linkedVersion) + return m_linkedVersion->descriptor(); + return m_descriptor; +} + +QString StdInstVersion::name() const +{ + if (m_linkedVersion) + return m_linkedVersion->name(); + return m_name; +} + +QString StdInstVersion::type() const +{ + if (m_linkedVersion) + return m_linkedVersion->type(); + + 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 StdInstVersion::timestamp() const +{ + if (m_linkedVersion) + return m_linkedVersion->timestamp(); + return m_timestamp; +} + +QString StdInstVersion::downloadURL() const +{ + if (m_linkedVersion) + return m_linkedVersion->downloadURL(); + return m_dlUrl; +} + +bool StdInstVersion::hasLWJGL() const +{ + if (m_linkedVersion) + return m_linkedVersion->hasLWJGL(); + return m_hasLWJGL; +} + +QString StdInstVersion::etag() const +{ + if (m_linkedVersion) + return m_linkedVersion->etag(); + return m_etag; +} + +StdInstVersion::VersionType StdInstVersion::versionType() const +{ + return m_type; +} + +void StdInstVersion::setVersionType(StdInstVersion::VersionType type) +{ + m_type = type; +} + +bool StdInstVersion::isMeta() const +{ + return versionType() == MetaCustom || + versionType() == MetaLatestSnapshot || + versionType() == MetaLatestStable; +} diff --git a/plugins/stdinstance/stdinstversion.h b/plugins/stdinstance/stdinstversion.h new file mode 100644 index 00000000..3f03ae83 --- /dev/null +++ b/plugins/stdinstance/stdinstversion.h @@ -0,0 +1,82 @@ +/* Copyright 2013 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. + */ + +#ifndef STDINSTVERSION_H +#define STDINSTVERSION_H + +#include "instversion.h" + +/*! + * \brief Implements a standard Minecraft instance version. + * This class also supports meta versions (i.e. versions that are essentially + * aliases of other versions). + */ +class StdInstVersion : public InstVersion +{ + Q_OBJECT +public: + explicit StdInstVersion(QString descriptor, + QString name, + qint64 timestamp, + QString dlUrl, + bool hasLWJGL, + QString etag, + InstVersionList *parent); + + explicit StdInstVersion(StdInstVersion *linkedVersion); + + StdInstVersion(); + + static StdInstVersion *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 type() const; + virtual qint64 timestamp() const; + virtual QString downloadURL() const; + virtual bool hasLWJGL() const; + virtual QString etag() const; + + virtual VersionType versionType() const; + virtual void setVersionType(VersionType type); + + virtual bool isMeta() const; + + +protected: + QString m_descriptor; + QString m_name; + qint64 m_timestamp; + QString m_dlUrl; + bool m_hasLWJGL; + QString m_etag; + VersionType m_type; + + StdInstVersion *m_linkedVersion; +}; + +#endif // STDINSTVERSION_H diff --git a/plugins/stdinstance/stdinstversionlist.cpp b/plugins/stdinstance/stdinstversionlist.cpp new file mode 100644 index 00000000..4ad4c52f --- /dev/null +++ b/plugins/stdinstance/stdinstversionlist.cpp @@ -0,0 +1,509 @@ +/* Copyright 2013 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. + */ + +#include "stdinstversionlist.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include "stdinstversion.h" + +#define MCDL_URLBASE "http://assets.minecraft.net/" +#define ASSETS_URLBASE "http://s3.amazonaws.com/MinecraftDownload/" +#define MCN_URLBASE "http://sonicrules.org/mcnweb.py" + +// When this is defined, prints the entire version list to qDebug() after loading. +#define PRINT_VERSIONS + + +StdInstVersionList vList; + +StdInstVersionList::StdInstVersionList(QObject *parent) : + InstVersionList(parent) +{ + loaded = false; +} + +Task *StdInstVersionList::getLoadTask() +{ + return new StdInstVListLoadTask(this); +} + +bool StdInstVersionList::isLoaded() +{ + return loaded; +} + +const InstVersion *StdInstVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int StdInstVersionList::count() const +{ + return m_vlist.count(); +} + +void StdInstVersionList::printToStdOut() +{ + qDebug() << "---------------- Version List ----------------"; + + for (int i = 0; i < m_vlist.count(); i++) + { + StdInstVersion *version = qobject_cast(m_vlist.at(i)); + + if (!version) + continue; + + qDebug() << "Version " << version->name(); + qDebug() << "\tDownload: " << version->downloadURL(); + qDebug() << "\tTimestamp: " << version->timestamp(); + qDebug() << "\tType: " << version->type(); + qDebug() << "----------------------------------------------"; + } +} + + +StdInstVListLoadTask::StdInstVListLoadTask(StdInstVersionList *vlist) : + Task(vlist) +{ + m_list = vlist; + processedMCDLReply = false; + processedAssetsReply = false; + processedMCNReply = false; + + currentStable = NULL; + foundCurrentInAssets = false; +} + +void StdInstVListLoadTask::executeTask() +{ + setSubStatus(); + + // Initialize the network access manager. + QNetworkAccessManager netMgr; + + mcdlReply = netMgr.get(QNetworkRequest(QUrl(ASSETS_URLBASE))); + assetsReply = netMgr.get(QNetworkRequest(QUrl(MCDL_URLBASE))); + mcnReply = netMgr.get(QNetworkRequest(QUrl(QString(MCN_URLBASE) + "?pversion=1&list=True"))); + + connect(mcdlReply, SIGNAL(finished()), + SLOT(processMCDLReply())); + connect(mcnReply, SIGNAL(finished()), + SLOT(processMCNReply())); + + exec(); + finalize(); +} + +void StdInstVListLoadTask::finalize() +{ + // First, we need to do some cleanup. We loaded 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. + + // 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 MCNostalgia version list and remove anything with + // a descriptor that matches one we already have in tempList. + // We'll need a list of items we're going to remove. + 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 two lists. This is + // simple enough. + tempList.append(mcnList); + + // We're done with mcnList now, but the items have been moved over to + // tempList, so we don't need to delete them. + + // Now we swap the list we loaded into the actual version list. + // This applies our changes to the version list immediately and still gives us + // access to the old list so that we can delete the objects in it and free their memory. + // By doing this, we cause the version list to update immediately. + m_list->m_vlist.swap(tempList); + + // 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(); + +#ifdef PRINT_VERSIONS + m_list->printToStdOut(); +#endif +} + +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); +} + +void StdInstVListLoadTask::processMCDLReply() +{ + switch (mcdlReply->error()) + { + case QNetworkReply::NoError: + { + // Get the XML string. + QString xmlString = mcdlReply->readAll(); + + QString xmlErrorMsg; + + QDomDocument doc; + if (!doc.setContent(xmlString, false, &xmlErrorMsg)) + { + // TODO: Display error message to the user. + qDebug(QString("Failed to process Minecraft download site. XML error: %s"). + arg(xmlErrorMsg).toUtf8()); + } + + QDomNodeList contents = doc.elementsByTagName("Contents"); + + 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(); + QString dlUrl = "http://s3.amazonaws.com/MinecraftDownload/"; + + if (key != "minecraft.jar") + continue; + + QDateTime versionTimestamp = timeFromS3Time(lastModStr); + if (!versionTimestamp.isValid()) + { + qDebug(QString("Failed to parse timestamp for current stable version %1"). + arg(lastModStr).toUtf8()); + versionTimestamp = QDateTime::currentDateTime(); + } + + currentStable = new StdInstVersion("LatestStable", "Current", + versionTimestamp.toMSecsSinceEpoch(), + "http://s3.amazonaws.com/MinecraftDownload/", + true, etagStr, m_list); + + setSubStatus("Loaded latest version info."); + } + break; + } + + default: + // TODO: Network error handling. + break; + } + + if (!currentStable) + qDebug("Failed to get current stable version."); + + + processedMCDLReply = true; + updateStuff(); + + // If the assets request isn't finished yet, connect the slot to allow it + // to process when the request is done. Otherwise, simply call the + // processAssetsReply slot directly. + if (!assetsReply->isFinished()) + connect(assetsReply, SIGNAL(finished()), + SLOT(processAssetsReply())); + else if (!processedAssetsReply) + processAssetsReply(); +} + +void StdInstVListLoadTask::processAssetsReply() +{ + 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(QString("Failed to process assets.minecraft.net. XML error: %s"). + arg(xmlErrorMsg).toUtf8()); + } + + 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 (currentStable) + { + if (etagStr == currentStable->etag()) + { + StdInstVersion *version = new StdInstVersion( + versionName, versionName, + versionTimestamp.toMSecsSinceEpoch(), + currentStable->downloadURL(), true, etagStr, m_list); + version->setVersionType(StdInstVersion::CurrentStable); + tempList.push_back(version); + foundCurrentInAssets = true; + } + else + { + bool older = versionTimestamp.toMSecsSinceEpoch() < currentStable->timestamp(); + bool newer = versionTimestamp.toMSecsSinceEpoch() > currentStable->timestamp(); + bool isSnapshot = versionName.contains(snapshotRegex); + + StdInstVersion *version = new StdInstVersion( + versionName, versionName, + versionTimestamp.toMSecsSinceEpoch(), + dlUrl, false, etagStr, m_list); + + if (newer) + { + version->setVersionType(StdInstVersion::Snapshot); + } + else if (older && isSnapshot) + { + version->setVersionType(StdInstVersion::OldSnapshot); + } + else if (older) + { + version->setVersionType(StdInstVersion::Stable); + } + else + { + // Shouldn't happen, but just in case... + version->setVersionType(StdInstVersion::CurrentStable); + } + + tempList.push_back(version); + } + } + else // If there isn't a current stable version. + { + bool isSnapshot = versionName.contains(snapshotRegex); + + StdInstVersion *version = new StdInstVersion( + versionName, versionName, + versionTimestamp.toMSecsSinceEpoch(), + dlUrl, false, etagStr, m_list); + version->setVersionType(isSnapshot? StdInstVersion::Snapshot : + StdInstVersion::Stable); + tempList.push_back(version); + } + } + + setSubStatus("Loaded assets.minecraft.net"); + break; + } + + default: + // TODO: Network error handling. + break; + } + + processedAssetsReply = true; + updateStuff(); +} + + +QString mcnToAssetsVersion(QString mcnVersion); + +void StdInstVListLoadTask::processMCNReply() +{ + switch (assetsReply->error()) + { + case QNetworkReply::NoError: + { + QJsonParseError pError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(mcnReply->readAll(), &pError); + + if (pError.error != QJsonParseError::NoError) + { + // Handle errors. + qDebug() << "Failed to parse MCNostalgia response. JSON parser error: " << + pError.errorString(); + break; + } + + + // Load data. + QRegExp indevRegex("in(f)?dev"); + QJsonArray vlistArray = jsonDoc.object().value("order").toArray(); + + for (int i = 0; i < vlistArray.size(); i++) + { + QString rawVersion = vlistArray.at(i).toString(); + if (rawVersion.isEmpty() || rawVersion.contains(indevRegex)) + continue; + + QString niceVersion = mcnToAssetsVersion(rawVersion); + if (niceVersion.isEmpty()) + continue; + + StdInstVersion *version = StdInstVersion::mcnVersion(rawVersion, niceVersion); + mcnList.prepend(version); + } + + setSubStatus("Loaded MCNostalgia"); + break; + } + + default: + // TODO: Network error handling. + break; + } + + + processedMCNReply = true; + updateStuff(); +} + +void StdInstVListLoadTask::setSubStatus(const QString &msg) +{ + if (msg.isEmpty()) + setStatus("Loading instance version list..."); + else + setStatus("Loading instance version list: " + msg); +} + +void StdInstVListLoadTask::updateStuff() +{ + const int totalReqs = 3; + int reqsComplete = 0; + + if (processedMCDLReply) + reqsComplete++; + if (processedAssetsReply) + reqsComplete++; + if (processedMCNReply) + reqsComplete++; + + calcProgress(reqsComplete, totalReqs); + + if (reqsComplete >= totalReqs) + { + quit(); + } +} + +class MCNostalgiaVNameMap +{ +public: + QMap mapping; + MCNostalgiaVNameMap() + { + // An empty string means that it should be ignored + mapping["1.4.6_pre"] = ""; + mapping["1.4.5_pre"] = ""; + mapping["1.4.3_pre"] = "1.4.3"; + mapping["1.4.2_pre"] = ""; + mapping["1.4.1_pre"] = "1.4.1"; + mapping["1.4_pre"] = "1.4"; + mapping["1.3.2_pre"] = ""; + mapping["1.3.1_pre"] = ""; + mapping["1.3_pre"] = ""; + mapping["1.2_pre"] = "1.2"; + } +} mcnVNMap; + +QString mcnToAssetsVersion(QString mcnVersion) +{ + QMap::iterator iter = mcnVNMap.mapping.find(mcnVersion); + if (iter != mcnVNMap.mapping.end()) + { + return iter.value(); + } + return mcnVersion; +} diff --git a/plugins/stdinstance/stdinstversionlist.h b/plugins/stdinstance/stdinstversionlist.h new file mode 100644 index 00000000..1fad48c2 --- /dev/null +++ b/plugins/stdinstance/stdinstversionlist.h @@ -0,0 +1,99 @@ +/* Copyright 2013 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. + */ + +#ifndef STDINSTVERSIONLIST_H +#define STDINSTVERSIONLIST_H + +#include + +#include + +class QNetworkReply; + +class StdInstVListLoadTask; +class StdInstVersion; + +class StdInstVersionList : public InstVersionList +{ + Q_OBJECT +public: + friend class StdInstVListLoadTask; + + explicit StdInstVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + + virtual bool isLoaded(); + + virtual const InstVersion *at(int i) const; + + virtual int count() const; + + //! Prints the list to stdout. This is mainly for debugging. + virtual void printToStdOut(); + +protected: + QList m_vlist; + + bool loaded; +}; + +class StdInstVListLoadTask : public Task +{ +Q_OBJECT +public: + StdInstVListLoadTask(StdInstVersionList *vlist); + + virtual void executeTask(); + + //! Performs some final processing when the task is finished. + virtual void finalize(); + +protected slots: + //! Slot connected to the finished signal for mcdlReply. + virtual void processMCDLReply(); + + //! Slot connected to the finished signal for assetsReply. + virtual void processAssetsReply(); + + //! Slot connected to the finished signal for mcnReply. + virtual void processMCNReply(); + +protected: + void setSubStatus(const QString &msg = ""); + + StdInstVersionList *m_list; + QList tempList; //! < List of loaded versions. + QList mcnList; //! < List of MCNostalgia versions. + + QNetworkReply *assetsReply; //! < The reply from assets.minecraft.net + QNetworkReply *mcdlReply; //! < The reply from s3.amazonaws.com/MinecraftDownload + QNetworkReply *mcnReply; //! < The reply from MCNostalgia. + + bool processedAssetsReply; + bool processedMCDLReply; + bool processedMCNReply; + + //! Checks if the task is finished processing replies and, if so, exits the task's event loop. + void updateStuff(); + + StdInstVersion *currentStable; + + bool foundCurrentInAssets; +}; + +extern StdInstVersionList vList; + +#endif // STDINSTVERSIONLIST_H