diff options
author | Sky <git@bunnies.cc> | 2013-10-21 18:24:29 +0100 |
---|---|---|
committer | Sky <git@bunnies.cc> | 2013-10-21 18:24:29 +0100 |
commit | aaf6fe894406ab8aa814de83692504493060e303 (patch) | |
tree | a7ac3a60534322f70cbdc34111228885e0606691 /logic | |
parent | 6892c11e9f287dcfb1e698f8f46233a01fb7abb6 (diff) | |
parent | 11813a0621dd7b500c2d7966a2671c0ab93be692 (diff) | |
download | MultiMC-aaf6fe894406ab8aa814de83692504493060e303.tar MultiMC-aaf6fe894406ab8aa814de83692504493060e303.tar.gz MultiMC-aaf6fe894406ab8aa814de83692504493060e303.tar.lz MultiMC-aaf6fe894406ab8aa814de83692504493060e303.tar.xz MultiMC-aaf6fe894406ab8aa814de83692504493060e303.zip |
Merge branch 'develop'
Diffstat (limited to 'logic')
75 files changed, 4531 insertions, 1928 deletions
diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index e166449f..6a6b195b 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -13,6 +13,7 @@ * limitations under the License. */ +#include "MultiMC.h" #include "BaseInstance.h" #include "BaseInstance_p.h" @@ -131,9 +132,9 @@ InstanceList *BaseInstance::instList() const return NULL; } -InstVersionList *BaseInstance::versionList() const +std::shared_ptr<BaseVersionList> BaseInstance::versionList() const { - return &MinecraftVersionList::getMainList(); + return MMC->minecraftlist(); } SettingsObject &BaseInstance::settings() const diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index cc9422be..b083c24a 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -3,7 +3,7 @@ * 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 @@ -21,7 +21,8 @@ #include <settingsobject.h> #include "inifile.h" -#include "lists/InstVersionList.h" +#include "lists/BaseVersionList.h" +#include "net/LoginTask.h" class QDialog; class BaseUpdate; @@ -32,9 +33,9 @@ class BaseInstancePrivate; /*! * \brief Base class for instances. - * This class implements many functions that are common between instances and + * This class implements many functions that are common between instances and * provides a standard interface for all instances. - * + * * To create a new instance type, create a new class inheriting from this class * and implement the pure virtual functions. */ @@ -43,66 +44,72 @@ class BaseInstance : public QObject Q_OBJECT protected: /// no-touchy! - BaseInstance(BaseInstancePrivate * d, const QString &rootDir, SettingsObject * settings, QObject *parent = 0); + BaseInstance(BaseInstancePrivate *d, const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + public: /// virtual destructor to make sure the destruction is COMPLETE virtual ~BaseInstance() {}; - - /// nuke thoroughly - deletes the instance contents, notifies the list/model which is responsible of cleaning up the husk + + /// nuke thoroughly - deletes the instance contents, notifies the list/model which is + /// responsible of cleaning up the husk void nuke(); - - /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to be unique. + + /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to + /// be unique. QString id() const; - + /// get the type of this instance QString instanceType() const; - + /// Path to the instance's root directory. QString instanceRoot() const; - + /// Path to the instance's minecraft directory. QString minecraftRoot() const; - + QString name() const; void setName(QString val); - + QString iconKey() const; void setIconKey(QString val); - + QString notes() const; void setNotes(QString val); - + QString group() const; void setGroupInitial(QString val); void setGroupPost(QString val); - - + virtual QString intendedVersionId() const = 0; virtual bool setIntendedVersionId(QString version) = 0; - + + virtual bool versionIsCustom() = 0; + /*! * The instance's current version. - * This value represents the instance's current version. If this value is + * This value represents the instance's current version. If this value is * different from the intendedVersion, the instance should be updated. * \warning Don't change this value unless you know what you're doing. */ virtual QString currentVersionId() const = 0; - //virtual void setCurrentVersionId(QString val) = 0; - + // virtual void setCurrentVersionId(QString val) = 0; + /*! * Whether or not Minecraft should be downloaded when the instance is launched. */ virtual bool shouldUpdate() const = 0; virtual void setShouldUpdate(bool val) = 0; - /// Get the curent base jar of this instance. By default, it's the versions/$version/$version.jar + /// Get the curent base jar of this instance. By default, it's the + /// versions/$version/$version.jar QString baseJar() const; /// the default base jar of this instance virtual QString defaultBaseJar() const = 0; /// the default custom base jar of this instance virtual QString defaultCustomBaseJar() const = 0; - + /*! * Whether or not custom base jar is used */ @@ -113,7 +120,7 @@ public: */ QString customBaseJar() const; void setCustomBaseJar(QString val); - + /** * Gets the time that the instance was last launched. * Stored in milliseconds since epoch. @@ -121,53 +128,54 @@ public: qint64 lastLaunch() const; /// Sets the last launched time to 'val' milliseconds since epoch void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - + /*! - * \brief Gets the instance list that this instance is a part of. - * Returns NULL if this instance is not in a list + * \brief Gets the instance list that this instance is a part of. + * Returns NULL if this instance is not in a list * (the parent is not an InstanceList). - * \return A pointer to the InstanceList containing this instance. + * \return A pointer to the InstanceList containing this instance. */ InstanceList *instList() const; - + /*! * \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; - + virtual std::shared_ptr<BaseVersionList> versionList() const; + /*! * \brief Gets this instance's settings object. * This settings object stores instance-specific settings. * \return A pointer to this instance's settings object. */ virtual SettingsObject &settings() const; - + /// returns a valid update task if update is needed, NULL otherwise - virtual BaseUpdate* doUpdate() = 0; - + virtual BaseUpdate *doUpdate() = 0; + /// returns a valid minecraft process, ready for launch - virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0; - - /// do any necessary cleanups after the instance finishes. also runs before 'prepareForLaunch' + virtual MinecraftProcess *prepareForLaunch(LoginResponse response) = 0; + + /// do any necessary cleanups after the instance finishes. also runs before + /// 'prepareForLaunch' virtual void cleanupAfterRun() = 0; - + /// create a mod edit dialog for the instance - virtual QDialog * createModEditDialog ( QWidget* parent ) = 0; - + virtual QDialog *createModEditDialog(QWidget *parent) = 0; + /// is a particular action enabled with this instance selected? virtual bool menuActionEnabled(QString action_name) const = 0; - + virtual QString getStatusbarDescription() = 0; - + /// FIXME: this really should be elsewhere... virtual QString instanceConfigFolder() const = 0; - + signals: /*! * \brief Signal emitted when properties relevant to the instance view change */ - void propertiesChanged(BaseInstance * inst); + void propertiesChanged(BaseInstance *inst); /*! * \brief Signal emitted when groups are affected in any way */ @@ -175,12 +183,11 @@ signals: /*! * \brief The instance just got nuked. Hurray! */ - void nuked(BaseInstance * inst); - + void nuked(BaseInstance *inst); + protected: - QSharedPointer<BaseInstancePrivate> inst_d; + std::shared_ptr<BaseInstancePrivate> inst_d; }; // pointer for lazy people -typedef QSharedPointer<BaseInstance> InstancePtr; - +typedef std::shared_ptr<BaseInstance> InstancePtr; diff --git a/logic/BaseInstance_p.h b/logic/BaseInstance_p.h index a30916a4..06c0c0ba 100644 --- a/logic/BaseInstance_p.h +++ b/logic/BaseInstance_p.h @@ -4,7 +4,7 @@ class BaseInstance; -#define I_D(Class) Class##Private * const d = (Class##Private * const) inst_d.data() +#define I_D(Class) Class##Private * const d = (Class##Private * const) inst_d.get() struct BaseInstancePrivate { diff --git a/logic/BaseUpdate.cpp b/logic/BaseUpdate.cpp index b086ab14..02b29d32 100644 --- a/logic/BaseUpdate.cpp +++ b/logic/BaseUpdate.cpp @@ -7,7 +7,5 @@ BaseUpdate::BaseUpdate ( BaseInstance* inst, QObject* parent ) : Task ( parent ) void BaseUpdate::updateDownloadProgress(qint64 current, qint64 total) { - // The progress on the current file is current / total - float currentDLProgress = (float) current / (float) total; - setProgress((int)(currentDLProgress * 100)); // convert to percentage + emit progress(current, total); }
\ No newline at end of file diff --git a/logic/BaseVersion.h b/logic/BaseVersion.h new file mode 100644 index 00000000..01745c46 --- /dev/null +++ b/logic/BaseVersion.h @@ -0,0 +1,45 @@ +/* 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. + */ + +#pragma once +#include <memory> + +/*! + * An abstract base class for versions. + */ +struct BaseVersion +{ + /*! + * A string used to identify this version in config files. + * This should be unique within the version list or shenanigans will occur. + */ + virtual QString descriptor() = 0; + + /*! + * The name of this version as it is displayed to the user. + * For example: "1.5.1" + */ + virtual QString name() = 0; + + /*! + * This should return a string that describes + * the kind of version this is (Stable, Beta, Snapshot, whatever) + */ + virtual QString typeString() const = 0; +}; + +typedef std::shared_ptr<BaseVersion> BaseVersionPtr; + +Q_DECLARE_METATYPE( BaseVersionPtr )
\ No newline at end of file diff --git a/logic/EnabledItemFilter.cpp b/logic/EnabledItemFilter.cpp new file mode 100644 index 00000000..6ecd0271 --- /dev/null +++ b/logic/EnabledItemFilter.cpp @@ -0,0 +1,30 @@ +#include "EnabledItemFilter.h" + +EnabledItemFilter::EnabledItemFilter(QObject* parent) + :QSortFilterProxyModel(parent) +{ + +} + +void EnabledItemFilter::setActive(bool active) +{ + m_active = active; + invalidateFilter(); +} + +bool EnabledItemFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if(!m_active) + return true; + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + if(sourceModel()->flags(index) & Qt::ItemIsEnabled) + { + return true; + } + return false; +} + +bool EnabledItemFilter::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + return QSortFilterProxyModel::lessThan(left, right); +} diff --git a/logic/EnabledItemFilter.h b/logic/EnabledItemFilter.h new file mode 100644 index 00000000..cb6d4041 --- /dev/null +++ b/logic/EnabledItemFilter.h @@ -0,0 +1,16 @@ +#pragma once +#include <QSortFilterProxyModel> + +class EnabledItemFilter : public QSortFilterProxyModel +{ + Q_OBJECT +public: + EnabledItemFilter(QObject *parent = 0); + void setActive(bool active); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +private: + bool m_active = false; +};
\ No newline at end of file diff --git a/logic/ForgeInstaller.cpp b/logic/ForgeInstaller.cpp new file mode 100644 index 00000000..a946dd44 --- /dev/null +++ b/logic/ForgeInstaller.cpp @@ -0,0 +1,140 @@ +#include "ForgeInstaller.h" +#include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "net/HttpMetaCache.h" +#include <quazip.h> +#include <quazipfile.h> +#include <pathutils.h> +#include <QStringList> +#include "MultiMC.h" + +ForgeInstaller::ForgeInstaller(QString filename, QString universal_url) +{ + std::shared_ptr<OneSixVersion> newVersion; + m_universal_url = universal_url; + + QuaZip zip(filename); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + // read the install profile + if (!zip.setCurrentFile("install_profile.json")) + return; + + QJsonParseError jsonError; + if (!file.open(QIODevice::ReadOnly)) + return; + QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &jsonError); + file.close(); + if (jsonError.error != QJsonParseError::NoError) + return; + + if (!jsonDoc.isObject()) + return; + + QJsonObject root = jsonDoc.object(); + + auto installVal = root.value("install"); + auto versionInfoVal = root.value("versionInfo"); + if (!installVal.isObject() || !versionInfoVal.isObject()) + return; + + // read the forge version info + { + newVersion = OneSixVersion::fromJson(versionInfoVal.toObject()); + if (!newVersion) + return; + } + + QJsonObject installObj = installVal.toObject(); + QString libraryName = installObj.value("path").toString(); + internalPath = installObj.value("filePath").toString(); + + // where do we put the library? decode the mojang path + OneSixLibrary lib(libraryName); + lib.finalize(); + + auto cacheentry = MMC->metacache()->resolveEntry("libraries", lib.storagePath()); + finalPath = "libraries/" + lib.storagePath(); + if (!ensureFilePathExists(finalPath)) + return; + + if (!zip.setCurrentFile(internalPath)) + return; + if (!file.open(QIODevice::ReadOnly)) + return; + { + QByteArray data = file.readAll(); + // extract file + QSaveFile extraction(finalPath); + if (!extraction.open(QIODevice::WriteOnly)) + return; + if (extraction.write(data) != data.size()) + return; + if (!extraction.commit()) + return; + QCryptographicHash md5sum(QCryptographicHash::Md5); + md5sum.addData(data); + + cacheentry->stale = false; + cacheentry->md5sum = md5sum.result().toHex().constData(); + MMC->metacache()->updateEntry(cacheentry); + } + file.close(); + + m_forge_version = newVersion; + realVersionId = m_forge_version->id = installObj.value("minecraft").toString(); +} + +bool ForgeInstaller::apply(std::shared_ptr<OneSixVersion> to) +{ + if (!m_forge_version) + return false; + to->externalUpdateStart(); + int sliding_insert_window = 0; + { + // for each library in the version we are adding (except for the blacklisted) + QSet<QString> blacklist{"lwjgl", "lwjgl_util", "lwjgl-platform"}; + for (auto lib : m_forge_version->libraries) + { + QString libName = lib->name(); + // WARNING: This could actually break. + // if this is the actual forge lib, set an absolute url for the download + if (libName.contains("minecraftforge")) + { + lib->setAbsoluteUrl(m_universal_url); + } + else if (libName.contains("scala")) + { + lib->setHint("forge-pack-xz"); + } + if (blacklist.contains(libName)) + continue; + + // find an entry that matches this one + bool found = false; + for (auto tolib : to->libraries) + { + if (tolib->name() != libName) + continue; + found = true; + // replace lib + tolib = lib; + break; + } + if (!found) + { + // add lib + to->libraries.insert(sliding_insert_window, lib); + sliding_insert_window++; + } + } + to->mainClass = m_forge_version->mainClass; + to->minecraftArguments = m_forge_version->minecraftArguments; + to->processArguments = m_forge_version->processArguments; + } + to->externalUpdateFinish(); + return to->toOriginalFile(); +} diff --git a/logic/ForgeInstaller.h b/logic/ForgeInstaller.h new file mode 100644 index 00000000..f6f22a2a --- /dev/null +++ b/logic/ForgeInstaller.h @@ -0,0 +1,25 @@ +#pragma once +#include <QString> +#include <memory> + +class OneSixVersion; + +class ForgeInstaller +{ +public: + ForgeInstaller(QString filename, QString universal_url); + + bool apply(std::shared_ptr<OneSixVersion> to); + +private: + // the version, read from the installer + std::shared_ptr<OneSixVersion> m_forge_version; + QString internalPath; + QString finalPath; + QString realVersionId; + QString m_universal_url; +}; + + + + diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index f0630568..0da62803 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -22,7 +22,7 @@ #include "LegacyInstance.h" #include "OneSixInstance.h" #include "NostalgiaInstance.h" -#include "InstanceVersion.h" +#include "BaseVersion.h" #include "MinecraftVersion.h" #include "inifile.h" @@ -30,6 +30,7 @@ #include <setting.h> #include "pathutils.h" +#include <logger/QsLog.h> InstanceFactory InstanceFactory::loader; @@ -68,16 +69,16 @@ InstanceFactory::InstLoadError InstanceFactory::loadInstance(BaseInstance *&inst } -InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& inst, InstVersionPtr version, const QString& instDir ) +InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& inst, BaseVersionPtr version, const QString& instDir ) { QDir rootDir(instDir); - qDebug(instDir.toUtf8()); + QLOG_DEBUG() << instDir.toUtf8(); if (!rootDir.exists() && !rootDir.mkpath(".")) { return InstanceFactory::CantCreateDir; } - auto mcVer = version.dynamicCast<MinecraftVersion>(); + auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(version); if(!mcVer) return InstanceFactory::NoSuchVersion; @@ -89,19 +90,19 @@ InstanceFactory::InstCreateError InstanceFactory::createInstance( BaseInstance*& case MinecraftVersion::Legacy: m_settings->set("InstanceType", "Legacy"); inst = new LegacyInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::OneSix: m_settings->set("InstanceType", "OneSix"); inst = new OneSixInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; case MinecraftVersion::Nostalgia: m_settings->set("InstanceType", "Nostalgia"); inst = new NostalgiaInstance(instDir, m_settings, this); - inst->setIntendedVersionId(version->descriptor); + inst->setIntendedVersionId(version->descriptor()); inst->setShouldUseCustomBaseJar(false); break; default: diff --git a/logic/InstanceFactory.h b/logic/InstanceFactory.h index ed54f520..1c527749 100644 --- a/logic/InstanceFactory.h +++ b/logic/InstanceFactory.h @@ -19,9 +19,9 @@ #include <QMap> #include <QList> -#include "InstanceVersion.h" +#include "BaseVersion.h" -class InstVersion; +class BaseVersion; class BaseInstance; /*! @@ -61,7 +61,7 @@ public: * - InstExists if the given instance directory is already an instance. * - CantCreateDir if the given instance directory cannot be created. */ - InstCreateError createInstance(BaseInstance *&inst, InstVersionPtr version, const QString &instDir); + InstCreateError createInstance(BaseInstance *&inst, BaseVersionPtr version, const QString &instDir); /*! * \brief Loads an instance from the given directory. diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index 312f4c69..720052a3 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -3,9 +3,9 @@ #include <iostream> #include "gui/logindialog.h" -#include "gui/taskdialog.h" +#include "gui/ProgressDialog.h" #include "gui/consolewindow.h" -#include "logic/tasks/LoginTask.h" +#include "logic/net/LoginTask.h" #include "logic/MinecraftProcess.h" #include "lists/InstanceList.h" @@ -25,13 +25,13 @@ void InstanceLauncher::onLoginComplete() LoginTask * task = ( LoginTask * ) QObject::sender(); auto result = task->getResult(); auto instance = MMC->instances()->getInstanceById(instId); - proc = instance->prepareForLaunch ( result.username, result.sessionID ); + proc = instance->prepareForLaunch ( result ); if ( !proc ) { //FIXME: report error return; } - console = new ConsoleWindow(); + console = new ConsoleWindow(proc); console->show(); connect ( proc, SIGNAL ( ended() ), SLOT ( onTerminated() ) ); @@ -48,7 +48,7 @@ void InstanceLauncher::doLogin ( const QString& errorMsg ) { UserInfo uInfo {loginDlg->getUsername(), loginDlg->getPassword() }; - TaskDialog* tDialog = new TaskDialog ( nullptr ); + ProgressDialog* tDialog = new ProgressDialog ( nullptr ); LoginTask* loginTask = new LoginTask ( uInfo, tDialog ); connect ( loginTask, SIGNAL ( succeeded() ),SLOT ( onLoginComplete() ), Qt::QueuedConnection ); connect ( loginTask, SIGNAL ( failed ( QString ) ),SLOT ( doLogin ( QString ) ), Qt::QueuedConnection ); @@ -61,7 +61,7 @@ int InstanceLauncher::launch() { std::cout << "Launching Instance '" << qPrintable ( instId ) << "'" << std::endl; auto instance = MMC->instances()->getInstanceById(instId); - if ( instance.isNull() ) + if ( !instance ) { std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; return 1; diff --git a/logic/InstanceVersion.h b/logic/InstanceVersion.h deleted file mode 100644 index eecd9c4e..00000000 --- a/logic/InstanceVersion.h +++ /dev/null @@ -1,68 +0,0 @@ -/* 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. - */ - -#pragma once -#include <QSharedPointer> - -/*! - * An abstract base class for versions. - */ -struct InstVersion -{ - /*! - * Checks if this version is less (older) than the given version. - * \param other The version to compare this one to. - * \return True if this version is older than the given version. - */ - virtual bool operator<(const InstVersion &rhs) const - { - return timestamp < rhs.timestamp; - } - - /*! - * Checks if this version is greater (newer) than the given version. - * \param other The version to compare this one to. - * \return True if this version is newer than the given version. - */ - virtual bool operator>( const InstVersion& rhs ) const - { - return timestamp > rhs.timestamp; - } - - /*! - * A string used to identify this version in config files. - * This should be unique within the version list or shenanigans will occur. - */ - QString descriptor; - /*! - * The name of this version as it is displayed to the user. - * For example: "1.5.1" - */ - QString name; - /*! - * Gets the version's timestamp. - * This is primarily used for sorting versions in a list. - */ - qint64 timestamp; - - virtual QString typeString() const - { - return "InstVersion"; - } -}; - -typedef QSharedPointer<InstVersion> InstVersionPtr; - -Q_DECLARE_METATYPE( InstVersionPtr )
\ No newline at end of file diff --git a/logic/JavaUtils.cpp b/logic/JavaUtils.cpp new file mode 100644 index 00000000..8e9c984f --- /dev/null +++ b/logic/JavaUtils.cpp @@ -0,0 +1,183 @@ +/* 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 "JavaUtils.h" +#include "pathutils.h" +#include "MultiMC.h" + +#include <QStringList> +#include <QString> +#include <QDir> +#include <QMessageBox> +#include <logger/QsLog.h> +#include <gui/versionselectdialog.h> +#include <setting.h> + +JavaUtils::JavaUtils() +{ + +} + +JavaVersionPtr JavaUtils::GetDefaultJava() +{ + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = "java"; + javaVersion->arch = "unknown"; + javaVersion->path = "java"; + javaVersion->recommended = false; + + return javaVersion; +} + +#if WINDOWS +QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) +{ + QList<JavaVersionPtr> javas; + + QString archType = "unknown"; + if(keyType == KEY_WOW64_64KEY) archType = "64"; + else if(keyType == KEY_WOW64_32KEY) archType = "32"; + + HKEY jreKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) + { + // Read the current type version from the registry. + // This will be used to find any key that contains the JavaHome value. + char *value = new char[0]; + DWORD valueSz = 0; + if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE*)value, &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE*)value, &valueSz); + } + + QString recommended = value; + + TCHAR subKeyName[255]; + DWORD subKeyNameSize, numSubKeys, retCode; + + // Get the number of subkeys + RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + + // Iterate until RegEnumKeyEx fails + if(numSubKeys > 0) + { + for(int i = 0; i < numSubKeys; i++) + { + subKeyNameSize = 255; + retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL); + if(retCode == ERROR_SUCCESS) + { + // Now open the registry key for the version that we just got. + QString newKeyName = keyName + "\\" + subKeyName; + + HKEY newKey; + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) + { + // Read the JavaHome value to find where Java is installed. + value = new char[0]; + valueSz = 0; + if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE*)value, &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE*)value, &valueSz); + + // Now, we construct the version object and add it to the list. + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = subKeyName; + javaVersion->arch = archType; + javaVersion->path = QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); + javaVersion->recommended = (recommended == subKeyName); + javas.append(javaVersion); + } + + RegCloseKey(newKey); + } + } + } + } + + RegCloseKey(jreKey); + } + + return javas; +} + +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QList<JavaVersionPtr> javas; + + QList<JavaVersionPtr> JRE64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList<JavaVersionPtr> JDK64s = this->FindJavaFromRegistryKey(KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + QList<JavaVersionPtr> JRE32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList<JavaVersionPtr> JDK32s = this->FindJavaFromRegistryKey(KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + + javas.append(JRE64s); + javas.append(JDK64s); + javas.append(JRE32s); + javas.append(JDK32s); + + if(javas.size() <= 0) + { + QLOG_WARN() << "Failed to find Java in the Windows registry - defaulting to \"java\""; + javas.append(this->GetDefaultJava()); + return javas; + } + + QLOG_INFO() << "Found the following Java installations (64 -> 32, JRE -> JDK): "; + + for(auto &java : javas) + { + QString sRec; + if(java->recommended) sRec = "(Recommended)"; + QLOG_INFO() << java->id << java->arch << " at " << java->path << sRec; + } + + return javas; +} +#elif OSX +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "OS X Java detection incomplete - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} + +#elif LINUX +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} +#else +QList<JavaVersionPtr> JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Unknown operating system build - defaulting to \"java\""; + + QList<JavaVersionPtr> javas; + javas.append(this->GetDefaultJava()); + + return javas; +} +#endif diff --git a/logic/JavaUtils.h b/logic/JavaUtils.h new file mode 100644 index 00000000..e4f777d0 --- /dev/null +++ b/logic/JavaUtils.h @@ -0,0 +1,40 @@ +/* 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. + */ + +#pragma once + +#include <QStringList> +#include <QWidget> +#include <logic/lists/JavaVersionList.h> +#include "osutils.h" + +#if WINDOWS + #include <windows.h> +#endif + +class JavaUtils +{ +public: + JavaUtils(); + + QList<JavaVersionPtr> FindJavaPaths(); + JavaVersionPtr GetDefaultJava(); + +private: + +#if WINDOWS + QList<JavaVersionPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName); +#endif +}; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 0672d2c8..f741caad 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -14,8 +14,9 @@ #define LAUNCHER_FILE "MultiMCLauncher.jar" -LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, QObject* parent) - :BaseInstance( new LegacyInstancePrivate(),rootDir, settings, parent) +LegacyInstance::LegacyInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent) + : BaseInstance(new LegacyInstancePrivate(), rootDir, settings, parent) { settings->registerSetting(new Setting("NeedsRebuild", true)); settings->registerSetting(new Setting("ShouldUpdate", false)); @@ -24,97 +25,96 @@ LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, settings->registerSetting(new Setting("IntendedJarVersion", "")); } -BaseUpdate* LegacyInstance::doUpdate() +BaseUpdate *LegacyInstance::doUpdate() { + auto list = jarModList(); return new LegacyUpdate(this, this); } -MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session) +MinecraftProcess *LegacyInstance::prepareForLaunch(LoginResponse response) { - MinecraftProcess * proc = new MinecraftProcess(this); - + MinecraftProcess *proc = new MinecraftProcess(this); + QIcon icon = MMC->icons()->getIcon(iconKey()); - auto pixmap = icon.pixmap(128,128); - pixmap.save(PathCombine(minecraftRoot(), "icon.png"),"PNG"); - + auto pixmap = icon.pixmap(128, 128); + pixmap.save(PathCombine(minecraftRoot(), "icon.png"), "PNG"); + // extract the legacy launcher QFile(":/launcher/launcher.jar").copy(PathCombine(minecraftRoot(), LAUNCHER_FILE)); - + // set the process arguments { QStringList args; - + // window size QString windowSize; if (settings().get("LaunchMaximized").toBool()) windowSize = "max"; else - windowSize = QString("%1x%2"). - arg(settings().get("MinecraftWinWidth").toInt()). - arg(settings().get("MinecraftWinHeight").toInt()); - + windowSize = QString("%1x%2").arg(settings().get("MinecraftWinWidth").toInt()).arg( + settings().get("MinecraftWinHeight").toInt()); + // window title QString windowTitle; windowTitle.append("MultiMC: ").append(name()); - + // Java arguments args.append(Util::Commandline::splitArgs(settings().get("JvmArgs").toString())); - + #ifdef OSX // OSX dock icon and name args << "-Xdock:icon=icon.png"; args << QString("-Xdock:name=\"%1\"").arg(windowTitle); #endif - - QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()).absolutePath(); - + + QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()) + .absolutePath(); + // launcher arguments args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt()); args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); args << "-jar" << LAUNCHER_FILE; - args << user; - args << session; + args << response.player_name; + args << response.session_id; args << windowTitle; args << windowSize; args << lwjgl; proc->setMinecraftArguments(args); } - + // set the process work path proc->setMinecraftWorkdir(minecraftRoot()); - + return proc; } void LegacyInstance::cleanupAfterRun() { - //FIXME: delete the launcher and icons and whatnot. + // FIXME: delete the launcher and icons and whatnot. } -QSharedPointer< ModList > LegacyInstance::coreModList() +std::shared_ptr<ModList> LegacyInstance::coreModList() { I_D(LegacyInstance); - if(!d->core_mod_list) + if (!d->core_mod_list) { d->core_mod_list.reset(new ModList(coreModsDir())); } - else - d->core_mod_list->update(); + d->core_mod_list->update(); return d->core_mod_list; } -QSharedPointer< ModList > LegacyInstance::jarModList() +std::shared_ptr<ModList> LegacyInstance::jarModList() { I_D(LegacyInstance); - if(!d->jar_mod_list) + if (!d->jar_mod_list) { auto list = new ModList(jarModsDir(), modListFile()); connect(list, SIGNAL(changed()), SLOT(jarModsChanged())); d->jar_mod_list.reset(list); } - else - d->jar_mod_list->update(); + d->jar_mod_list->update(); return d->jar_mod_list; } @@ -123,38 +123,33 @@ void LegacyInstance::jarModsChanged() setShouldRebuild(true); } - -QSharedPointer< ModList > LegacyInstance::loaderModList() +std::shared_ptr<ModList> LegacyInstance::loaderModList() { I_D(LegacyInstance); - if(!d->loader_mod_list) + if (!d->loader_mod_list) { d->loader_mod_list.reset(new ModList(loaderModsDir())); } - else - d->loader_mod_list->update(); + d->loader_mod_list->update(); return d->loader_mod_list; } -QSharedPointer< ModList > LegacyInstance::texturePackList() +std::shared_ptr<ModList> LegacyInstance::texturePackList() { I_D(LegacyInstance); - if(!d->texture_pack_list) + if (!d->texture_pack_list) { d->texture_pack_list.reset(new ModList(texturePacksDir())); } - else - d->texture_pack_list->update(); + d->texture_pack_list->update(); return d->texture_pack_list; } - -QDialog * LegacyInstance::createModEditDialog ( QWidget* parent ) +QDialog *LegacyInstance::createModEditDialog(QWidget *parent) { return new LegacyModEditDialog(this, parent); } - QString LegacyInstance::jarModsDir() const { return PathCombine(instanceRoot(), "instMods"); @@ -204,7 +199,6 @@ QString LegacyInstance::instanceConfigFolder() const return PathCombine(minecraftRoot(), "config"); } - /* bool LegacyInstance::shouldUpdateCurrentVersion() const { @@ -215,21 +209,22 @@ bool LegacyInstance::shouldUpdateCurrentVersion() const void LegacyInstance::updateCurrentVersion(bool keepCurrent) { QFileInfo jar(runnableJar()); - + if(!jar.exists()) { setLastCurrentVersionUpdate(0); setCurrentVersionId("Unknown"); return; } - + qint64 time = jar.lastModified().toUTC().toMSecsSinceEpoch(); - + setLastCurrentVersionUpdate(time); if (!keepCurrent) { // TODO: Implement GetMinecraftJarVersion function. - QString newVersion = "Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath()); + QString newVersion = +"Unknown";//javautils::GetMinecraftJarVersion(jar.absoluteFilePath()); setCurrentVersionId(newVersion); } } @@ -247,41 +242,41 @@ void LegacyInstance::setLastCurrentVersionUpdate ( qint64 val ) bool LegacyInstance::shouldRebuild() const { I_D(LegacyInstance); - return d->m_settings->get ( "NeedsRebuild" ).toBool(); + return d->m_settings->get("NeedsRebuild").toBool(); } -void LegacyInstance::setShouldRebuild ( bool val ) +void LegacyInstance::setShouldRebuild(bool val) { I_D(LegacyInstance); - d->m_settings->set ( "NeedsRebuild", val ); + d->m_settings->set("NeedsRebuild", val); } QString LegacyInstance::currentVersionId() const { I_D(LegacyInstance); - return d->m_settings->get ( "JarVersion" ).toString(); + return d->m_settings->get("JarVersion").toString(); } -void LegacyInstance::setCurrentVersionId ( QString val ) +void LegacyInstance::setCurrentVersionId(QString val) { I_D(LegacyInstance); - d->m_settings->set ( "JarVersion", val ); + d->m_settings->set("JarVersion", val); } QString LegacyInstance::lwjglVersion() const { I_D(LegacyInstance); - return d->m_settings->get ( "LwjglVersion" ).toString(); + return d->m_settings->get("LwjglVersion").toString(); } -void LegacyInstance::setLWJGLVersion ( QString val ) +void LegacyInstance::setLWJGLVersion(QString val) { I_D(LegacyInstance); - d->m_settings->set ( "LwjglVersion", val ); + d->m_settings->set("LwjglVersion", val); } QString LegacyInstance::intendedVersionId() const { I_D(LegacyInstance); - return d->m_settings->get ( "IntendedJarVersion" ).toString(); + return d->m_settings->get("IntendedJarVersion").toString(); } -bool LegacyInstance::setIntendedVersionId ( QString version ) +bool LegacyInstance::setIntendedVersionId(QString version) { settings().set("IntendedJarVersion", version); setShouldUpdate(true); @@ -290,16 +285,16 @@ bool LegacyInstance::setIntendedVersionId ( QString version ) bool LegacyInstance::shouldUpdate() const { I_D(LegacyInstance); - QVariant var = settings().get ( "ShouldUpdate" ); - if ( !var.isValid() || var.toBool() == false ) + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) { return intendedVersionId() != currentVersionId(); } return true; } -void LegacyInstance::setShouldUpdate ( bool val ) +void LegacyInstance::setShouldUpdate(bool val) { - settings().set ( "ShouldUpdate", val ); + settings().set("ShouldUpdate", val); } QString LegacyInstance::defaultBaseJar() const @@ -312,14 +307,16 @@ QString LegacyInstance::defaultCustomBaseJar() const return PathCombine(binDir(), "mcbackup.jar"); } -bool LegacyInstance::menuActionEnabled ( QString action_name ) const +bool LegacyInstance::menuActionEnabled(QString action_name) const { + if (action_name == "actionChangeInstMCVersion") + return false; return true; } QString LegacyInstance::getStatusbarDescription() { - if(shouldUpdate()) + if (shouldUpdate()) return "Legacy : " + currentVersionId() + " -> " + intendedVersionId(); else return "Legacy : " + currentVersionId(); diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h index b36026fc..8bf334f6 100644 --- a/logic/LegacyInstance.h +++ b/logic/LegacyInstance.h @@ -9,21 +9,22 @@ class LegacyInstance : public BaseInstance { Q_OBJECT public: - - explicit LegacyInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); - + + explicit LegacyInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + /// Path to the instance's minecraft.jar QString runnableJar() const; - + //! Path to the instance's modlist file. QString modListFile() const; - + ////// Mod Lists ////// - QSharedPointer<ModList> jarModList(); - QSharedPointer<ModList> coreModList(); - QSharedPointer<ModList> loaderModList(); - QSharedPointer<ModList> texturePackList(); - + std::shared_ptr<ModList> jarModList(); + std::shared_ptr<ModList> coreModList(); + std::shared_ptr<ModList> loaderModList(); + std::shared_ptr<ModList> texturePackList(); + ////// Directories ////// QString savesDir() const; QString texturePacksDir() const; @@ -33,40 +34,47 @@ public: QString coreModsDir() const; QString resourceDir() const; virtual QString instanceConfigFolder() const; - + /*! * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be + * If this is true, when the instance launches, its jar mods will be * re-added to a fresh minecraft.jar file. */ bool shouldRebuild() const; void setShouldRebuild(bool val); - + virtual QString currentVersionId() const; virtual void setCurrentVersionId(QString val); - + //! The version of LWJGL that this instance uses. QString lwjglVersion() const; /// st the version of LWJGL libs this instance will use void setLWJGLVersion(QString val); - + virtual QString intendedVersionId() const; - virtual bool setIntendedVersionId ( QString version ); - + virtual bool setIntendedVersionId(QString version); + // the `version' of Legacy instances is defined by the launcher code. + // in contrast with OneSix, where `version' is described in a json file + virtual bool versionIsCustom() override + { + return false; + }; + virtual bool shouldUpdate() const; virtual void setShouldUpdate(bool val); - virtual BaseUpdate* doUpdate(); - - virtual MinecraftProcess* prepareForLaunch( QString user, QString session ); + virtual BaseUpdate *doUpdate(); + + virtual MinecraftProcess *prepareForLaunch(LoginResponse response); virtual void cleanupAfterRun(); - virtual QDialog * createModEditDialog ( QWidget* parent ); - + virtual QDialog *createModEditDialog(QWidget *parent); + virtual QString defaultBaseJar() const; virtual QString defaultCustomBaseJar() const; - - bool menuActionEnabled ( QString action_name ) const; + + bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription(); - -protected slots: + +protected +slots: virtual void jarModsChanged(); };
\ No newline at end of file diff --git a/logic/LegacyInstance_p.h b/logic/LegacyInstance_p.h index d1f417fe..0809b8d2 100644 --- a/logic/LegacyInstance_p.h +++ b/logic/LegacyInstance_p.h @@ -9,8 +9,8 @@ class ModList; struct LegacyInstancePrivate: public BaseInstancePrivate { - QSharedPointer<ModList> jar_mod_list; - QSharedPointer<ModList> core_mod_list; - QSharedPointer<ModList> loader_mod_list; - QSharedPointer<ModList> texture_pack_list; + std::shared_ptr<ModList> jar_mod_list; + std::shared_ptr<ModList> core_mod_list; + std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> texture_pack_list; };
\ No newline at end of file diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 626ad1e0..66b4bf8a 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -9,9 +9,11 @@ #include <quazip.h> #include <quazipfile.h> #include <JlCompress.h> +#include <logger/QsLog.h> - -LegacyUpdate::LegacyUpdate ( BaseInstance* inst, QObject* parent ) : BaseUpdate ( inst, parent ) {} +LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : BaseUpdate(inst, parent) +{ +} void LegacyUpdate::executeTask() { @@ -20,85 +22,89 @@ void LegacyUpdate::executeTask() void LegacyUpdate::lwjglStart() { - LegacyInstance * inst = (LegacyInstance *) m_inst; + LegacyInstance *inst = (LegacyInstance *)m_inst; + + lwjglVersion = inst->lwjglVersion(); + lwjglTargetPath = PathCombine("lwjgl", lwjglVersion); + lwjglNativesPath = PathCombine(lwjglTargetPath, "natives"); - lwjglVersion = inst->lwjglVersion(); - lwjglTargetPath = PathCombine("lwjgl", lwjglVersion ); - lwjglNativesPath = PathCombine( lwjglTargetPath, "natives"); - // if the 'done' file exists, we don't have to download this again QFileInfo doneFile(PathCombine(lwjglTargetPath, "done")); - if(doneFile.exists()) + if (doneFile.exists()) { jarStart(); return; } - - auto &list = LWJGLVersionList::get(); - if(!list.isLoaded()) + + auto list = MMC->lwjgllist(); + if (!list->isLoaded()) { emitFailed("Too soon! Let the LWJGL list load :)"); return; } - + setStatus("Downloading new LWJGL."); - auto version = list.getVersion(lwjglVersion); - if(!version) + auto version = list->getVersion(lwjglVersion); + if (!version) { emitFailed("Game update failed: the selected LWJGL version is invalid."); return; } - + QString url = version->url(); QUrl realUrl(url); QString hostname = realUrl.host(); auto worker = MMC->qnam(); QNetworkRequest req(realUrl); req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker->get ( req ); - - m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater); - connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); - connect(worker, SIGNAL(finished(QNetworkReply*)), SLOT(lwjglFinished(QNetworkReply*))); - //connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + QNetworkReply *rep = worker->get(req); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), + SLOT(lwjglFinished(QNetworkReply *))); + // connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + // SLOT(downloadError(QNetworkReply::NetworkError))); } -void LegacyUpdate::lwjglFinished(QNetworkReply* reply) +void LegacyUpdate::lwjglFinished(QNetworkReply *reply) { - if(m_reply != reply) + if (m_reply.get() != reply) { return; } - if(reply->error() != QNetworkReply::NoError) + if (reply->error() != QNetworkReply::NoError) { - emitFailed( "Failed to download: "+ - reply->errorString()+ - "\nSometimes you have to wait a bit if you download many LWJGL versions in a row. YMMV"); + emitFailed("Failed to download: " + reply->errorString() + + "\nSometimes you have to wait a bit if you download many LWJGL versions in " + "a row. YMMV"); return; } - auto *worker = MMC->qnam(); - //Here i check if there is a cookie for me in the reply and extract it - QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); - if(cookies.count() != 0) + auto worker = MMC->qnam(); + // Here i check if there is a cookie for me in the reply and extract it + QList<QNetworkCookie> cookies = + qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader)); + if (cookies.count() != 0) { - //you must tell which cookie goes with which url + // you must tell which cookie goes with which url worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); } - //here you can check for the 302 or whatever other header i need + // here you can check for the 302 or whatever other header i need QVariant newLoc = reply->header(QNetworkRequest::LocationHeader); - if(newLoc.isValid()) + if (newLoc.isValid()) { QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString(); QUrl realUrl(redirectedTo); QString hostname = realUrl.host(); QNetworkRequest req(redirectedTo); req.setRawHeader("Host", hostname.toLatin1()); - req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker->get(req); - connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); - m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater); + req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + QNetworkReply *rep = worker->get(req); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); + m_reply = std::shared_ptr<QNetworkReply>(rep); return; } QFile saveMe("lwjgl.zip"); @@ -114,26 +120,26 @@ void LegacyUpdate::extractLwjgl() // make sure the directories are there bool success = ensureFolderPathExists(lwjglNativesPath); - - if(!success) + + if (!success) { emitFailed("Failed to extract the lwjgl libs - error when creating required folders."); return; } - + QuaZip zip("lwjgl.zip"); - if(!zip.open(QuaZip::mdUnzip)) + if (!zip.open(QuaZip::mdUnzip)) { emitFailed("Failed to extract the lwjgl libs - not a valid archive."); return; } - + // and now we are going to access files inside it QuaZipFile file(&zip); - const QString jarNames[] = { "jinput.jar", "lwjgl_util.jar", "lwjgl.jar" }; - for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) + const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"}; + for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); emitFailed("Failed to extract the lwjgl libs - error while reading archive."); @@ -141,7 +147,7 @@ void LegacyUpdate::extractLwjgl() } QuaZipFileInfo info; QString name = file.getActualFileName(); - if(name.endsWith('/')) + if (name.endsWith('/')) { file.close(); continue; @@ -156,25 +162,25 @@ void LegacyUpdate::extractLwjgl() } } // Not found? look for the natives - if(destFileName.isEmpty()) + if (destFileName.isEmpty()) { #ifdef Q_OS_WIN32 QString nativesDir = "windows"; #else - #ifdef Q_OS_MAC +#ifdef Q_OS_MAC QString nativesDir = "macosx"; - #else +#else QString nativesDir = "linux"; - #endif +#endif #endif if (name.contains(nativesDir)) { int lastSlash = name.lastIndexOf('/'); - int lastBackSlash = name.lastIndexOf('/'); - if(lastSlash != -1) - name = name.mid(lastSlash+1); - else if(lastBackSlash != -1) - name = name.mid(lastBackSlash+1); + int lastBackSlash = name.lastIndexOf('\\'); + if (lastSlash != -1) + name = name.mid(lastSlash + 1); + else if (lastBackSlash != -1) + name = name.mid(lastBackSlash + 1); destFileName = PathCombine(lwjglNativesPath, name); } } @@ -190,7 +196,7 @@ void LegacyUpdate::extractLwjgl() file.close(); // do not forget to close! } zip.close(); - m_reply.clear(); + m_reply.reset(); QFile doneFile(PathCombine(lwjglTargetPath, "done")); doneFile.open(QIODevice::WriteOnly); doneFile.write("done."); @@ -204,13 +210,13 @@ void LegacyUpdate::lwjglFailed() void LegacyUpdate::jarStart() { - LegacyInstance * inst = (LegacyInstance *) m_inst; - if(!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) + LegacyInstance *inst = (LegacyInstance *)m_inst; + if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar()) { ModTheJar(); return; } - + setStatus("Checking for jar updates..."); // Make directories QDir binDir(inst->binDir()); @@ -226,11 +232,13 @@ void LegacyUpdate::jarStart() QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); QString intended_version_id = inst->intendedVersionId(); urlstr += intended_version_id + "/" + intended_version_id + ".jar"; - - legacyDownloadJob.reset(new DownloadJob(QUrl(urlstr), inst->defaultBaseJar())); - connect(legacyDownloadJob.data(), SIGNAL(finished()), SLOT(jarFinished())); - connect(legacyDownloadJob.data(), SIGNAL(failed()), SLOT(jarFailed())); - connect(legacyDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + + auto dljob = new DownloadJob("Minecraft.jar for version " + intended_version_id); + dljob->addFileDownload(QUrl(urlstr), inst->defaultBaseJar()); + legacyDownloadJob.reset(dljob); + connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished())); + connect(dljob, SIGNAL(failed()), SLOT(jarFailed())); + connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); legacyDownloadJob->start(); } @@ -246,34 +254,36 @@ void LegacyUpdate::jarFailed() emitFailed("Failed to download the minecraft jar. Try again later."); } -bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& contained, MetainfAction metainf ) +bool LegacyUpdate::MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, + MetainfAction metainf) { setStatus("Installing mods - Adding " + from.fileName()); - + QuaZip modZip(from.filePath()); modZip.open(QuaZip::mdUnzip); - + QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile( into ); - for(bool more=modZip.goToFirstFile(); more; more=modZip.goToNextFile()) + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) { QString filename = modZip.getCurrentFileName(); - if(filename.contains("META-INF") && metainf == LegacyUpdate::IgnoreMetainf) + if (filename.contains("META-INF") && metainf == LegacyUpdate::IgnoreMetainf) { - qDebug() << "Skipping META-INF " << filename << " from " << from.fileName(); + QLOG_INFO() << "Skipping META-INF " << filename << " from " << from.fileName(); continue; } - if(contained.contains(filename)) + if (contained.contains(filename)) { - qDebug() << "Skipping already contained file " << filename << " from " << from.fileName(); + QLOG_INFO() << "Skipping already contained file " << filename << " from " + << from.fileName(); continue; } contained.insert(filename); - qDebug() << "Adding file " << filename << " from " << from.fileName(); - - if(!fileInsideMod.open(QIODevice::ReadOnly)) + QLOG_INFO() << "Adding file " << filename << " from " << from.fileName(); + + if (!fileInsideMod.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open " << filename << " from " << from.fileName(); + QLOG_ERROR() << "Failed to open " << filename << " from " << from.fileName(); return false; } /* @@ -284,17 +294,17 @@ bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& /* info_out.externalAttr = old_info.externalAttr; */ - if(!zipOutFile.open(QIODevice::WriteOnly, info_out)) + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) { - qDebug() << "Failed to open " << filename << " in the jar"; + QLOG_ERROR() << "Failed to open " << filename << " in the jar"; fileInsideMod.close(); return false; } - if(!JlCompress::copyData(fileInsideMod, zipOutFile)) + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) { zipOutFile.close(); fileInsideMod.close(); - qDebug() << "Failed to copy data of " << filename << " into the jar"; + QLOG_ERROR() << "Failed to copy data of " << filename << " into the jar"; return false; } zipOutFile.close(); @@ -305,34 +315,34 @@ bool LegacyUpdate::MergeZipFiles( QuaZip* into, QFileInfo from, QSet< QString >& void LegacyUpdate::ModTheJar() { - LegacyInstance * inst = (LegacyInstance *) m_inst; - - if(!inst->shouldRebuild()) + LegacyInstance *inst = (LegacyInstance *)m_inst; + + if (!inst->shouldRebuild()) { emitSucceeded(); return; } - + // Get the mod list auto modList = inst->jarModList(); - - QFileInfo runnableJar (inst->runnableJar()); - QFileInfo baseJar (inst->baseJar()); + + QFileInfo runnableJar(inst->runnableJar()); + QFileInfo baseJar(inst->baseJar()); bool base_is_custom = inst->shouldUseCustomBaseJar(); - + // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if(base_is_custom) + if (base_is_custom) { // yes, this can happen if the instance only has the runnable jar and not the base jar // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe // because that's not something mmc4 guarantees - if(runnableJar.isFile() && !baseJar.exists() && modList->empty()) + if (runnableJar.isFile() && !baseJar.exists() && modList->empty()) { inst->setShouldRebuild(false); emitSucceeded(); return; } - + setStatus("Installing mods - backing up minecraft.jar..."); if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) { @@ -340,24 +350,24 @@ void LegacyUpdate::ModTheJar() return; } } - + if (!baseJar.exists()) { emitFailed("The base jar " + baseJar.filePath() + " does not exist"); return; } - + if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) { emitFailed("Failed to delete old minecraft.jar"); return; } - - //TaskStep(); // STEP 1 + + // TaskStep(); // STEP 1 setStatus("Installing mods - Opening minecraft.jar"); QuaZip zipOut(runnableJar.filePath()); - if(!zipOut.open(QuaZip::mdCreate)) + if (!zipOut.open(QuaZip::mdCreate)) { QFile::remove(runnableJar.filePath()); emitFailed("Failed to open the minecraft.jar for modding"); @@ -374,7 +384,7 @@ void LegacyUpdate::ModTheJar() auto &mod = modList->operator[](i); if (mod.type() == Mod::MOD_ZIPFILE) { - if(!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf)) + if (!MergeZipFiles(&zipOut, mod.filename(), addedFiles, LegacyUpdate::KeepMetainf)) { zipOut.close(); QFile::remove(runnableJar.filePath()); @@ -385,7 +395,8 @@ void LegacyUpdate::ModTheJar() else if (mod.type() == Mod::MOD_SINGLEFILE) { auto filename = mod.filename(); - if(!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) + if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), + filename.fileName())) { zipOut.close(); QFile::remove(runnableJar.filePath()); @@ -393,7 +404,8 @@ void LegacyUpdate::ModTheJar() return; } addedFiles.insert(filename.fileName()); - qDebug() << "Adding file " << filename.fileName() << " from " << filename.absoluteFilePath(); + QLOG_INFO() << "Adding file " << filename.fileName() << " from " + << filename.absoluteFilePath(); } else if (mod.type() == Mod::MOD_FOLDER) { @@ -402,35 +414,36 @@ void LegacyUpdate::ModTheJar() QDir dir(what_to_zip); dir.cdUp(); QString parent_dir = dir.absolutePath(); - if(!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles)) + if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, true, addedFiles)) { zipOut.close(); QFile::remove(runnableJar.filePath()); emitFailed("Failed to add " + filename.fileName() + " to the jar"); return; } - qDebug() << "Adding folder " << filename.fileName() << " from " << filename.absoluteFilePath(); + QLOG_INFO() << "Adding folder " << filename.fileName() << " from " + << filename.absoluteFilePath(); } } - - if(!MergeZipFiles(&zipOut, baseJar, addedFiles, LegacyUpdate::IgnoreMetainf)) + + if (!MergeZipFiles(&zipOut, baseJar, addedFiles, LegacyUpdate::IgnoreMetainf)) { zipOut.close(); QFile::remove(runnableJar.filePath()); emitFailed("Failed to insert minecraft.jar contents."); return; } - + // Recompress the jar zipOut.close(); - if(zipOut.getZipError()!=0) + if (zipOut.getZipError() != 0) { QFile::remove(runnableJar.filePath()); emitFailed("Failed to finalize minecraft.jar!"); return; - } + } inst->setShouldRebuild(false); - //inst->UpdateVersion(true); + // inst->UpdateVersion(true); emitSucceeded(); return; }
\ No newline at end of file diff --git a/logic/LegacyUpdate.h b/logic/LegacyUpdate.h index 05c00495..e84ec56a 100644 --- a/logic/LegacyUpdate.h +++ b/logic/LegacyUpdate.h @@ -56,7 +56,7 @@ private: bool MergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString>& contained, MetainfAction metainf); private: - QSharedPointer<QNetworkReply> m_reply; + std::shared_ptr<QNetworkReply> m_reply; // target version, determined during this task // MinecraftVersion *targetVersion; diff --git a/logic/MinecraftProcess.cpp b/logic/MinecraftProcess.cpp index d34be835..06b7a1f1 100644 --- a/logic/MinecraftProcess.cpp +++ b/logic/MinecraftProcess.cpp @@ -32,46 +32,45 @@ #define IBUS "@im=ibus" // constructor -MinecraftProcess::MinecraftProcess( BaseInstance* inst ) : - m_instance(inst) +MinecraftProcess::MinecraftProcess(BaseInstance *inst) : m_instance(inst) { - connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(finish(int, QProcess::ExitStatus))); - + connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), + SLOT(finish(int, QProcess::ExitStatus))); + // prepare the process environment QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - + #ifdef LINUX // Strip IBus if (env.value("XMODIFIERS").contains(IBUS)) env.insert("XMODIFIERS", env.value("XMODIFIERS").replace(IBUS, "")); #endif - + // export some infos env.insert("INST_NAME", inst->name()); env.insert("INST_ID", inst->id()); env.insert("INST_DIR", QDir(inst->instanceRoot()).absolutePath()); - + this->setProcessEnvironment(env); m_prepostlaunchprocess.setProcessEnvironment(env); - + // std channels connect(this, SIGNAL(readyReadStandardError()), SLOT(on_stdErr())); connect(this, SIGNAL(readyReadStandardOutput()), SLOT(on_stdOut())); } -void MinecraftProcess::setMinecraftArguments ( QStringList args ) +void MinecraftProcess::setMinecraftArguments(QStringList args) { m_args = args; } -void MinecraftProcess::setMinecraftWorkdir ( QString path ) +void MinecraftProcess::setMinecraftWorkdir(QString path) { QDir mcDir(path); this->setWorkingDirectory(mcDir.absolutePath()); m_prepostlaunchprocess.setWorkingDirectory(mcDir.absolutePath()); } - // console window void MinecraftProcess::on_stdErr() { @@ -80,18 +79,14 @@ void MinecraftProcess::on_stdErr() m_err_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - MessageLevel::Enum level = MessageLevel::Error; - if(line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || line.contains("[FINER]") || line.contains("[FINEST]") ) - level = MessageLevel::Message; - if(line.contains("[SEVERE]") || line.contains("[WARNING]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - emit log(lines[i].toLocal8Bit(), level); + QString &line = lines[i]; + emit log(line /*.replace(username, "<Username>").replace(sessionID, "<Session ID>")*/, + getLevel(line, MessageLevel::Error)); } - if(!complete) + if (!complete) m_err_leftover = lines.last(); } @@ -102,13 +97,14 @@ void MinecraftProcess::on_stdOut() m_out_leftover.clear(); QStringList lines = str.split("\n"); bool complete = str.endsWith("\n"); - - for(int i = 0; i < lines.size() - 1; i++) + + for (int i = 0; i < lines.size() - 1; i++) { - QString & line = lines[i]; - emit log(lines[i].toLocal8Bit(), MessageLevel::Message); + QString &line = lines[i]; + emit log(line /*.replace(username, "<Username>").replace(sessionID, "<Session ID>")*/, + getLevel(line, MessageLevel::Message)); } - if(!complete) + if (!complete) m_out_leftover = lines.last(); } @@ -117,13 +113,20 @@ void MinecraftProcess::finish(int code, ExitStatus status) { if (status != NormalExit) { - //TODO: error handling + // TODO: error handling } - - emit log("Minecraft exited."); - + + // TODO: Localization + + if (!killed) + //: Message displayed on instance exit + emit log(tr("Minecraft exited with exitcode %1.").arg(status)); + else + //: Message displayed after the instance exits due to kill request + emit log(tr("Minecraft was killed by user."), MessageLevel::Error); + m_prepostlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(code)); - + // run post-exit if (!m_instance->settings().get("PostExitCommand").toString().isEmpty()) { @@ -131,13 +134,19 @@ void MinecraftProcess::finish(int code, ExitStatus status) m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling } } m_instance->cleanupAfterRun(); emit ended(); } +void MinecraftProcess::killMinecraft() +{ + killed = true; + kill(); +} + void MinecraftProcess::launch() { if (!m_instance->settings().get("PreLaunchCommand").toString().isEmpty()) @@ -146,24 +155,42 @@ void MinecraftProcess::launch() m_prepostlaunchprocess.waitForFinished(); if (m_prepostlaunchprocess.exitStatus() != NormalExit) { - //TODO: error handling + // TODO: error handling return; } } - + m_instance->setLastLaunch(); - + emit log(QString("Minecraft folder is: '%1'").arg(workingDirectory())); QString JavaPath = m_instance->settings().get("JavaPath").toString(); emit log(QString("Java path: '%1'").arg(JavaPath)); - emit log(QString("Arguments: '%1'").arg(m_args.join("' '"))); + emit log(QString("Arguments: '%1'").arg( + m_args.join("' '") /*.replace(username, "<Username>").replace(sessionID, "<Session +ID>")*/)); start(JavaPath, m_args); if (!waitForStarted()) { - emit log("Could not launch minecraft!"); + //: Error message displayed if instace can't start + emit log(tr("Could not launch minecraft!")); return; - //TODO: error handling + // TODO: error handling } } +MessageLevel::Enum MinecraftProcess::getLevel(const QString &line, MessageLevel::Enum level) +{ + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("Exception in thread") || line.contains(" at ")) + level = MessageLevel::Fatal; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + return level; +}
\ No newline at end of file diff --git a/logic/MinecraftProcess.h b/logic/MinecraftProcess.h index 516bf986..a1dfa23f 100644 --- a/logic/MinecraftProcess.h +++ b/logic/MinecraftProcess.h @@ -59,6 +59,10 @@ public: void setMinecraftArguments(QStringList args); + void killMinecraft(); + + inline void setLogin(QString user, QString sid) { username = user; sessionID = sid; } + signals: /** * @brief emitted when mc has finished and the PostLaunchCommand was run @@ -83,4 +87,9 @@ protected slots: void finish(int, QProcess::ExitStatus status); void on_stdErr(); void on_stdOut(); +private: + bool killed; + MessageLevel::Enum getLevel(const QString &message, MessageLevel::Enum defaultLevel); + QString sessionID; + QString username; }; diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h index 27977262..53c2f5ef 100644 --- a/logic/MinecraftVersion.h +++ b/logic/MinecraftVersion.h @@ -15,17 +15,16 @@ #pragma once -#include "InstanceVersion.h" +#include "BaseVersion.h" #include <QStringList> -struct MinecraftVersion : public InstVersion +struct MinecraftVersion : public BaseVersion { - // From InstVersion: - /* - QString m_descriptor; - QString m_name; - qint64 m_timestamp; - */ + /*! + * Gets the version's timestamp. + * This is primarily used for sorting versions in a list. + */ + qint64 timestamp; /// The URL that this version will be downloaded from. maybe. QString download_url; @@ -44,6 +43,20 @@ struct MinecraftVersion : public InstVersion /// is this a snapshot? bool is_snapshot = false; + QString m_name; + + QString m_descriptor; + + virtual QString descriptor() + { + return m_descriptor; + } + + virtual QString name() + { + return m_name; + } + virtual QString typeString() const { QStringList pre_final; diff --git a/logic/Mod.cpp b/logic/Mod.cpp index 38faa760..c45e3ad2 100644 --- a/logic/Mod.cpp +++ b/logic/Mod.cpp @@ -1,12 +1,12 @@ -// +// // Copyright 2012 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. @@ -20,26 +20,25 @@ #include <QJsonObject> #include <QJsonArray> #include <QJsonValue> -#include <QDebug> #include <quazip.h> #include <quazipfile.h> #include "Mod.h" #include <pathutils.h> #include <inifile.h> +#include <logger/QsLog.h> - -Mod::Mod( const QFileInfo& file ) +Mod::Mod(const QFileInfo &file) { repath(file); } -void Mod::repath ( const QFileInfo& file ) +void Mod::repath(const QFileInfo &file) { m_file = file; m_name = file.completeBaseName(); m_id = file.fileName(); - + m_type = Mod::MOD_UNKNOWN; if (m_file.isDir()) m_type = MOD_FOLDER; @@ -51,19 +50,19 @@ void Mod::repath ( const QFileInfo& file ) else m_type = MOD_SINGLEFILE; } - if(m_type == MOD_ZIPFILE) + if (m_type == MOD_ZIPFILE) { QuaZip zip(m_file.filePath()); - if(!zip.open(QuaZip::mdUnzip)) + if (!zip.open(QuaZip::mdUnzip)) return; - + QuaZipFile file(&zip); - for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) + for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) { QString name = zip.getCurrentFileName(); - if(name == "mcmod.info") + if (name == "mcmod.info") { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; @@ -73,9 +72,9 @@ void Mod::repath ( const QFileInfo& file ) zip.close(); return; } - else if(name == "forgeversion.properties") + else if (name == "forgeversion.properties") { - if(!file.open(QIODevice::ReadOnly)) + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; @@ -88,16 +87,16 @@ void Mod::repath ( const QFileInfo& file ) } zip.close(); } - else if(m_type == MOD_FOLDER) + else if (m_type == MOD_FOLDER) { QFileInfo mcmod_info(PathCombine(m_file.filePath(), "mcmod.info")); if (mcmod_info.isFile()) { QFile mcmod(mcmod_info.filePath()); - if(!mcmod.open(QIODevice::ReadOnly)) + if (!mcmod.open(QIODevice::ReadOnly)) return; auto data = mcmod.readAll(); - if(data.isEmpty() || data.isNull()) + if (data.isEmpty() || data.isNull()) return; ReadMCModInfo(data); } @@ -111,35 +110,49 @@ void Mod::repath ( const QFileInfo& file ) // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc void Mod::ReadMCModInfo(QByteArray contents) { - auto getInfoFromArray = [&]( QJsonArray arr ) -> void + auto getInfoFromArray = [&](QJsonArray arr)->void { - if(!arr.at(0).isObject()) + if (!arr.at(0).isObject()) return; auto firstObj = arr.at(0).toObject(); m_id = firstObj.value("modid").toString(); m_name = firstObj.value("name").toString(); m_version = firstObj.value("version").toString(); + m_homeurl = firstObj.value("url").toString(); + m_description = firstObj.value("description").toString(); + QJsonArray authors = firstObj.value("authors").toArray(); + if(authors.size() == 0) m_authors = ""; + else if(authors.size() >= 1) + { + m_authors = authors.at(0).toString(); + for(int i = 1; i < authors.size(); i++) + { + m_authors += ", " + authors.at(i).toString(); + } + } + m_credits = firstObj.value("credits").toString(); return; - }; + } + ; QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); // this is the very old format that had just the array - if(jsonDoc.isArray()) + if (jsonDoc.isArray()) { getInfoFromArray(jsonDoc.array()); } - else if(jsonDoc.isObject()) + else if (jsonDoc.isObject()) { auto val = jsonDoc.object().value("modinfoversion"); int version = val.toDouble(); - if(version != 2) + if (version != 2) { - qDebug() << "BAD stuff happened to mod json:"; - qDebug() << contents; + QLOG_ERROR() << "BAD stuff happened to mod json:"; + QLOG_ERROR() << contents; return; } auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isArray()) + if (arrVal.isArray()) { getInfoFromArray(arrVal.toArray()); } @@ -151,33 +164,34 @@ void Mod::ReadForgeInfo(QByteArray contents) // Read the data m_name = "Minecraft Forge"; m_id = "Forge"; + m_homeurl = "http://www.minecraftforge.net/forum/"; INIFile ini; - if(!ini.loadFile(contents)) + if (!ini.loadFile(contents)) return; - - QString major = ini.get("forge.major.number","0").toString(); - QString minor = ini.get("forge.minor.number","0").toString(); - QString revision = ini.get("forge.revision.number","0").toString(); - QString build = ini.get("forge.build.number","0").toString(); - + + QString major = ini.get("forge.major.number", "0").toString(); + QString minor = ini.get("forge.minor.number", "0").toString(); + QString revision = ini.get("forge.revision.number", "0").toString(); + QString build = ini.get("forge.build.number", "0").toString(); + m_version = major + "." + minor + "." + revision + "." + build; } -bool Mod::replace ( Mod& with ) +bool Mod::replace(Mod &with) { - if(!destroy()) + if (!destroy()) return false; bool success = false; auto t = with.type(); - if(t == MOD_ZIPFILE || t == MOD_SINGLEFILE) + if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE) { success = QFile::copy(with.m_file.filePath(), m_file.path()); } - if(t == MOD_FOLDER) + if (t == MOD_FOLDER) { success = copyPath(with.m_file.filePath(), m_file.path()); } - if(success) + if (success) { m_id = with.m_id; m_mcversion = with.m_mcversion; @@ -190,10 +204,10 @@ bool Mod::replace ( Mod& with ) bool Mod::destroy() { - if(m_type == MOD_FOLDER) + if (m_type == MOD_FOLDER) { QDir d(m_file.filePath()); - if(d.removeRecursively()) + if (d.removeRecursively()) { m_type = MOD_UNKNOWN; return true; @@ -203,7 +217,7 @@ bool Mod::destroy() else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE) { QFile f(m_file.filePath()); - if(f.remove()) + if (f.remove()) { m_type = MOD_UNKNOWN; return true; @@ -213,18 +227,17 @@ bool Mod::destroy() return true; } - QString Mod::version() const { - switch(type()) + switch (type()) { - case MOD_ZIPFILE: - return m_version; - case MOD_FOLDER: - return "Folder"; - case MOD_SINGLEFILE: - return "File"; - default: - return "VOID"; + case MOD_ZIPFILE: + return m_version; + case MOD_FOLDER: + return "Folder"; + case MOD_SINGLEFILE: + return "File"; + default: + return "VOID"; } } diff --git a/logic/Mod.h b/logic/Mod.h index fcfcc139..f3aaf18b 100644 --- a/logic/Mod.h +++ b/logic/Mod.h @@ -1,12 +1,12 @@ -// +// // Copyright 2012 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. @@ -22,46 +22,87 @@ class Mod public: enum ModType { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. + MOD_UNKNOWN, //!< Indicates an unspecified mod type. + MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. + MOD_FOLDER, //!< The mod is in a folder on the filesystem. }; Mod(const QFileInfo &file); - - QFileInfo filename() const { return m_file; } - QString id() const { return m_id; } - ModType type() const { return m_type; } - QString mcversion() const { return m_mcversion; }; - bool valid() {return m_type != MOD_UNKNOWN;} - QString name() const {return m_name; }; - + + QFileInfo filename() const + { + return m_file; + } + QString id() const + { + return m_id; + } + ModType type() const + { + return m_type; + } + QString mcversion() const + { + return m_mcversion; + } + ; + bool valid() + { + return m_type != MOD_UNKNOWN; + } + QString name() const + { + return m_name; + } + QString version() const; - - + + QString homeurl() const + { + return m_homeurl; + } + + QString description() const + { + return m_description; + } + + QString authors() const + { + return m_authors; + } + + QString credits() const + { + return m_credits; + } + // delete all the files of this mod bool destroy(); // replace this mod with a copy of the other - bool replace(Mod & with); + bool replace(Mod &with); // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) void repath(const QFileInfo &file); // WEAK compare operator - used for replacing mods - bool operator ==(const Mod &other) const + bool operator==(const Mod &other) const { return filename() == other.filename(); } bool strongCompare(const Mod &other) const { - return filename() == other.filename() && id() == other.id() && version() == other.version() && type() == other.type(); + return filename() == other.filename() && id() == other.id() && + version() == other.version() && type() == other.type(); } + private: void ReadMCModInfo(QByteArray contents); void ReadForgeInfo(QByteArray contents); + protected: - //FIXME: what do do with those? HMM... + // FIXME: what do do with those? HMM... /* void ReadModInfoData(QString info); void ReadForgeInfoData(QString infoFileData); @@ -72,6 +113,10 @@ protected: QString m_name; QString m_version; QString m_mcversion; + QString m_homeurl; + QString m_description; + QString m_authors; + QString m_credits; ModType m_type; }; diff --git a/logic/ModList.cpp b/logic/ModList.cpp index 84511e4c..236f0db6 100644 --- a/logic/ModList.cpp +++ b/logic/ModList.cpp @@ -1,12 +1,12 @@ -// +// // 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. @@ -19,60 +19,60 @@ #include <pathutils.h> #include <QMimeData> #include <QUrl> -#include <QDebug> #include <QUuid> #include <QFileSystemWatcher> +#include <logger/QsLog.h> -ModList::ModList ( const QString& dir, const QString& list_file ) -: QAbstractListModel(), m_dir(dir), m_list_file(list_file) +ModList::ModList(const QString &dir, const QString &list_file) + : QAbstractListModel(), m_dir(dir), m_list_file(list_file) { - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | + QDir::NoSymLinks); m_dir.setSorting(QDir::Name); m_list_id = QUuid::createUuid().toString(); m_watcher = new QFileSystemWatcher(this); is_watching = false; - connect(m_watcher,SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); - update(); + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); } void ModList::startWatching() { is_watching = m_watcher->addPath(m_dir.absolutePath()); - if(is_watching) - qDebug() << "Started watching " << m_dir.absolutePath(); + if (is_watching) + QLOG_INFO() << "Started watching " << m_dir.absolutePath(); else - qDebug() << "Failed to start watching " << m_dir.absolutePath(); + QLOG_INFO() << "Failed to start watching " << m_dir.absolutePath(); } void ModList::stopWatching() { is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if(!is_watching) - qDebug() << "Stopped watching " << m_dir.absolutePath(); + if (!is_watching) + QLOG_INFO() << "Stopped watching " << m_dir.absolutePath(); else - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + QLOG_INFO() << "Failed to stop watching " << m_dir.absolutePath(); } - bool ModList::update() { if (!isValid()) return false; - + QList<Mod> newMods; m_dir.refresh(); auto folderContents = m_dir.entryInfoList(); bool orderWasInvalid = false; - + // first, process the ordered items (if any) int currentOrderIndex = 0; QStringList listOrder = readListFile(); - for(auto item: listOrder) + for (auto item : listOrder) { - QFileInfo info (m_dir.filePath(item)); + QFileInfo info(m_dir.filePath(item)); int idx = folderContents.indexOf(info); // if the file from the index file exists - if(idx != -1) + if (idx != -1) { // remove from the actual folder contents list folderContents.takeAt(idx); @@ -84,26 +84,27 @@ bool ModList::update() orderWasInvalid = true; } } - for(auto entry: folderContents) + for (auto entry : folderContents) { newMods.append(Mod(entry)); } - if(mods.size() != newMods.size()) + if (mods.size() != newMods.size()) { orderWasInvalid = true; } - else for(int i = 0; i < mods.size(); i++) - { - if(!mods[i].strongCompare(newMods[i])) + else + for (int i = 0; i < mods.size(); i++) { - orderWasInvalid = true; - break; + if (!mods[i].strongCompare(newMods[i])) + { + orderWasInvalid = true; + break; + } } - } beginResetModel(); mods.swap(newMods); endResetModel(); - if(orderWasInvalid) + if (orderWasInvalid) { saveListFile(); emit changed(); @@ -111,22 +112,21 @@ bool ModList::update() return true; } -void ModList::directoryChanged ( QString path ) +void ModList::directoryChanged(QString path) { update(); } - QStringList ModList::readListFile() { QStringList stringList; - if(m_list_file.isNull() || m_list_file.isEmpty()) + if (m_list_file.isNull() || m_list_file.isEmpty()) return stringList; - + QFile textFile(m_list_file); - if(!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) return QStringList(); - + QTextStream textStream(&textFile); while (true) { @@ -144,13 +144,13 @@ QStringList ModList::readListFile() bool ModList::saveListFile() { - if(m_list_file.isNull() || m_list_file.isEmpty()) + if (m_list_file.isNull() || m_list_file.isEmpty()) return false; QFile textFile(m_list_file); - if(!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) return false; QTextStream textStream(&textFile); - for(auto mod:mods) + for (auto mod : mods) { auto pathname = mod.filename(); QString filename = pathname.fileName(); @@ -160,29 +160,28 @@ bool ModList::saveListFile() return false; } - bool ModList::isValid() { return m_dir.exists() && m_dir.isReadable(); } -bool ModList::installMod ( const QFileInfo& filename, int index ) +bool ModList::installMod(const QFileInfo &filename, int index) { - if(!filename.exists() || !filename.isReadable() || index < 0) + if (!filename.exists() || !filename.isReadable() || index < 0) { return false; } Mod m(filename); - if(!m.valid()) + if (!m.valid()) return false; - + // if it's already there, replace the original mod (in place) int idx = mods.indexOf(m); - if(idx != -1) + if (idx != -1) { - if(mods[idx].replace(m)) + if (mods[idx].replace(m)) { - + auto left = this->index(index); auto right = this->index(index, columnCount(QModelIndex()) - 1); emit dataChanged(left, right); @@ -192,33 +191,33 @@ bool ModList::installMod ( const QFileInfo& filename, int index ) } return false; } - + auto type = m.type(); - if(type == Mod::MOD_UNKNOWN) + if (type == Mod::MOD_UNKNOWN) return false; - if(type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) + if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE) { QString newpath = PathCombine(m_dir.path(), filename.fileName()); - if(!QFile::copy(filename.filePath(), newpath)) + if (!QFile::copy(filename.filePath(), newpath)) return false; m.repath(newpath); beginInsertRows(QModelIndex(), index, index); - mods.insert(index,m); + mods.insert(index, m); endInsertRows(); saveListFile(); emit changed(); return true; } - else if(type == Mod::MOD_FOLDER) + else if (type == Mod::MOD_FOLDER) { - + QString from = filename.filePath(); QString to = PathCombine(m_dir.path(), filename.fileName()); - if(!copyPath(from, to)) + if (!copyPath(from, to)) return false; m.repath(to); beginInsertRows(QModelIndex(), index, index); - mods.insert(index,m); + mods.insert(index, m); endInsertRows(); saveListFile(); emit changed(); @@ -227,12 +226,12 @@ bool ModList::installMod ( const QFileInfo& filename, int index ) return false; } -bool ModList::deleteMod ( int index ) +bool ModList::deleteMod(int index) { - if(index >= mods.size() || index < 0) + if (index >= mods.size() || index < 0) return false; - Mod & m = mods[index]; - if(m.destroy()) + Mod &m = mods[index]; + if (m.destroy()) { beginRemoveRows(QModelIndex(), index, index); mods.removeAt(index); @@ -244,11 +243,11 @@ bool ModList::deleteMod ( int index ) return false; } -bool ModList::deleteMods ( int first, int last ) +bool ModList::deleteMods(int first, int last) { - for(int i = first; i <= last; i++) + for (int i = first; i <= last; i++) { - Mod & m = mods[i]; + Mod &m = mods[i]; m.destroy(); } beginRemoveRows(QModelIndex(), first, last); @@ -259,18 +258,17 @@ bool ModList::deleteMods ( int first, int last ) return true; } - -bool ModList::moveModTo ( int from, int to ) +bool ModList::moveModTo(int from, int to) { - if(from < 0 || from >= mods.size()) + if (from < 0 || from >= mods.size()) return false; if (to >= rowCount()) to = rowCount() - 1; if (to == -1) to = rowCount() - 1; - if(from == to) + if (from == to) return false; - int togap = to > from ? to + 1: to; + int togap = to > from ? to + 1 : to; beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap); mods.move(from, to); endMoveRows(); @@ -279,83 +277,81 @@ bool ModList::moveModTo ( int from, int to ) return true; } -bool ModList::moveModUp ( int from ) +bool ModList::moveModUp(int from) { - if(from > 0) + if (from > 0) return moveModTo(from, from - 1); return false; } -bool ModList::moveModsUp ( int first, int last ) +bool ModList::moveModsUp(int first, int last) { - if(first == 0) + if (first == 0) return false; - + beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1); - mods.move(first-1, last); + mods.move(first - 1, last); endMoveRows(); saveListFile(); emit changed(); return true; } - -bool ModList::moveModDown ( int from ) +bool ModList::moveModDown(int from) { - if(from < 0) + if (from < 0) return false; - if(from < mods.size() - 1) + if (from < mods.size() - 1) return moveModTo(from, from + 1); return false; } -bool ModList::moveModsDown ( int first, int last ) +bool ModList::moveModsDown(int first, int last) { - if(last == mods.size() - 1) + if (last == mods.size() - 1) return false; - + beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2); - mods.move(last+1, first); + mods.move(last + 1, first); endMoveRows(); saveListFile(); emit changed(); return true; } - -int ModList::columnCount ( const QModelIndex& parent ) const +int ModList::columnCount(const QModelIndex &parent) const { return 2; } -QVariant ModList::data ( const QModelIndex& index, int role ) const +QVariant ModList::data(const QModelIndex &index, int role) const { - if(!index.isValid()) + if (!index.isValid()) return QVariant(); - + int row = index.row(); int column = index.column(); - - if(row < 0 || row >= mods.size()) + + if (row < 0 || row >= mods.size()) return QVariant(); - - if(role != Qt::DisplayRole) + + if (role != Qt::DisplayRole) return QVariant(); - - switch(column) + + switch (column) { - case 0: - return mods[row].name(); - case 1: - return mods[row].version(); - case 2: - return mods[row].mcversion(); - default: - return QVariant(); + case 0: + return mods[row].name(); + case 1: + return mods[row].version(); + case 2: + return mods[row].mcversion(); + default: + return QVariant(); } } -QVariant ModList::headerData ( int section, Qt::Orientation orientation, int role ) const +QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); @@ -370,10 +366,9 @@ QVariant ModList::headerData ( int section, Qt::Orientation orientation, int rol } } - -Qt::ItemFlags ModList::flags ( const QModelIndex& index ) const +Qt::ItemFlags ModList::flags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractListModel::flags ( index ); + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); if (index.isValid()) return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; else @@ -400,78 +395,79 @@ Qt::DropActions ModList::supportedDragActions() const return Qt::MoveAction; } -QMimeData* ModList::mimeData ( const QModelIndexList& indexes ) const +QMimeData *ModList::mimeData(const QModelIndexList &indexes) const { - QMimeData * data = new QMimeData(); - - if(indexes.size() == 0) + QMimeData *data = new QMimeData(); + + if (indexes.size() == 0) return data; - + auto idx = indexes[0]; int row = idx.row(); - if(row <0 || row >= mods.size()) + if (row < 0 || row >= mods.size()) return data; - + QStringList params; params << m_list_id << QString::number(row); data->setText(params.join('|')); return data; } -bool ModList::dropMimeData ( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) +bool ModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, + const QModelIndex &parent) { if (action == Qt::IgnoreAction) - return true; + return true; // check if the action is supported if (!data || !(action & supportedDropActions())) return false; - if(parent.isValid()) + if (parent.isValid()) { row = parent.row(); column = parent.column(); } - + if (row > rowCount()) row = rowCount(); if (row == -1) row = rowCount(); if (column == -1) column = 0; - qDebug() << "Drop row: " << row << " column: " << column; - + QLOG_INFO() << "Drop row: " << row << " column: " << column; + // files dropped from outside? - if(data->hasUrls()) + if (data->hasUrls()) { bool was_watching = is_watching; - if(was_watching) + if (was_watching) stopWatching(); auto urls = data->urls(); - for(auto url: urls) + for (auto url : urls) { // only local files may be dropped... - if(!url.isLocalFile()) + if (!url.isLocalFile()) continue; QString filename = url.toLocalFile(); installMod(filename, row); - qDebug() << "installing: " << filename; + QLOG_INFO() << "installing: " << filename; } - if(was_watching) + if (was_watching) startWatching(); return true; } - else if(data->hasText()) + else if (data->hasText()) { QString sourcestr = data->text(); auto list = sourcestr.split('|'); - if(list.size() != 2) + if (list.size() != 2) return false; QString remoteId = list[0]; int remoteIndex = list[1].toInt(); - qDebug() << "move: " << sourcestr; + QLOG_INFO() << "move: " << sourcestr; // no moving of things between two lists - if(remoteId != m_list_id) + if (remoteId != m_list_id) return false; // no point moving to the same place... - if(row == remoteIndex) + if (row == remoteIndex) return false; // otherwise, move the mod :D moveModTo(remoteIndex, row); @@ -479,4 +475,3 @@ bool ModList::dropMimeData ( const QMimeData* data, Qt::DropAction action, int r } return false; } - diff --git a/logic/ModList.h b/logic/ModList.h index 5395e9ae..e99b6c82 100644 --- a/logic/ModList.h +++ b/logic/ModList.h @@ -112,3 +112,6 @@ protected: QString m_list_id; QList<Mod> mods; }; + + + diff --git a/logic/NostalgiaInstance.cpp b/logic/NostalgiaInstance.cpp index 039cd9ce..efd8f46b 100644 --- a/logic/NostalgiaInstance.cpp +++ b/logic/NostalgiaInstance.cpp @@ -11,6 +11,10 @@ QString NostalgiaInstance::getStatusbarDescription() return "Nostalgia : " + intendedVersionId(); } +bool NostalgiaInstance::menuActionEnabled(QString action_name) const +{ + return false; +} /* ADD MORE diff --git a/logic/NostalgiaInstance.h b/logic/NostalgiaInstance.h index f2df1828..64eb7a81 100644 --- a/logic/NostalgiaInstance.h +++ b/logic/NostalgiaInstance.h @@ -8,4 +8,6 @@ class NostalgiaInstance : public OneSixInstance public: explicit NostalgiaInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); virtual QString getStatusbarDescription(); + virtual bool menuActionEnabled(QString action_name) const; }; + diff --git a/logic/OneSixAssets.cpp b/logic/OneSixAssets.cpp index c65ee607..6aa0a207 100644 --- a/logic/OneSixAssets.cpp +++ b/logic/OneSixAssets.cpp @@ -1,8 +1,10 @@ #include <QString> -#include <QDebug> +#include <logger/QsLog.h> #include <QtXml/QtXml> #include "OneSixAssets.h" #include "net/DownloadJob.h" +#include "net/HttpMetaCache.h" +#include "MultiMC.h" inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) { @@ -19,6 +21,7 @@ class ThreadedDeleter : public QThread public: void run() { + QLOG_INFO() << "Cleaning up assets folder..."; QDirIterator iter ( m_base, QDirIterator::Subdirectories ); int base_length = m_base.length(); while ( iter.hasNext() ) @@ -32,12 +35,12 @@ public: trimmedf.remove ( 0, base_length + 1 ); if ( m_whitelist.contains ( trimmedf ) ) { - // qDebug() << trimmedf << " gets to live"; + QLOG_TRACE() << trimmedf << " gets to live"; } else { // DO NOT TOLERATE JUNK - // qDebug() << trimmedf << " dies"; + QLOG_TRACE() << trimmedf << " dies"; QFile f ( filename ); f.remove(); } @@ -65,21 +68,25 @@ void OneSixAssets::fetchXMLFinished() nuke_whitelist.clear(); auto firstJob = index_job->first(); - QByteArray ba = firstJob->m_data; + QByteArray ba = std::dynamic_pointer_cast<ByteArrayDownload>(firstJob)->m_data; QString xmlErrorMsg; QDomDocument doc; if ( !doc.setContent ( ba, false, &xmlErrorMsg ) ) { - qDebug() << "Failed to process s3.amazonaws.com/Minecraft.Resources. XML error:" << xmlErrorMsg << ba; + QLOG_ERROR() << "Failed to process s3.amazonaws.com/Minecraft.Resources. XML error:" << xmlErrorMsg << ba; + emit failed(); + return; } //QRegExp etag_match(".*([a-f0-9]{32}).*"); QDomNodeList contents = doc.elementsByTagName ( "Contents" ); - DownloadJob *job = new DownloadJob(); + DownloadJob *job = new DownloadJob("Assets"); connect ( job, SIGNAL(succeeded()), SLOT(downloadFinished()) ); connect ( job, SIGNAL(failed()), SIGNAL(failed()) ); + auto metacache = MMC->metacache(); + for ( int i = 0; i < contents.length(); i++ ) { QDomElement element = contents.at ( i ).toElement(); @@ -104,22 +111,12 @@ void OneSixAssets::fetchXMLFinished() if ( sizeStr == "0" ) continue; - QString filename = fprefix + keyStr; - QFile check_file ( filename ); - QString client_etag = "nonsense"; - // if there already is a file and md5 checking is in effect and it can be opened - if ( check_file.exists() && check_file.open ( QIODevice::ReadOnly ) ) - { - // check the md5 against the expected one - client_etag = QCryptographicHash::hash ( check_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); - check_file.close(); - } - - QString trimmedEtag = etagStr.remove ( '"' ); nuke_whitelist.append ( keyStr ); - if(trimmedEtag != client_etag) + + auto entry = metacache->resolveEntry("assets", keyStr, etagStr); + if(entry->stale) { - job->add ( QUrl ( prefix + keyStr ), filename ); + job->addCacheDownload(QUrl(prefix + keyStr), entry); } } if(job->size()) @@ -135,7 +132,8 @@ void OneSixAssets::fetchXMLFinished() } void OneSixAssets::start() { - DownloadJob * job = new DownloadJob(QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" )); + auto job = new DownloadJob("Assets index"); + job->addByteArrayDownload(QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" )); connect ( job, SIGNAL(succeeded()), SLOT ( fetchXMLFinished() ) ); index_job.reset ( job ); job->start(); diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index c926df60..d80f6b37 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -2,16 +2,18 @@ #include "OneSixInstance_p.h" #include "OneSixUpdate.h" #include "MinecraftProcess.h" -#include "VersionFactory.h" +#include "OneSixVersion.h" #include <setting.h> #include <pathutils.h> #include <cmdutils.h> #include <JlCompress.h> #include <gui/OneSixModEditDialog.h> +#include <logger/QsLog.h> -OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting_obj, QObject* parent ) -: BaseInstance ( new OneSixInstancePrivate(), rootDir, setting_obj, parent ) +OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *setting_obj, + QObject *parent) + : BaseInstance(new OneSixInstancePrivate(), rootDir, setting_obj, parent) { I_D(OneSixInstance); d->m_settings->registerSetting(new Setting("IntendedVersion", "")); @@ -19,7 +21,7 @@ OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting reloadFullVersion(); } -BaseUpdate* OneSixInstance::doUpdate() +BaseUpdate *OneSixInstance::doUpdate() { return new OneSixUpdate(this); } @@ -34,10 +36,10 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) int head = 0; while ((head = token_regexp.indexIn(text, head)) != -1) { - result.append(text.mid(tail, head-tail)); + result.append(text.mid(tail, head - tail)); QString key = token_regexp.cap(1); auto iter = with.find(key); - if(iter != with.end()) + if (iter != with.end()) { result.append(*iter); } @@ -48,26 +50,27 @@ QString replaceTokensIn(QString text, QMap<QString, QString> with) return result; } -QStringList OneSixInstance::processMinecraftArgs( QString user, QString session ) +QStringList OneSixInstance::processMinecraftArgs(LoginResponse response) { I_D(OneSixInstance); auto version = d->version; QString args_pattern = version->minecraftArguments; - + QMap<QString, QString> token_mapping; - token_mapping["auth_username"] = user; - token_mapping["auth_session"] = session; - //FIXME: user and player name are DIFFERENT! - token_mapping["auth_player_name"] = user; - //FIXME: WTF is this. I just plugged in a random UUID here. - token_mapping["auth_uuid"] = "7d4bacf0-fd62-11e2-b778-0800200c9a66"; // obviously fake. - - // this is for offline: + // yggdrasil! + token_mapping["auth_username"] = response.username; + token_mapping["auth_session"] = response.session_id; + token_mapping["auth_access_token"] = response.access_token; + token_mapping["auth_player_name"] = response.player_name; + token_mapping["auth_uuid"] = response.player_id; + + // this is for offline?: /* map["auth_player_name"] = "Player"; map["auth_player_name"] = "00000000-0000-0000-0000-000000000000"; */ - + + // these do nothing and are stupid. token_mapping["profile_name"] = name(); token_mapping["version_name"] = version->id; @@ -75,8 +78,8 @@ QStringList OneSixInstance::processMinecraftArgs( QString user, QString session token_mapping["game_directory"] = absRootDir; QString absAssetsDir = QDir("assets/").absolutePath(); token_mapping["game_assets"] = absAssetsDir; - - QStringList parts = args_pattern.split(' ',QString::SkipEmptyParts); + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); for (int i = 0; i < parts.length(); i++) { parts[i] = replaceTokensIn(parts[i], token_mapping); @@ -84,27 +87,28 @@ QStringList OneSixInstance::processMinecraftArgs( QString user, QString session return parts; } -MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString session ) +MinecraftProcess *OneSixInstance::prepareForLaunch(LoginResponse response) { I_D(OneSixInstance); cleanupAfterRun(); auto version = d->version; - if(!version) + if (!version) return nullptr; auto libs_to_extract = version->getActiveNativeLibs(); QString natives_dir_raw = PathCombine(instanceRoot(), "natives/"); bool success = ensureFolderPathExists(natives_dir_raw); - if(!success) + if (!success) { // FIXME: handle errors return nullptr; } - - for(auto lib: libs_to_extract) + + for (auto lib : libs_to_extract) { QString path = "libraries/" + lib->storagePath(); - qDebug() << "Will extract " << path.toLocal8Bit(); - if(JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes).isEmpty()) + QLOG_INFO() << "Will extract " << path.toLocal8Bit(); + if (JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes) + .isEmpty()) { return nullptr; } @@ -116,11 +120,11 @@ MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString sessi args << QString("-Xmx%1m").arg(settings().get("MaxMemAlloc").toInt()); args << QString("-XX:PermSize=%1m").arg(settings().get("PermGen").toInt()); QDir natives_dir(natives_dir_raw); - args << QString("-Djava.library.path=%1").arg( natives_dir.absolutePath() ); + args << QString("-Djava.library.path=%1").arg(natives_dir.absolutePath()); QString classPath; { auto libs = version->getActiveNormalLibs(); - for (auto lib: libs) + for (auto lib : libs) { QFileInfo fi(QString("libraries/") + lib->storagePath()); classPath.append(fi.absoluteFilePath()); @@ -134,16 +138,29 @@ MinecraftProcess* OneSixInstance::prepareForLaunch ( QString user, QString sessi QFileInfo fi(targetstr); classPath.append(fi.absoluteFilePath()); } - if(classPath.size()) + if (classPath.size()) { args << "-cp"; args << classPath; } args << version->mainClass; - args.append(processMinecraftArgs(user, session)); - + args.append(processMinecraftArgs(response)); + + // Set the width and height for 1.6 instances + bool maximize = settings().get("LaunchMaximized").toBool(); + if(maximize) + { + // this is probably a BAD idea + // args << QString("--fullscreen"); + } + else + { + args << QString("--width") << settings().get("MinecraftWinWidth").toString(); + args << QString("--height") << settings().get("MinecraftWinHeight").toString(); + } + // create the process and set its parameters - MinecraftProcess * proc = new MinecraftProcess(this); + MinecraftProcess *proc = new MinecraftProcess(this); proc->setMinecraftArguments(args); proc->setMinecraftWorkdir(minecraftRoot()); return proc; @@ -156,40 +173,42 @@ void OneSixInstance::cleanupAfterRun() dir.removeRecursively(); } -QSharedPointer< ModList > OneSixInstance::loaderModList() +std::shared_ptr<ModList> OneSixInstance::loaderModList() { I_D(OneSixInstance); - if(!d->loader_mod_list) + if (!d->loader_mod_list) { d->loader_mod_list.reset(new ModList(loaderModsDir())); } - else - d->loader_mod_list->update(); + d->loader_mod_list->update(); return d->loader_mod_list; } -QSharedPointer< ModList > OneSixInstance::resourcePackList() +std::shared_ptr<ModList> OneSixInstance::resourcePackList() { I_D(OneSixInstance); - if(!d->resource_pack_list) + if (!d->resource_pack_list) { d->resource_pack_list.reset(new ModList(resourcePacksDir())); } - else - d->resource_pack_list->update(); + d->resource_pack_list->update(); return d->resource_pack_list; } - -QDialog * OneSixInstance::createModEditDialog ( QWidget* parent ) +QDialog *OneSixInstance::createModEditDialog(QWidget *parent) { return new OneSixModEditDialog(this, parent); } -bool OneSixInstance::setIntendedVersionId ( QString version ) +bool OneSixInstance::setIntendedVersionId(QString version) { settings().set("IntendedVersion", version); setShouldUpdate(true); + auto pathCustom = PathCombine(instanceRoot(), "custom.json"); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::remove(pathCustom); + QFile::remove(pathOrig); + reloadFullVersion(); return true; } @@ -198,48 +217,85 @@ QString OneSixInstance::intendedVersionId() const return settings().get("IntendedVersion").toString(); } -void OneSixInstance::setShouldUpdate ( bool val ) +void OneSixInstance::setShouldUpdate(bool val) { - settings().set ( "ShouldUpdate", val ); + settings().set("ShouldUpdate", val); } bool OneSixInstance::shouldUpdate() const { I_D(OneSixInstance); - QVariant var = settings().get ( "ShouldUpdate" ); - if ( !var.isValid() || var.toBool() == false ) + QVariant var = settings().get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) { return intendedVersionId() != currentVersionId(); } return true; } +bool OneSixInstance::versionIsCustom() +{ + QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); + QFile versionfile(verpath_custom); + return versionfile.exists(); +} + QString OneSixInstance::currentVersionId() const { return intendedVersionId(); } +bool OneSixInstance::customizeVersion() +{ + if (!versionIsCustom()) + { + auto pathCustom = PathCombine(instanceRoot(), "custom.json"); + auto pathOrig = PathCombine(instanceRoot(), "version.json"); + QFile::copy(pathOrig, pathCustom); + return reloadFullVersion(); + } + else + return true; +} + +bool OneSixInstance::revertCustomVersion() +{ + if (versionIsCustom()) + { + auto path = PathCombine(instanceRoot(), "custom.json"); + QFile::remove(path); + return reloadFullVersion(); + } + else + return true; +} + bool OneSixInstance::reloadFullVersion() { I_D(OneSixInstance); - + QString verpath = PathCombine(instanceRoot(), "version.json"); - QFile versionfile(verpath); - if(versionfile.exists() && versionfile.open(QIODevice::ReadOnly)) { - FullVersionFactory fvf; - auto version = fvf.parse(versionfile.readAll()); - versionfile.close(); - if(version) - { - d->version = version; - return true; - } - }; - return false; + QString verpath_custom = PathCombine(instanceRoot(), "custom.json"); + QFile versionfile(verpath_custom); + if (versionfile.exists()) + verpath = verpath_custom; + } + + auto version = OneSixVersion::fromFile(verpath); + if (version) + { + d->version = version; + return true; + } + else + { + d->version.reset(); + return false; + } } -QSharedPointer< OneSixVersion > OneSixInstance::getFullVersion() +std::shared_ptr<OneSixVersion> OneSixInstance::getFullVersion() { I_D(OneSixInstance); return d->version; @@ -255,16 +311,21 @@ QString OneSixInstance::defaultCustomBaseJar() const return PathCombine(instanceRoot(), "custom.jar"); } -bool OneSixInstance::menuActionEnabled ( QString action_name ) const +bool OneSixInstance::menuActionEnabled(QString action_name) const { - if(action_name == "actionChangeInstLWJGLVersion") + if (action_name == "actionChangeInstLWJGLVersion") return false; return true; } QString OneSixInstance::getStatusbarDescription() { - return "One Six : " + intendedVersionId(); + QString descr = "One Six : " + intendedVersionId(); + if (versionIsCustom()) + { + descr + " (custom)"; + } + return descr; } QString OneSixInstance::loaderModsDir() const @@ -281,3 +342,4 @@ QString OneSixInstance::instanceConfigFolder() const { return PathCombine(minecraftRoot(), "config"); } + diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index a4c67ed1..d2276afc 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -10,43 +10,50 @@ class OneSixInstance : public BaseInstance { Q_OBJECT public: - explicit OneSixInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); - - + explicit OneSixInstance(const QString &rootDir, SettingsObject *settings, + QObject *parent = 0); + ////// Mod Lists ////// - QSharedPointer<ModList> loaderModList(); - QSharedPointer<ModList> resourcePackList(); - + std::shared_ptr<ModList> loaderModList(); + std::shared_ptr<ModList> resourcePackList(); + ////// Directories ////// QString resourcePacksDir() const; QString loaderModsDir() const; virtual QString instanceConfigFolder() const; - - virtual BaseUpdate* doUpdate(); - virtual MinecraftProcess* prepareForLaunch ( QString user, QString session ); + + virtual BaseUpdate *doUpdate(); + virtual MinecraftProcess *prepareForLaunch(LoginResponse response); virtual void cleanupAfterRun(); - + virtual QString intendedVersionId() const; - virtual bool setIntendedVersionId ( QString version ); - + virtual bool setIntendedVersionId(QString version); + virtual QString currentVersionId() const; // virtual void setCurrentVersionId ( QString val ) {}; - + virtual bool shouldUpdate() const; virtual void setShouldUpdate(bool val); - - virtual QDialog * createModEditDialog ( QWidget* parent ); - + + virtual QDialog *createModEditDialog(QWidget *parent); + /// reload the full version json file. return true on success! bool reloadFullVersion(); /// get the current full version info - QSharedPointer<OneSixVersion> getFullVersion(); - + std::shared_ptr<OneSixVersion> getFullVersion(); + /// revert the current custom version back to base + bool revertCustomVersion(); + /// customize the current base version + bool customizeVersion(); + /// is the current version original, or custom? + virtual bool versionIsCustom() override; + virtual QString defaultBaseJar() const; virtual QString defaultCustomBaseJar() const; - - virtual bool menuActionEnabled ( QString action_name ) const; + + virtual bool menuActionEnabled(QString action_name) const; virtual QString getStatusbarDescription(); + private: - QStringList processMinecraftArgs( QString user, QString session ); + QStringList processMinecraftArgs(LoginResponse response); };
\ No newline at end of file diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index c098c9e2..06737b6f 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -2,11 +2,12 @@ #include "BaseInstance_p.h" #include "OneSixVersion.h" +#include "OneSixLibrary.h" #include "ModList.h" struct OneSixInstancePrivate: public BaseInstancePrivate { - QSharedPointer<OneSixVersion> version; - QSharedPointer<ModList> loader_mod_list; - QSharedPointer<ModList> resource_pack_list; + std::shared_ptr<OneSixVersion> version; + std::shared_ptr<ModList> loader_mod_list; + std::shared_ptr<ModList> resource_pack_list; };
\ No newline at end of file diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp new file mode 100644 index 00000000..9c1caaa7 --- /dev/null +++ b/logic/OneSixLibrary.cpp @@ -0,0 +1,162 @@ +#include "OneSixLibrary.h" +#include "OneSixRule.h" +#include "OpSys.h" +#include <QJsonArray> +void OneSixLibrary::finalize() +{ + QStringList parts = m_name.split(':'); + QString relative = parts[0]; + relative.replace('.', '/'); + relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; + + if (!m_is_native) + relative += ".jar"; + else + { + if (m_native_suffixes.contains(currentSystem)) + { + relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + } + else + { + // really, bad. + relative += ".jar"; + } + } + + m_decentname = parts[1]; + m_decentversion = parts[2]; + m_storage_path = relative; + m_download_url = m_base_url + relative; + + if (m_rules.empty()) + { + m_is_active = true; + } + else + { + RuleAction result = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + result = temp; + } + m_is_active = (result == Allow); + } + if (m_is_native) + { + m_is_active = m_is_active && m_native_suffixes.contains(currentSystem); + m_decenttype = "Native"; + } + else + { + m_decenttype = "Java"; + } +} + +void OneSixLibrary::setName(QString name) +{ + m_name = name; +} +void OneSixLibrary::setBaseUrl(QString base_url) +{ + m_base_url = base_url; +} +void OneSixLibrary::setIsNative() +{ + m_is_native = true; +} +void OneSixLibrary::addNative(OpSys os, QString suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void OneSixLibrary::setRules(QList<std::shared_ptr<Rule>> rules) +{ + m_rules = rules; +} +bool OneSixLibrary::isActive() +{ + return m_is_active; +} +bool OneSixLibrary::isNative() +{ + return m_is_native; +} +QString OneSixLibrary::downloadUrl() +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString OneSixLibrary::storagePath() +{ + return m_storage_path; +} + +void OneSixLibrary::setAbsoluteUrl(QString absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString OneSixLibrary::absoluteUrl() +{ + return m_absolute_url; +} + +void OneSixLibrary::setHint(QString hint) +{ + m_hint = hint; +} + +QString OneSixLibrary::hint() +{ + return m_hint; +} + +QJsonObject OneSixLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://s3.amazonaws.com/Minecraft.Download/libraries/" && + m_base_url != "https://s3.amazonaws.com/Minecraft.Download/libraries/") + libRoot.insert("url", m_base_url); + if (isNative() && m_native_suffixes.size()) + { + QJsonObject nativeList; + auto iter = m_native_suffixes.begin(); + while (iter != m_native_suffixes.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + } + if (isNative() && extract_excludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : extract_excludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + if (m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + return libRoot; +} diff --git a/logic/OneSixLibrary.h b/logic/OneSixLibrary.h new file mode 100644 index 00000000..5e58ef89 --- /dev/null +++ b/logic/OneSixLibrary.h @@ -0,0 +1,104 @@ +#pragma once +#include <QString> +#include <QStringList> +#include <QMap> +#include <memory> +#include <QJsonObject> +#include "OpSys.h" + +class Rule; + +class OneSixLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "http://s3.amazonaws.com/Minecraft.Download/libraries/"; + QList<std::shared_ptr<Rule> > m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// download hint - how to actually get the library + QString m_hint; + + // derived values used for real things + /// a decent name fit for display + QString m_decentname; + /// a decent version fit for display + QString m_decentversion; + /// a decent type fit for display + QString m_decenttype; + /// where to store the lib locally + QString m_storage_path; + /// where to download the lib from + QString m_download_url; + /// is this lib actually active on the current OS? + bool m_is_active = false; + /// is the library a native? + bool m_is_native = false; + /// native suffixes per OS + QMap<OpSys, QString> m_native_suffixes; +public: + QStringList extract_excludes; + +public: + /// Constructor + OneSixLibrary(QString name) + { + m_name = name; + } + + QJsonObject toJson(); + + /** + * finalize the library, processing the input values into derived values and state + * + * This SHALL be called after all the values are parsed or after any further change. + */ + void finalize(); + + /// Set the library composite name + void setName(QString name); + /// get a decent-looking name + QString name() + { + return m_decentname; + } + /// get a decent-looking version + QString version() + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(QString base_url); + + /// Call this to mark the library as 'native' (it's a zip archive with DLLs) + void setIsNative(); + /// Attach a name suffix to the specified OS native + void addNative(OpSys os, QString suffix); + /// Set the load rules + void setRules(QList<std::shared_ptr<Rule> > rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive(); + /// Returns true if the library is native + bool isNative(); + /// Get the URL to download the library from + QString downloadUrl(); + /// Get the relative path where the library should be saved + QString storagePath(); + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(QString absolute_url); + QString absoluteUrl(); + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(QString hint); + QString hint(); +}; diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp new file mode 100644 index 00000000..cb64c9ba --- /dev/null +++ b/logic/OneSixRule.cpp @@ -0,0 +1,72 @@ +#include "OneSixRule.h" +#include <QJsonObject> +#include <QJsonArray> + +QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules) +{ + QList<std::shared_ptr<Rule>> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr<Rule> rule; + if (!ruleVal.isObject()) + continue; + auto ruleObj = ruleVal.toObject(); + auto actionVal = ruleObj.value("action"); + if (!actionVal.isString()) + continue; + auto action = RuleAction_fromString(actionVal.toString()); + if (action == Defer) + continue; + + auto osVal = ruleObj.value("os"); + if (!osVal.isObject()) + { + // add a new implicit action rule + rules.append(ImplicitRule::create(action)); + continue; + } + + auto osObj = osVal.toObject(); + auto osNameVal = osObj.value("name"); + if (!osNameVal.isString()) + continue; + OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString versionRegex = osObj.value("version").toString(); + // add a new OS rule + rules.append(OsRule::create(action, requiredOs, versionRegex)); + } +} + +QJsonObject ImplicitRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + return ruleObj; +} + +QJsonObject OsRule::toJson() +{ + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("version", m_version_regexp); + } + ruleObj.insert("os", osObj); + return ruleObj; +} + +RuleAction RuleAction_fromString(QString name) +{ + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; +}
\ No newline at end of file diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h new file mode 100644 index 00000000..6be01f1b --- /dev/null +++ b/logic/OneSixRule.h @@ -0,0 +1,72 @@ +#pragma once +#include <QString> +#include <QSharedPointer> +#include "OneSixLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList<std::shared_ptr<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(OneSixLibrary * parent) = 0; +public: + Rule(RuleAction result) + :m_result(result) {} + virtual ~Rule(){}; + virtual QJsonObject toJson() = 0; + RuleAction apply(OneSixLibrary * parent) + { + if(applies(parent)) + return m_result; + else + return Defer; + }; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; +protected: + virtual bool applies ( OneSixLibrary* ) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) {} +public: + virtual QJsonObject toJson(); + static std::shared_ptr<OsRule> create(RuleAction result, OpSys system, QString version_regexp) + { + return std::shared_ptr<OsRule> (new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies ( OneSixLibrary* ) + { + return true; + } + ImplicitRule(RuleAction result) + : Rule(result) {} +public: + virtual QJsonObject toJson(); + static std::shared_ptr<ImplicitRule> create(RuleAction result) + { + return std::shared_ptr<ImplicitRule> (new ImplicitRule(result)); + } +}; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 428d6ef7..b5f1d78b 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -3,7 +3,7 @@ * 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 @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +#include "MultiMC.h" #include "OneSixUpdate.h" #include <QtNetwork> @@ -22,23 +22,22 @@ #include <QTextStream> #include <QDataStream> -#include <QDebug> - #include "BaseInstance.h" #include "lists/MinecraftVersionList.h" -#include "VersionFactory.h" #include "OneSixVersion.h" +#include "OneSixLibrary.h" #include "OneSixInstance.h" #include "pathutils.h" - -OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent):BaseUpdate(inst, parent){} +OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent) : BaseUpdate(inst, parent) +{ +} void OneSixUpdate::executeTask() { QString intendedVersion = m_inst->intendedVersionId(); - + // Make directories QDir mcDir(m_inst->minecraftRoot()); if (!mcDir.exists() && !mcDir.mkpath(".")) @@ -46,17 +45,18 @@ void OneSixUpdate::executeTask() emitFailed("Failed to create bin folder."); return; } - + // Get a pointer to the version object that corresponds to the instance's version. - targetVersion = MinecraftVersionList::getMainList().findVersion(intendedVersion).dynamicCast<MinecraftVersion>(); - if(targetVersion == nullptr) + targetVersion = std::dynamic_pointer_cast<MinecraftVersion>( + MMC->minecraftlist()->findVersion(intendedVersion)); + if (targetVersion == nullptr) { // don't do anything if it was invalid emitSucceeded(); return; } - - if(m_inst->shouldUpdate()) + + if (m_inst->shouldUpdate()) { versionFileStart(); } @@ -69,45 +69,64 @@ void OneSixUpdate::executeTask() void OneSixUpdate::versionFileStart() { setStatus("Getting the version files from Mojang."); - + QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); - urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".json"; - specificVersionDownloadJob.reset(new DownloadJob(QUrl(urlstr))); - connect(specificVersionDownloadJob.data(), SIGNAL(succeeded()), SLOT(versionFileFinished())); - connect(specificVersionDownloadJob.data(), SIGNAL(failed()), SLOT(versionFileFailed())); - connect(specificVersionDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + urlstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json"; + auto job = new DownloadJob("Version index"); + job->addByteArrayDownload(QUrl(urlstr)); + specificVersionDownloadJob.reset(job); + connect(specificVersionDownloadJob.get(), SIGNAL(succeeded()), SLOT(versionFileFinished())); + connect(specificVersionDownloadJob.get(), SIGNAL(failed()), SLOT(versionFileFailed())); + connect(specificVersionDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); specificVersionDownloadJob->start(); } void OneSixUpdate::versionFileFinished() { DownloadPtr DlJob = specificVersionDownloadJob->first(); - - QString version_id = targetVersion->descriptor; + OneSixInstance *inst = (OneSixInstance *)m_inst; + + QString version_id = targetVersion->descriptor(); QString inst_dir = m_inst->instanceRoot(); // save the version file in $instanceId/version.json { - QString version1 = PathCombine(inst_dir, "/version.json"); + QString version1 = PathCombine(inst_dir, "/version.json"); ensureFilePathExists(version1); // FIXME: detect errors here, download to a temp file, swap - QFile vfile1 (version1); - vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly ); - vfile1.write(DlJob->m_data); - vfile1.close(); + QSaveFile vfile1(version1); + if (!vfile1.open(QIODevice::Truncate | QIODevice::WriteOnly)) + { + emitFailed("Can't open " + version1 + " for writing."); + return; + } + auto data = std::dynamic_pointer_cast<ByteArrayDownload>(DlJob)->m_data; + qint64 actual = 0; + if ((actual = vfile1.write(data)) != data.size()) + { + emitFailed("Failed to write into " + version1 + ". Written " + actual + " out of " + + data.size() + '.'); + return; + } + if (!vfile1.commit()) + { + emitFailed("Can't commit changes to " + version1); + return; + } } - + // the version is downloaded safely. update is 'done' at this point m_inst->setShouldUpdate(false); - // save the version file in versions/$version/$version.json - /* - //QString version2 = QString("versions/") + version_id + "/" + version_id + ".json"; - //ensurePathExists(version2); - //QFile vfile2 (version2); - //vfile2.open(QIODevice::Truncate | QIODevice::WriteOnly ); - //vfile2.write(DlJob->m_data); - //vfile2.close(); - */ - + + // delete any custom version inside the instance (it's no longer relevant, we did an update) + QString custom = PathCombine(inst_dir, "/custom.json"); + QFile finfo(custom); + if (finfo.exists()) + { + finfo.remove(); + } + inst->reloadFullVersion(); + jarlibStart(); } @@ -118,36 +137,49 @@ void OneSixUpdate::versionFileFailed() void OneSixUpdate::jarlibStart() { - OneSixInstance * inst = (OneSixInstance *) m_inst; + OneSixInstance *inst = (OneSixInstance *)m_inst; bool successful = inst->reloadFullVersion(); - if(!successful) + if (!successful) { - emitFailed("Failed to load the version description file (version.json). It might be corrupted, missing or simply too new."); + emitFailed("Failed to load the version description file (version.json). It might be " + "corrupted, missing or simply too new."); return; } - - QSharedPointer<OneSixVersion> version = inst->getFullVersion(); - + + std::shared_ptr<OneSixVersion> version = inst->getFullVersion(); + // download the right jar, save it in versions/$version/$version.jar QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); urlstr += version->id + "/" + version->id + ".jar"; - QString targetstr ("versions/"); + QString targetstr("versions/"); targetstr += version->id + "/" + version->id + ".jar"; - - jarlibDownloadJob.reset(new DownloadJob(QUrl(urlstr), targetstr)); - + + auto job = new DownloadJob("Libraries for instance " + inst->name()); + job->addFileDownload(QUrl(urlstr), targetstr); + jarlibDownloadJob.reset(job); + auto libs = version->getActiveNativeLibs(); libs.append(version->getActiveNormalLibs()); - - for(auto lib: libs) + + auto metacache = MMC->metacache(); + for (auto lib : libs) { - QString download_path = lib->downloadPath(); - QString storage_path = "libraries/" + lib->storagePath(); - jarlibDownloadJob->add(download_path, storage_path); + if (lib->hint() == "local") + continue; + QString download_path = lib->downloadUrl(); + auto entry = metacache->resolveEntry("libraries", lib->storagePath()); + if (entry->stale) + { + if (lib->hint() == "forge-pack-xz") + jarlibDownloadJob->addForgeXzDownload(download_path, entry); + else + jarlibDownloadJob->addCacheDownload(download_path, entry); + } } - connect(jarlibDownloadJob.data(), SIGNAL(succeeded()), SLOT(jarlibFinished())); - connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed())); - connect(jarlibDownloadJob.data(), SIGNAL(progress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); + connect(jarlibDownloadJob.get(), SIGNAL(succeeded()), SLOT(jarlibFinished())); + connect(jarlibDownloadJob.get(), SIGNAL(failed()), SLOT(jarlibFailed())); + connect(jarlibDownloadJob.get(), SIGNAL(progress(qint64, qint64)), + SIGNAL(progress(qint64, qint64))); jarlibDownloadJob->start(); } @@ -159,6 +191,8 @@ void OneSixUpdate::jarlibFinished() void OneSixUpdate::jarlibFailed() { - emitFailed("Failed to download the binary garbage. Try again. Maybe. IF YOU DARE"); + QStringList failed = jarlibDownloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed("Failed to download the following files:\n" + failed_all + + "\n\nPlease try again."); } - diff --git a/logic/OneSixUpdate.h b/logic/OneSixUpdate.h index 7314a6d1..ed8dcbcd 100644 --- a/logic/OneSixUpdate.h +++ b/logic/OneSixUpdate.h @@ -47,7 +47,7 @@ private: DownloadJobPtr jarlibDownloadJob; // target version, determined during this task - QSharedPointer<MinecraftVersion> targetVersion; + std::shared_ptr<MinecraftVersion> targetVersion; }; diff --git a/logic/OneSixVersion.cpp b/logic/OneSixVersion.cpp index 56e272e2..51958389 100644 --- a/logic/OneSixVersion.cpp +++ b/logic/OneSixVersion.cpp @@ -1,132 +1,311 @@ #include "OneSixVersion.h" +#include "OneSixLibrary.h" +#include "OneSixRule.h" -RuleAction RuleAction_fromString(QString name) +std::shared_ptr<OneSixVersion> fromJsonV4(QJsonObject root, + std::shared_ptr<OneSixVersion> fullVersion) { - if(name == "allow") - return Allow; - if(name == "disallow") - return Disallow; - return Defer; -} - -OpSys OpSys_fromString(QString name) -{ - if(name == "linux") - return Os_Linux; - if(name == "windows") - return Os_Windows; - if(name == "osx") - return Os_OSX; - return Os_Other; -} + fullVersion->id = root.value("id").toString(); -void Library::finalize() -{ - QStringList parts = m_name.split ( ':' ); - QString relative = parts[0]; - relative.replace ( '.','/' ); - relative += '/' + parts[1] + '/' + parts[2] + '/' + parts[1] + '-' + parts[2]; - if ( !m_is_native ) - relative += ".jar"; - else + fullVersion->mainClass = root.value("mainClass").toString(); + auto procArgsValue = root.value("processArguments"); + if (procArgsValue.isString()) { - if ( m_native_suffixes.contains ( currentSystem ) ) + fullVersion->processArguments = procArgsValue.toString(); + QString toCompare = fullVersion->processArguments.toLower(); + if (toCompare == "legacy") { - relative += "-" + m_native_suffixes[currentSystem] + ".jar"; + fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; } - else + else if (toCompare == "username_session") { - // really, bad. - relative += ".jar"; + fullVersion->minecraftArguments = + "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + fullVersion->minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; } } - m_storage_path = relative; - m_download_path = m_base_url + relative; - if ( m_rules.empty() ) + auto minecraftArgsValue = root.value("minecraftArguments"); + if (minecraftArgsValue.isString()) { - m_is_active = true; + fullVersion->minecraftArguments = minecraftArgsValue.toString(); } - else + + auto minecraftTypeValue = root.value("type"); + if (minecraftTypeValue.isString()) { - RuleAction result = Disallow; - for ( auto rule: m_rules ) - { - RuleAction temp = rule->apply ( this ); - if ( temp != Defer ) - result = temp; - } - m_is_active = ( result == Allow ); + fullVersion->type = minecraftTypeValue.toString(); } - if ( m_is_native ) + + fullVersion->releaseTime = root.value("releaseTime").toString(); + fullVersion->time = root.value("time").toString(); + + // Iterate through the list, if it's a list. + auto librariesValue = root.value("libraries"); + if (!librariesValue.isArray()) + return fullVersion; + + QJsonArray libList = root.value("libraries").toArray(); + for (auto libVal : libList) { - m_is_active = m_is_active && m_native_suffixes.contains ( currentSystem ); + if (!libVal.isObject()) + { + continue; + } + + QJsonObject libObj = libVal.toObject(); + + // Library name + auto nameVal = libObj.value("name"); + if (!nameVal.isString()) + continue; + std::shared_ptr<OneSixLibrary> library(new OneSixLibrary(nameVal.toString())); + + auto urlVal = libObj.value("url"); + if (urlVal.isString()) + { + library->setBaseUrl(urlVal.toString()); + } + auto hintVal = libObj.value("MMC-hint"); + if (hintVal.isString()) + { + library->setHint(hintVal.toString()); + } + auto urlAbsVal = libObj.value("MMC-absoluteUrl"); + auto urlAbsuVal = libObj.value("MMC-absulute_url"); // compatibility + if (urlAbsVal.isString()) + { + library->setAbsoluteUrl(urlAbsVal.toString()); + } + else if(urlAbsuVal.isString()) + { + library->setAbsoluteUrl(urlAbsuVal.toString()); + } + // Extract excludes (if any) + auto extractVal = libObj.value("extract"); + if (extractVal.isObject()) + { + QStringList excludes; + auto extractObj = extractVal.toObject(); + auto excludesVal = extractObj.value("exclude"); + if (excludesVal.isArray()) + { + auto excludesList = excludesVal.toArray(); + for (auto excludeVal : excludesList) + { + if (excludeVal.isString()) + excludes.append(excludeVal.toString()); + } + library->extract_excludes = excludes; + } + } + + auto nativesVal = libObj.value("natives"); + if (nativesVal.isObject()) + { + library->setIsNative(); + auto nativesObj = nativesVal.toObject(); + auto iter = nativesObj.begin(); + while (iter != nativesObj.end()) + { + auto osType = OpSys_fromString(iter.key()); + if (osType == Os_Other) + continue; + if (!iter.value().isString()) + continue; + library->addNative(osType, iter.value().toString()); + iter++; + } + } + library->setRules(rulesFromJsonV4(libObj)); + library->finalize(); + fullVersion->libraries.append(library); } + return fullVersion; } -void Library::setName ( QString name ) +std::shared_ptr<OneSixVersion> OneSixVersion::fromJson(QJsonObject root) { - m_name = name; -} -void Library::setBaseUrl ( QString base_url ) -{ - m_base_url = base_url; + std::shared_ptr<OneSixVersion> readVersion(new OneSixVersion()); + int launcher_ver = readVersion->minimumLauncherVersion = + root.value("minimumLauncherVersion").toDouble(); + + // ADD MORE HERE :D + if (launcher_ver > 0 && launcher_ver <= 9) + return fromJsonV4(root, readVersion); + else + { + return std::shared_ptr<OneSixVersion>(); + } } -void Library::setIsNative() + +std::shared_ptr<OneSixVersion> OneSixVersion::fromFile(QString filepath) { - m_is_native = true; + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) + { + return std::shared_ptr<OneSixVersion>(); + } + + auto data = file.readAll(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + return std::shared_ptr<OneSixVersion>(); + } + + if (!jsonDoc.isObject()) + { + return std::shared_ptr<OneSixVersion>(); + } + QJsonObject root = jsonDoc.object(); + auto version = fromJson(root); + if(version) + version->original_file = filepath; + return version; } -void Library::addNative ( OpSys os, QString suffix ) + +bool OneSixVersion::toOriginalFile() { - m_is_native = true; - m_native_suffixes[os] = suffix; + if (original_file.isEmpty()) + return false; + QSaveFile file(original_file); + if (!file.open(QIODevice::WriteOnly)) + { + return false; + } + // serialize base attributes (those we care about anyway) + QJsonObject root; + root.insert("minecraftArguments", minecraftArguments); + root.insert("mainClass", mainClass); + root.insert("minimumLauncherVersion", minimumLauncherVersion); + root.insert("time", time); + root.insert("id", id); + root.insert("type", type); + // screw processArguments + root.insert("releaseTime", releaseTime); + QJsonArray libarray; + for(const auto & lib: libraries) + { + libarray.append(lib->toJson()); + } + if(libarray.count()) + root.insert("libraries", libarray); + QJsonDocument doc(root); + file.write(doc.toJson()); + return file.commit(); } -void Library::setRules ( QList< QSharedPointer< Rule > > rules ) + +QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNormalLibs() { - m_rules = rules; + QList<std::shared_ptr<OneSixLibrary>> output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; } -bool Library::isActive() + +QList<std::shared_ptr<OneSixLibrary>> OneSixVersion::getActiveNativeLibs() { - return m_is_active; + QList<std::shared_ptr<OneSixLibrary>> output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; } -bool Library::isNative() + +void OneSixVersion::externalUpdateStart() { - return m_is_native; + beginResetModel(); } -QString Library::downloadPath() + +void OneSixVersion::externalUpdateFinish() { - return m_download_path; + endResetModel(); } -QString Library::storagePath() + +QVariant OneSixVersion::data(const QModelIndex &index, int role) const { - return m_storage_path; -} + if (!index.isValid()) + return QVariant(); + int row = index.row(); + int column = index.column(); -QList<QSharedPointer<Library> > OneSixVersion::getActiveNormalLibs() -{ - QList<QSharedPointer<Library> > output; - for ( auto lib: libraries ) + if (row < 0 || row >= libraries.size()) + return QVariant(); + + if (role == Qt::DisplayRole) { - if (lib->isActive() && !lib->isNative()) + switch (column) { - output.append(lib); + case 0: + return libraries[row]->name(); + case 1: + return libraries[row]->type(); + case 2: + return libraries[row]->version(); + default: + return QVariant(); } } - return output; + return QVariant(); } -QList<QSharedPointer<Library> > OneSixVersion::getActiveNativeLibs() +Qt::ItemFlags OneSixVersion::flags(const QModelIndex &index) const { - QList<QSharedPointer<Library> > output; - for ( auto lib: libraries ) + if (!index.isValid()) + return Qt::NoItemFlags; + int row = index.row(); + if (libraries[row]->isActive()) { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } + else + { + return Qt::ItemNeverHasChildren; + } + // return QAbstractListModel::flags(index); +} + +QVariant OneSixVersion::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) + return QVariant(); + switch (section) + { + case 0: + return QString("Name"); + case 1: + return QString("Type"); + case 2: + return QString("Version"); + default: + return QString(); } - return output; } +int OneSixVersion::rowCount(const QModelIndex &parent) const +{ + return libraries.size(); +} +int OneSixVersion::columnCount(const QModelIndex &parent) const +{ + return 3; +} diff --git a/logic/OneSixVersion.h b/logic/OneSixVersion.h index 89b7c911..3529138c 100644 --- a/logic/OneSixVersion.h +++ b/logic/OneSixVersion.h @@ -1,155 +1,38 @@ #pragma once #include <QtCore> +#include <memory> -class Library; +class OneSixLibrary; -enum OpSys +class OneSixVersion : public QAbstractListModel { - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other -}; - -OpSys OpSys_fromString(QString); - -#ifdef Q_OS_WIN32 - #define currentSystem Os_Windows -#else - #ifdef Q_OS_MAC - #define currentSystem Os_OSX - #else - #define currentSystem Os_Linux - #endif -#endif -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -RuleAction RuleAction_fromString(QString); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(Library * parent) = 0; + // Things required to implement the Qt list model public: - Rule(RuleAction result) - :m_result(result) {} - virtual ~Rule(){}; - RuleAction apply(Library * parent) - { - if(applies(parent)) - return m_result; - else - return Defer; - }; -}; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; -protected: - virtual bool applies ( Library* ) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) {} + // serialization/deserialization public: - static QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp) - { - return QSharedPointer<OsRule> (new OsRule(result, system, version_regexp)); - } -}; + bool toOriginalFile(); + static std::shared_ptr<OneSixVersion> fromJson(QJsonObject root); + static std::shared_ptr<OneSixVersion> fromFile(QString filepath); -class ImplicitRule : public Rule -{ -protected: - virtual bool applies ( Library* ) - { - return true; - } - ImplicitRule(RuleAction result) - : Rule(result) {} public: - static QSharedPointer<ImplicitRule> create(RuleAction result) - { - return QSharedPointer<ImplicitRule> (new ImplicitRule(result)); - } -}; + QList<std::shared_ptr<OneSixLibrary>> getActiveNormalLibs(); + QList<std::shared_ptr<OneSixLibrary>> getActiveNativeLibs(); + // called when something starts/stops messing with the object + // FIXME: these are ugly in every possible way. + void externalUpdateStart(); + void externalUpdateFinish(); -class Library -{ -private: - // basic values used internally (so far) - QString m_name; - QString m_base_url; - QList<QSharedPointer<Rule> > m_rules; - - // derived values used for real things - /// where to store the lib locally - QString m_storage_path; - /// where to download the lib from - QString m_download_path; - /// is this lib actually active on the current OS? - bool m_is_active; - /// is the library a native? - bool m_is_native; - /// native suffixes per OS - QMap<OpSys, QString> m_native_suffixes; -public: - QStringList extract_excludes; - -public: - /// Constructor - Library(QString name) - { - m_is_native = false; - m_is_native = false; - m_name = name; - m_base_url = "https://s3.amazonaws.com/Minecraft.Download/libraries/"; - } - - /** - * finalize the library, processing the input values into derived values and state - * - * This SHALL be called after all the values are parsed or after any further change. - */ - void finalize(); - - /// Set the library composite name - void setName(QString name); - /// Set the url base for downloads - void setBaseUrl(QString base_url); - /// Call this to mark the library as 'native' (it's a zip archive with DLLs) - void setIsNative(); - /// Attach a name suffix to the specified OS native - void addNative(OpSys os, QString suffix); - /// Set the load rules - void setRules(QList<QSharedPointer<Rule> > rules); - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive(); - /// Returns true if the library is native - bool isNative(); - /// Get the URL to download the library from - QString downloadPath(); - /// Get the relative path where the library should be saved - QString storagePath(); -}; - - -class OneSixVersion -{ + // data members public: + /// file this was read from. blank, if none + QString original_file; /// the ID - determines which jar to use! ACTUALLY IMPORTANT! QString id; /// Last updated time - as a string @@ -162,26 +45,27 @@ public: * DEPRECATED: Old versions of the new vanilla launcher used this * ex: "username_session_version" */ - QString processArguments; + QString processArguments; /** * arguments that should be used for launching minecraft - * + * * ex: "--username ${auth_player_name} --session ${auth_session} * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}" */ QString minecraftArguments; /** - * the minimum launcher version required by this version ... current is 4 (at point of writing) + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) */ - int minimumLauncherVersion; + int minimumLauncherVersion = 0xDEADBEEF; /** * The main class to load first */ QString mainClass; - + /// the list of libs - both active and inactive, native and java - QList<QSharedPointer<Library> > libraries; - + QList<std::shared_ptr<OneSixLibrary>> libraries; + /* FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. @@ -197,17 +81,11 @@ public: } } ], - "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" + "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX + 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!" } */ // QList<Rule> rules; - -public: - OneSixVersion() - { - minimumLauncherVersion = 0xDEADBEEF; - } - - QList<QSharedPointer<Library> > getActiveNormalLibs(); - QList<QSharedPointer<Library> > getActiveNativeLibs(); -};
\ No newline at end of file + + +}; diff --git a/logic/OpSys.cpp b/logic/OpSys.cpp new file mode 100644 index 00000000..f101fd08 --- /dev/null +++ b/logic/OpSys.cpp @@ -0,0 +1,23 @@ +#include "OpSys.h" + +OpSys OpSys_fromString(QString name) +{ + if(name == "linux") + return Os_Linux; + if(name == "windows") + return Os_Windows; + if(name == "osx") + return Os_OSX; + return Os_Other; +} + +QString OpSys_toString(OpSys name) +{ + switch(name) + { + case Os_Linux: return "linux"; + case Os_OSX: return "osx"; + case Os_Windows: return "windows"; + default: return "other"; + } +}
\ No newline at end of file diff --git a/logic/OpSys.h b/logic/OpSys.h new file mode 100644 index 00000000..aaa2eb65 --- /dev/null +++ b/logic/OpSys.h @@ -0,0 +1,22 @@ +#pragma once +#include <QString> +enum OpSys +{ + Os_Windows, + Os_Linux, + Os_OSX, + Os_Other +}; + +OpSys OpSys_fromString(QString); +QString OpSys_toString(OpSys); + +#ifdef Q_OS_WIN32 + #define currentSystem Os_Windows +#else + #ifdef Q_OS_MAC + #define currentSystem Os_OSX + #else + #define currentSystem Os_Linux + #endif +#endif
\ No newline at end of file diff --git a/logic/VersionFactory.cpp b/logic/VersionFactory.cpp deleted file mode 100644 index 71c4d747..00000000 --- a/logic/VersionFactory.cpp +++ /dev/null @@ -1,195 +0,0 @@ -#include "VersionFactory.h" -#include "OneSixVersion.h" - -// Library rules (if any) -QList<QSharedPointer<Rule> > FullVersionFactory::parse4rules(QJsonObject & baseObj) -{ - QList<QSharedPointer<Rule> > rules; - auto rulesVal = baseObj.value("rules"); - if(rulesVal.isArray()) - { - QJsonArray ruleList = rulesVal.toArray(); - for(auto ruleVal : ruleList) - { - QSharedPointer<Rule> rule; - if(!ruleVal.isObject()) - continue; - auto ruleObj = ruleVal.toObject(); - auto actionVal = ruleObj.value("action"); - if(!actionVal.isString()) - continue; - auto action = RuleAction_fromString(actionVal.toString()); - if(action == Defer) - continue; - - auto osVal = ruleObj.value("os"); - if(!osVal.isObject()) - { - // add a new implicit action rule - rules.append(ImplicitRule::create(action)); - } - else - { - auto osObj = osVal.toObject(); - auto osNameVal = osObj.value("name"); - if(!osNameVal.isString()) - continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); - QString versionRegex = osObj.value("version").toString(); - // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); - } - } - } - return rules; -} - - -QSharedPointer<OneSixVersion> FullVersionFactory::parse4(QJsonObject root, QSharedPointer<OneSixVersion> fullVersion) -{ - fullVersion->id = root.value("id").toString(); - - fullVersion->mainClass = root.value("mainClass").toString(); - auto procArgsValue = root.value("processArguments"); - if(procArgsValue.isString()) - { - fullVersion->processArguments = procArgsValue.toString(); - QString toCompare = fullVersion->processArguments.toLower(); - if(toCompare == "legacy") - { - fullVersion->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if(toCompare == "username_session") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if(toCompare == "username_session_version") - { - fullVersion->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - } - - auto minecraftArgsValue = root.value("minecraftArguments"); - if(minecraftArgsValue.isString()) - { - fullVersion->minecraftArguments = minecraftArgsValue.toString(); - } - - auto minecraftTypeValue = root.value("type"); - if(minecraftTypeValue.isString()) - { - fullVersion->type = minecraftTypeValue.toString(); - } - - fullVersion->releaseTime = root.value("releaseTime").toString(); - fullVersion->time = root.value("time").toString(); - - // Iterate through the list, if it's a list. - auto librariesValue = root.value("libraries"); - if(!librariesValue.isArray()) - return fullVersion; - - QJsonArray libList = root.value("libraries").toArray(); - for (auto libVal : libList) - { - if (!libVal.isObject()) - { - continue; - } - - QJsonObject libObj = libVal.toObject(); - - // Library name - auto nameVal = libObj.value("name"); - if(!nameVal.isString()) - continue; - QSharedPointer<Library> library(new Library(nameVal.toString())); - - auto urlVal = libObj.value("url"); - if(urlVal.isString()) - { - library->setBaseUrl(urlVal.toString()); - } - - // Extract excludes (if any) - auto extractVal = libObj.value("extract"); - if(extractVal.isObject()) - { - QStringList excludes; - auto extractObj = extractVal.toObject(); - auto excludesVal = extractObj.value("exclude"); - if(!excludesVal.isArray()) - goto SKIP_EXTRACTS; - auto excludesList = excludesVal.toArray(); - for(auto excludeVal : excludesList) - { - if(excludeVal.isString()) - excludes.append(excludeVal.toString()); - } - library->extract_excludes = excludes; - } - SKIP_EXTRACTS: - - auto nativesVal = libObj.value("natives"); - if(nativesVal.isObject()) - { - library->setIsNative(); - auto nativesObj = nativesVal.toObject(); - auto iter = nativesObj.begin(); - while(iter != nativesObj.end()) - { - auto osType = OpSys_fromString(iter.key()); - if(osType == Os_Other) - continue; - if(!iter.value().isString()) - continue; - library->addNative(osType, iter.value().toString()); - iter++; - } - } - library->setRules(parse4rules(libObj)); - library->finalize(); - fullVersion->libraries.append(library); - } - return fullVersion; -} - -QSharedPointer<OneSixVersion> FullVersionFactory::parse(QByteArray data) -{ - QSharedPointer<OneSixVersion> readVersion(new OneSixVersion()); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - error_string = QString( "Error reading version file :") + " " + jsonError.errorString(); - m_error = FullVersionFactory::ParseError; - return QSharedPointer<OneSixVersion>(); - } - - if(!jsonDoc.isObject()) - { - error_string = "Error reading version file."; - m_error = FullVersionFactory::ParseError; - return QSharedPointer<OneSixVersion>(); - } - QJsonObject root = jsonDoc.object(); - - int launcher_ver = readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble(); - // ADD MORE HERE :D - if(launcher_ver > 0 && launcher_ver <= 7) - return parse4(root, readVersion); - else - { - error_string = "Version file was for an unrecognized launcher version. RIP"; - m_error = FullVersionFactory::UnsupportedVersion; - return QSharedPointer<OneSixVersion>(); - } -} - - -FullVersionFactory::FullVersionFactory() -{ - m_error = FullVersionFactory::AllOK; -} diff --git a/logic/VersionFactory.h b/logic/VersionFactory.h deleted file mode 100644 index 0c0ee2d4..00000000 --- a/logic/VersionFactory.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include <QtCore> - -struct OneSixVersion; -class Rule; - -class FullVersionFactory -{ -public: - enum Error - { - AllOK, // all parsed OK - ParseError, // the file was corrupted somehow - UnsupportedVersion // the file was meant for a launcher version we don't support (yet) - } m_error; - QString error_string; - -public: - FullVersionFactory(); - QSharedPointer<OneSixVersion> parse(QByteArray data); -private: - QSharedPointer<OneSixVersion> parse4(QJsonObject root, QSharedPointer<OneSixVersion> product); - QList<QSharedPointer<Rule> > parse4rules(QJsonObject & baseObj); -};
\ No newline at end of file diff --git a/logic/lists/InstVersionList.cpp b/logic/lists/BaseVersionList.cpp index 7dc67155..61da5eeb 100644 --- a/logic/lists/InstVersionList.cpp +++ b/logic/lists/BaseVersionList.cpp @@ -13,33 +13,33 @@ * limitations under the License. */ -#include "logic/lists/InstVersionList.h" -#include "logic/InstanceVersion.h" +#include "logic/lists/BaseVersionList.h" +#include "logic/BaseVersion.h" -InstVersionList::InstVersionList(QObject *parent) : +BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) { } -InstVersionPtr InstVersionList::findVersion( const QString& descriptor ) +BaseVersionPtr BaseVersionList::findVersion( const QString& descriptor ) { for (int i = 0; i < count(); i++) { - if (at(i)->descriptor == descriptor) + if (at(i)->descriptor() == descriptor) return at(i); } - return InstVersionPtr(); + return BaseVersionPtr(); } -InstVersionPtr InstVersionList::getLatestStable() const +BaseVersionPtr BaseVersionList::getLatestStable() const { if (count() <= 0) - return InstVersionPtr(); + return BaseVersionPtr(); else return at(0); } -QVariant InstVersionList::data(const QModelIndex &index, int role) const +QVariant BaseVersionList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -48,7 +48,7 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const return QVariant(); - InstVersionPtr version = at(index.row()); + BaseVersionPtr version = at(index.row()); switch (role) { @@ -56,20 +56,17 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const switch (index.column()) { case NameColumn: - return version->name; + return version->name(); case TypeColumn: return version->typeString(); - case TimeColumn: - return version->timestamp; - default: return QVariant(); } case Qt::ToolTipRole: - return version->descriptor; + return version->descriptor(); case VersionPointerRole: return qVariantFromValue(version); @@ -79,7 +76,7 @@ QVariant InstVersionList::data(const QModelIndex &index, int role) const } } -QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, int role) const +QVariant BaseVersionList::headerData(int section, Qt::Orientation orientation, int role) const { switch (role) { @@ -91,9 +88,6 @@ QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, i case TypeColumn: return "Type"; - - case TimeColumn: - return "Time"; default: return QVariant(); @@ -117,13 +111,13 @@ QVariant InstVersionList::headerData(int section, Qt::Orientation orientation, i } } -int InstVersionList::rowCount(const QModelIndex &parent) const +int BaseVersionList::rowCount(const QModelIndex &parent) const { // Return count return count(); } -int InstVersionList::columnCount(const QModelIndex &parent) const +int BaseVersionList::columnCount(const QModelIndex &parent) const { return 2; } diff --git a/logic/lists/InstVersionList.h b/logic/lists/BaseVersionList.h index bc6aa5d4..d37431ed 100644 --- a/logic/lists/InstVersionList.h +++ b/logic/lists/BaseVersionList.h @@ -20,7 +20,7 @@ #include <QAbstractListModel> #include <QSharedPointer> -#include "logic/InstanceVersion.h" +#include "logic/BaseVersion.h" class Task; @@ -36,7 +36,7 @@ class Task; * all have a default implementation, but they can be overridden by plugins to * change the behavior of the list. */ -class InstVersionList : public QAbstractListModel +class BaseVersionList : public QAbstractListModel { Q_OBJECT public: @@ -57,7 +57,7 @@ public: TimeColumn }; - explicit InstVersionList(QObject *parent = 0); + explicit BaseVersionList(QObject *parent = 0); /*! * \brief Gets a task that will reload the version list. @@ -71,7 +71,7 @@ public: virtual bool isLoaded() = 0; //! Gets the version at the given index. - virtual const InstVersionPtr at(int i) const = 0; + virtual const BaseVersionPtr at(int i) const = 0; //! Returns the number of versions in the list. virtual int count() const = 0; @@ -90,14 +90,14 @@ public: * \return A const pointer to the version with the given descriptor. NULL if * one doesn't exist. */ - virtual InstVersionPtr findVersion(const QString &descriptor); + virtual BaseVersionPtr findVersion(const QString &descriptor); /*! * \brief Gets the latest stable version of this instance type. * This is the version that will be selected by default. * By default, this is simply the first version in the list. */ - virtual InstVersionPtr getLatestStable() const; + virtual BaseVersionPtr getLatestStable() const; /*! * Sorts the version list. @@ -117,5 +117,5 @@ protected slots: * then copies the versions and sets their parents correctly. * \param versions List of versions whose parents should be set. */ - virtual void updateListData(QList<InstVersionPtr > versions) = 0; + virtual void updateListData(QList<BaseVersionPtr > versions) = 0; }; diff --git a/logic/lists/ForgeVersionList.cpp b/logic/lists/ForgeVersionList.cpp new file mode 100644 index 00000000..491a43d7 --- /dev/null +++ b/logic/lists/ForgeVersionList.cpp @@ -0,0 +1,297 @@ +/* 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 "ForgeVersionList.h" +#include <logic/net/DownloadJob.h> +#include "MultiMC.h" + +#include <QtNetwork> +#include <QtXml> +#include <QRegExp> + +#include <logger/QsLog.h> + +#define JSON_URL "http://files.minecraftforge.net/minecraftforge/json" + +ForgeVersionList::ForgeVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *ForgeVersionList::getLoadTask() +{ + return new ForgeListLoadTask(this); +} + +bool ForgeVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr ForgeVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int ForgeVersionList::count() const +{ + return m_vlist.count(); +} + +int ForgeVersionList::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant ForgeVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast<ForgeVersion>(m_vlist[index.row()]); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: + return version->name(); + + case 1: + return version->mcver; + + case 2: + return version->typeString(); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + + default: + return QVariant(); + } +} + +QVariant ForgeVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case 0: + return "Version"; + + case 1: + return "Minecraft"; + + case 2: + return "Type"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case 0: + return "The name of the version."; + + case 1: + return "Minecraft version"; + + case 2: + return "The version's type."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +BaseVersionPtr ForgeVersionList::getLatestStable() const +{ + return BaseVersionPtr(); +} + +void ForgeVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + endResetModel(); + // NOW SORT!! + // sort(); +} + +void ForgeVersionList::sort() +{ + // NO-OP for now +} + +ForgeListLoadTask::ForgeListLoadTask(ForgeVersionList *vlist) : Task() +{ + m_list = vlist; +} + +void ForgeListLoadTask::executeTask() +{ + auto job = new DownloadJob("Version index"); + // we do not care if the version is stale or not. + auto forgeListEntry = MMC->metacache()->resolveEntry("minecraftforge", "list.json"); + + // verify by poking the server. + forgeListEntry->stale = true; + + job->addCacheDownload(QUrl(JSON_URL), forgeListEntry); + listJob.reset(job); + connect(listJob.get(), SIGNAL(succeeded()), SLOT(list_downloaded())); + connect(listJob.get(), SIGNAL(failed()), SLOT(list_failed())); + connect(listJob.get(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64))); + listJob->start(); +} + +void ForgeListLoadTask::list_failed() +{ + auto DlJob = listJob->first(); + auto reply = DlJob->m_reply; + if(reply) + { + QLOG_ERROR() << "Getting forge version list failed: " << reply->errorString(); + } + else + QLOG_ERROR() << "Getting forge version list failed for reasons unknown."; +} + +void ForgeListLoadTask::list_downloaded() +{ + QByteArray data; + { + auto DlJob = listJob->first(); + auto filename = std::dynamic_pointer_cast<CacheDownload>(DlJob)->m_target_path; + QFile listFile(filename); + if(!listFile.open(QIODevice::ReadOnly)) + return; + data = listFile.readAll(); + DlJob.reset(); + } + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed("Error parsing version list JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed("Error parsing version list JSON: jsonDoc is not an object"); + return; + } + + QJsonObject root = jsonDoc.object(); + + // Now, get the array of versions. + if (!root.value("builds").isArray()) + { + emitFailed( + "Error parsing version list JSON: version list object is missing 'builds' array"); + return; + } + QJsonArray builds = root.value("builds").toArray(); + + QList<BaseVersionPtr> tempList; + for (int i = 0; i < builds.count(); i++) + { + // Load the version info. + if (!builds[i].isObject()) + { + // FIXME: log this somewhere + continue; + } + QJsonObject obj = builds[i].toObject(); + int build_nr = obj.value("build").toDouble(0); + if (!build_nr) + continue; + QJsonArray files = obj.value("files").toArray(); + QString url, jobbuildver, mcver, buildtype, filename; + QString changelog_url, installer_url; + QString installer_filename; + bool valid = false; + for (int j = 0; j < files.count(); j++) + { + if (!files[j].isObject()) + continue; + QJsonObject file = files[j].toObject(); + buildtype = file.value("buildtype").toString(); + if ((buildtype == "client" || buildtype == "universal") && !valid) + { + mcver = file.value("mcver").toString(); + url = file.value("url").toString(); + jobbuildver = file.value("jobbuildver").toString(); + int lastSlash = url.lastIndexOf('/'); + filename = url.mid(lastSlash + 1); + valid = true; + } + else if (buildtype == "changelog") + { + QString ext = file.value("ext").toString(); + if (ext.isEmpty()) + continue; + changelog_url = file.value("url").toString(); + } + else if (buildtype == "installer") + { + installer_url = file.value("url").toString(); + int lastSlash = installer_url.lastIndexOf('/'); + installer_filename = installer_url.mid(lastSlash + 1); + } + } + if (valid) + { + // Now, we construct the version object and add it to the list. + std::shared_ptr<ForgeVersion> fVersion(new ForgeVersion()); + fVersion->universal_url = url; + fVersion->changelog_url = changelog_url; + fVersion->installer_url = installer_url; + fVersion->jobbuildver = jobbuildver; + fVersion->mcver = mcver; + if (installer_filename.isEmpty()) + fVersion->filename = filename; + else + fVersion->filename = installer_filename; + fVersion->m_buildnr = build_nr; + tempList.append(fVersion); + } + } + m_list->updateListData(tempList); + + emitSucceeded(); + return; +} diff --git a/logic/lists/ForgeVersionList.h b/logic/lists/ForgeVersionList.h new file mode 100644 index 00000000..56a207c8 --- /dev/null +++ b/logic/lists/ForgeVersionList.h @@ -0,0 +1,109 @@ +/* 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. + */ + +#pragma once + +#include <QObject> +#include <QAbstractListModel> +#include <QSharedPointer> +#include <QUrl> + +#include <QNetworkReply> +#include "BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/net/DownloadJob.h" + +class ForgeVersion; +typedef std::shared_ptr<ForgeVersion> ForgeVersionPtr; + +struct ForgeVersion : public BaseVersion +{ + virtual QString descriptor() + { + return filename; + } + ; + virtual QString name() + { + return "Forge " + jobbuildver; + } + ; + virtual QString typeString() const + { + if (installer_url.isEmpty()) + return "Universal"; + else + return "Installer"; + } + ; + + int m_buildnr = 0; + QString universal_url; + QString changelog_url; + QString installer_url; + QString jobbuildver; + QString mcver; + QString filename; +}; + +class ForgeVersionList : public BaseVersionList +{ + Q_OBJECT +public: + friend class ForgeListLoadTask; + + explicit ForgeVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getLatestStable() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role) const; + virtual int columnCount(const QModelIndex &parent) const; + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded; + +protected +slots: + virtual void updateListData(QList<BaseVersionPtr> versions); +}; + +class ForgeListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit ForgeListLoadTask(ForgeVersionList *vlist); + + virtual void executeTask(); + +protected +slots: + void list_downloaded(); + void list_failed(); + +protected: + DownloadJobPtr listJob; + ForgeVersionList *m_list; +}; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index b930f781..9740d5a5 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -3,7 +3,7 @@ * 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 @@ -29,13 +29,13 @@ #include "logic/lists/IconList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" +#include <logger/QsLog.h> const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(const QString &instDir, QObject *parent) : - QAbstractListModel ( parent ), m_instDir("instances") +InstanceList::InstanceList(const QString &instDir, QObject *parent) + : QAbstractListModel(parent), m_instDir("instances") { - } InstanceList::~InstanceList() @@ -43,32 +43,32 @@ InstanceList::~InstanceList() saveGroupList(); } -int InstanceList::rowCount ( const QModelIndex& parent ) const +int InstanceList::rowCount(const QModelIndex &parent) const { - Q_UNUSED ( parent ); + Q_UNUSED(parent); return m_instances.count(); } -QModelIndex InstanceList::index ( int row, int column, const QModelIndex& parent ) const +QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const { - Q_UNUSED ( parent ); - if ( row < 0 || row >= m_instances.size() ) + Q_UNUSED(parent); + if (row < 0 || row >= m_instances.size()) return QModelIndex(); - return createIndex ( row, column, ( void* ) m_instances.at ( row ).data() ); + return createIndex(row, column, (void *)m_instances.at(row).get()); } -QVariant InstanceList::data ( const QModelIndex& index, int role ) const +QVariant InstanceList::data(const QModelIndex &index, int role) const { - if ( !index.isValid() ) + if (!index.isValid()) { return QVariant(); } - BaseInstance *pdata = static_cast<BaseInstance*> ( index.internalPointer() ); - switch ( role ) + BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer()); + switch (role) { case InstancePointerRole: { - QVariant v = qVariantFromValue((void *) pdata); + QVariant v = qVariantFromValue((void *)pdata); return v; } case Qt::DisplayRole: @@ -96,12 +96,12 @@ QVariant InstanceList::data ( const QModelIndex& index, int role ) const return QVariant(); } -Qt::ItemFlags InstanceList::flags ( const QModelIndex& index ) const +Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const { Qt::ItemFlags f; - if ( index.isValid() ) + if (index.isValid()) { - f |= ( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); } return f; } @@ -116,23 +116,23 @@ void InstanceList::saveGroupList() { QString groupFileName = m_instDir + "/instgroups.json"; QFile groupFile(groupFileName); - + // if you can't open the file, fail - if (!groupFile.open(QIODevice::WriteOnly| QIODevice::Truncate)) + if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { // An error occurred. Ignore it. - qDebug("Failed to read instance group file."); + QLOG_ERROR() << "Failed to read instance group file."; return; } QTextStream out(&groupFile); - QMap<QString, QSet<QString> > groupMap; - for(auto instance: m_instances) + QMap<QString, QSet<QString>> groupMap; + for (auto instance : m_instances) { QString id = instance->id(); QString group = instance->group(); - if(group.isEmpty()) + if (group.isEmpty()) continue; - if(!groupMap.count(group)) + if (!groupMap.count(group)) { QSet<QString> set; set.insert(id); @@ -145,109 +145,114 @@ void InstanceList::saveGroupList() } } QJsonObject toplevel; - toplevel.insert("formatVersion",QJsonValue(QString("1"))); + toplevel.insert("formatVersion", QJsonValue(QString("1"))); QJsonObject groupsArr; - for(auto iter = groupMap.begin(); iter != groupMap.end(); iter++) + for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++) { auto list = iter.value(); auto name = iter.key(); QJsonObject groupObj; QJsonArray instanceArr; - groupObj.insert("hidden",QJsonValue(QString("false"))); - for(auto item: list) + groupObj.insert("hidden", QJsonValue(QString("false"))); + for (auto item : list) { instanceArr.append(QJsonValue(item)); } - groupObj.insert("instances",instanceArr); - groupsArr.insert(name,groupObj); + groupObj.insert("instances", instanceArr); + groupsArr.insert(name, groupObj); } - toplevel.insert("groups",groupsArr); + toplevel.insert("groups", groupsArr); QJsonDocument doc(toplevel); groupFile.write(doc.toJson()); groupFile.close(); } -void InstanceList::loadGroupList(QMap<QString, QString> & groupMap) +void InstanceList::loadGroupList(QMap<QString, QString> &groupMap) { QString groupFileName = m_instDir + "/instgroups.json"; - + // if there's no group file, fail - if(!QFileInfo(groupFileName).exists()) + if (!QFileInfo(groupFileName).exists()) return; - + QFile groupFile(groupFileName); - + // if you can't open the file, fail if (!groupFile.open(QIODevice::ReadOnly)) { // An error occurred. Ignore it. - qDebug("Failed to read instance group file."); + QLOG_ERROR() << "Failed to read instance group file."; return; } - + QTextStream in(&groupFile); QString jsonStr = in.readAll(); groupFile.close(); - + QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error); - + // if the json was bad, fail if (error.error != QJsonParseError::NoError) { - qWarning(QString("Failed to parse instance group file: %1 at offset %2"). - arg(error.errorString(), QString::number(error.offset)).toUtf8()); + QLOG_ERROR() << QString("Failed to parse instance group file: %1 at offset %2") + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); return; } - + // if the root of the json wasn't an object, fail if (!jsonDoc.isObject()) { qWarning("Invalid group file. Root entry should be an object."); return; } - + QJsonObject rootObj = jsonDoc.object(); - + // Make sure the format version matches, otherwise fail. if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) return; - + // Get the groups. if it's not an object, fail if (!rootObj.value("groups").isObject()) { qWarning("Invalid group list JSON: 'groups' should be an object."); return; } - + // Iterate through all the groups. QJsonObject groupMapping = rootObj.value("groups").toObject(); for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) { QString groupName = iter.key(); - + // If not an object, complain and skip to the next one. if (!iter.value().isObject()) { qWarning(QString("Group '%1' in the group list should " - "be an object.").arg(groupName).toUtf8()); + "be an object.") + .arg(groupName) + .toUtf8()); continue; } - + QJsonObject groupObj = iter.value().toObject(); if (!groupObj.value("instances").isArray()) { qWarning(QString("Group '%1' in the group list is invalid. " - "It should contain an array " - "called 'instances'.").arg(groupName).toUtf8()); + "It should contain an array " + "called 'instances'.") + .arg(groupName) + .toUtf8()); continue; } - + // Iterate through the list of instances in the group. QJsonArray instancesArray = groupObj.value("instances").toArray(); - - for (QJsonArray::iterator iter2 = instancesArray.begin(); - iter2 != instancesArray.end(); iter2++) + + for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); + iter2++) { groupMap[(*iter2).toString()] = groupName; } @@ -259,9 +264,9 @@ InstanceList::InstListError InstanceList::loadList() // load the instance groups QMap<QString, QString> groupMap; loadGroupList(groupMap); - + beginResetModel(); - + m_instances.clear(); QDir dir(m_instDir); QDirIterator iter(dir); @@ -270,53 +275,55 @@ InstanceList::InstListError InstanceList::loadList() QString subDir = iter.next(); if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) continue; - + BaseInstance *instPtr = NULL; auto &loader = InstanceFactory::get(); auto error = loader.loadInstance(instPtr, subDir); - - switch(error) + + switch (error) { - case InstanceFactory::NoLoadError: - break; - case InstanceFactory::NotAnInstance: - break; + case InstanceFactory::NoLoadError: + break; + case InstanceFactory::NotAnInstance: + break; } - - if (error != InstanceFactory::NoLoadError && - error != InstanceFactory::NotAnInstance) + + if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) { - QString errorMsg = QString("Failed to load instance %1: "). - arg(QFileInfo(subDir).baseName()).toUtf8(); - + QString errorMsg = QString("Failed to load instance %1: ") + .arg(QFileInfo(subDir).baseName()) + .toUtf8(); + switch (error) { default: - errorMsg += QString("Unknown instance loader error %1"). - arg(error); + errorMsg += QString("Unknown instance loader error %1").arg(error); break; } - qDebug(errorMsg.toUtf8()); + QLOG_ERROR() << errorMsg.toUtf8(); } else if (!instPtr) { - qDebug(QString("Error loading instance %1. Instance loader returned null."). - arg(QFileInfo(subDir).baseName()).toUtf8()); + QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.") + .arg(QFileInfo(subDir).baseName()) + .toUtf8(); } else { - QSharedPointer<BaseInstance> inst(instPtr); + std::shared_ptr<BaseInstance> inst(instPtr); auto iter = groupMap.find(inst->id()); - if(iter != groupMap.end()) + if (iter != groupMap.end()) { inst->setGroupInitial((*iter)); } - qDebug(QString("Loaded instance %1").arg(inst->name()).toUtf8()); + QLOG_INFO() << "Loaded instance " << inst->name(); inst->setParent(this); m_instances.append(inst); - connect(instPtr, SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*))); - connect(instPtr, SIGNAL(groupChanged()),this, SLOT(groupChanged())); - connect(instPtr, SIGNAL(nuked(BaseInstance*)), this, SLOT(instanceNuked(BaseInstance*))); + connect(instPtr, SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(instPtr, SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(instPtr, SIGNAL(nuked(BaseInstance *)), this, + SLOT(instanceNuked(BaseInstance *))); } } endResetModel(); @@ -332,7 +339,8 @@ void InstanceList::clear() m_instances.clear(); endResetModel(); emit dataIsInvalid(); -}; +} +; /// Add an instance. Triggers notifications, returns the new index int InstanceList::add(InstancePtr t) @@ -340,9 +348,10 @@ int InstanceList::add(InstancePtr t) beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size()); m_instances.append(t); t->setParent(this); - connect(t.data(), SIGNAL(propertiesChanged(BaseInstance*)),this, SLOT(propertiesChanged(BaseInstance*))); - connect(t.data(), SIGNAL(groupChanged()),this, SLOT(groupChanged())); - connect(t.data(), SIGNAL(nuked(BaseInstance*)), this, SLOT(instanceNuked(BaseInstance*))); + connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *))); endInsertRows(); return count() - 1; } @@ -351,7 +360,7 @@ InstancePtr InstanceList::getInstanceById(QString instId) { QListIterator<InstancePtr> iter(m_instances); InstancePtr inst; - while(iter.hasNext()) + while (iter.hasNext()) { inst = iter.next(); if (inst->id() == instId) @@ -363,11 +372,11 @@ InstancePtr InstanceList::getInstanceById(QString instId) return iter.peekPrevious(); } -int InstanceList::getInstIndex ( BaseInstance* inst ) +int InstanceList::getInstIndex(BaseInstance *inst) { - for(int i = 0; i < m_instances.count(); i++) + for (int i = 0; i < m_instances.count(); i++) { - if(inst == m_instances[i].data()) + if (inst == m_instances[i].get()) { return i; } @@ -375,40 +384,39 @@ int InstanceList::getInstIndex ( BaseInstance* inst ) return -1; } - -void InstanceList::instanceNuked ( BaseInstance* inst ) +void InstanceList::instanceNuked(BaseInstance *inst) { int i = getInstIndex(inst); - if(i != -1) + if (i != -1) { - beginRemoveRows(QModelIndex(),i,i); + beginRemoveRows(QModelIndex(), i, i); m_instances.removeAt(i); endRemoveRows(); } } - -void InstanceList::propertiesChanged(BaseInstance * inst) +void InstanceList::propertiesChanged(BaseInstance *inst) { int i = getInstIndex(inst); - if(i != -1) + if (i != -1) { emit dataChanged(index(i), index(i)); } } -InstanceProxyModel::InstanceProxyModel ( QObject *parent ) - : KCategorizedSortFilterProxyModel ( parent ) +InstanceProxyModel::InstanceProxyModel(QObject *parent) + : KCategorizedSortFilterProxyModel(parent) { // disable since by default we are globally sorting by date: setCategorizedModel(true); } -bool InstanceProxyModel::subSortLessThan (const QModelIndex& left, const QModelIndex& right ) const +bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, + const QModelIndex &right) const { - BaseInstance *pdataLeft = static_cast<BaseInstance*> ( left.internalPointer() ); - BaseInstance *pdataRight = static_cast<BaseInstance*> ( right.internalPointer() ); - //kDebug() << *pdataLeft << *pdataRight; + BaseInstance *pdataLeft = static_cast<BaseInstance *>(left.internalPointer()); + BaseInstance *pdataRight = static_cast<BaseInstance *>(right.internalPointer()); + // kDebug() << *pdataLeft << *pdataRight; return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; - //return pdataLeft->name() < pdataRight->name(); + // return pdataLeft->name() < pdataRight->name(); } diff --git a/logic/lists/JavaVersionList.cpp b/logic/lists/JavaVersionList.cpp new file mode 100644 index 00000000..5389c4cc --- /dev/null +++ b/logic/lists/JavaVersionList.cpp @@ -0,0 +1,203 @@ +/* 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 "JavaVersionList.h" +#include "MultiMC.h" + +#include <QtNetwork> +#include <QtXml> +#include <QRegExp> + +#include <logger/QsLog.h> +#include <logic/JavaUtils.h> + +JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *JavaVersionList::getLoadTask() +{ + return new JavaListLoadTask(this); +} + + +const BaseVersionPtr JavaVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +bool JavaVersionList::isLoaded() +{ + return m_loaded; +} + +int JavaVersionList::count() const +{ + return m_vlist.count(); +} + +int JavaVersionList::columnCount(const QModelIndex &parent) const +{ + return 4; +} + +QVariant JavaVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast<JavaVersion>(m_vlist[index.row()]); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: + return version->id; + + case 1: + return version->arch; + + case 2: + return version->path; + + case 3: + return version->recommended ? tr("Yes") : tr("No"); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + + default: + return QVariant(); + } +} + +QVariant JavaVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case 0: + return "Version"; + + case 1: + return "Arch"; + + case 2: + return "Path"; + + case 3: + return "Recommended"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case 0: + return "The name of the version."; + + case 1: + return "The architecture this version is for."; + + case 2: + return "Path to this Java version."; + + case 3: + return "Whether the version is recommended or not."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +BaseVersionPtr JavaVersionList::getTopRecommended() const +{ + for (int i = 0; i < m_vlist.length(); i++) + { + auto ver = std::dynamic_pointer_cast<JavaVersion>(m_vlist.at(i)); + if (ver->recommended) + { + return m_vlist.at(i); + } + } + return BaseVersionPtr(); +} + +void JavaVersionList::updateListData(QList<BaseVersionPtr> versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + endResetModel(); + // NOW SORT!! + // sort(); +} + +void JavaVersionList::sort() +{ + // NO-OP for now +} + +JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) +{ + m_list = vlist; + m_currentRecommended = NULL; +} + +JavaListLoadTask::~JavaListLoadTask() +{ +} + +void JavaListLoadTask::executeTask() +{ + setStatus("Detecting Java installations..."); + + JavaUtils ju; + QList<JavaVersionPtr> javas = ju.FindJavaPaths(); + + QList<BaseVersionPtr> javas_bvp; + for(int i = 0; i < javas.length(); i++) + { + BaseVersionPtr java = std::dynamic_pointer_cast<BaseVersion>(javas.at(i)); + + if(java) + { + javas_bvp.append(java); + } + } + + m_list->updateListData(javas_bvp); + + emitSucceeded(); +} diff --git a/logic/lists/JavaVersionList.h b/logic/lists/JavaVersionList.h new file mode 100644 index 00000000..23bccfe4 --- /dev/null +++ b/logic/lists/JavaVersionList.h @@ -0,0 +1,93 @@ +/* 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. + */ + +#pragma once + +#include <QObject> +#include <QAbstractListModel> +#include <QSharedPointer> + +#include "BaseVersionList.h" +#include "logic/tasks/Task.h" + +class JavaListLoadTask; + +struct JavaVersion : public BaseVersion +{ + virtual QString descriptor() + { + return id; + } + + virtual QString name() + { + return id; + } + + virtual QString typeString() const + { + return arch; + } + + QString id; + QString arch; + QString path; + bool recommended; +}; + +typedef std::shared_ptr<JavaVersion> JavaVersionPtr; + +class JavaVersionList : public BaseVersionList +{ + Q_OBJECT +public: + explicit JavaVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getTopRecommended() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role) const; + virtual int columnCount(const QModelIndex &parent) const; + +public slots: + virtual void updateListData(QList<BaseVersionPtr> versions); + +protected: + QList<BaseVersionPtr> m_vlist; + + bool m_loaded = false; +}; + +class JavaListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit JavaListLoadTask(JavaVersionList *vlist); + ~JavaListLoadTask(); + + virtual void executeTask(); + +protected: + JavaVersionList *m_list; + JavaVersion *m_currentRecommended; +}; diff --git a/logic/lists/LwjglVersionList.cpp b/logic/lists/LwjglVersionList.cpp index d7826a82..6bf401ec 100644 --- a/logic/lists/LwjglVersionList.cpp +++ b/logic/lists/LwjglVersionList.cpp @@ -3,7 +3,7 @@ * 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 @@ -17,23 +17,14 @@ #include "MultiMC.h" #include <QtNetwork> - #include <QtXml> - #include <QRegExp> -#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" - -LWJGLVersionList mainVersionList; - -LWJGLVersionList &LWJGLVersionList::get() -{ - return mainVersionList; -} +#include <logger/QsLog.h> +#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" -LWJGLVersionList::LWJGLVersionList(QObject *parent) : - QAbstractListModel(parent) +LWJGLVersionList::LWJGLVersionList(QObject *parent) : QAbstractListModel(parent) { setLoading(false); } @@ -42,20 +33,20 @@ QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - + if (index.row() > count()) return QVariant(); - + const PtrLWJGLVersion version = at(index.row()); - + switch (role) { case Qt::DisplayRole: return version->name(); - + case Qt::ToolTipRole: return version->url(); - + default: return QVariant(); } @@ -67,10 +58,10 @@ QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, { case Qt::DisplayRole: return "Version"; - + case Qt::ToolTipRole: return "LWJGL version name."; - + default: return QVariant(); } @@ -89,7 +80,7 @@ bool LWJGLVersionList::isLoading() const void LWJGLVersionList::loadList() { Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); - + setLoading(true); auto worker = MMC->qnam(); reply = worker->get(QNetworkRequest(QUrl(RSS_URL))); @@ -110,68 +101,68 @@ void LWJGLVersionList::netRequestComplete() if (reply->error() == QNetworkReply::NoError) { QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); - Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", - "LWJGL regex is invalid"); - + Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); + QDomDocument doc; - + QString xmlErrorMsg; int errorLine; if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine)) { - failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine)); + failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + + QString::number(errorLine)); setLoading(false); return; } - + QDomNodeList items = doc.elementsByTagName("item"); - + QList<PtrLWJGLVersion> tempList; - + for (int i = 0; i < items.length(); i++) { Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", "XML element isn't an element... wat?"); - + QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); if (linkElement.isNull()) { qWarning() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; continue; } - + QString link = linkElement.text(); - + // Make sure it's a download link. if (link.endsWith("/download") && link.contains(lwjglRegex)) { QString name = link.mid(lwjglRegex.indexIn(link) + 6); // Subtract 4 here to remove the .zip file extension. name = name.left(lwjglRegex.matchedLength() - 10); - + QUrl url(link); if (!url.isValid()) { qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping."; continue; } - + tempList.append(LWJGLVersion::Create(name, link)); } } - + beginResetModel(); m_vlist.swap(tempList); endResetModel(); - - qDebug("Loaded LWJGL list."); + + QLOG_INFO() << "Loaded LWJGL list."; finished(); } else { failed("Failed to load LWJGL list. Network error: " + reply->errorString()); } - + setLoading(false); reply->deleteLater(); } @@ -181,13 +172,12 @@ const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName) for (int i = 0; i < count(); i++) { QString name = at(i)->name(); - if ( name == versionName) + if (name == versionName) return at(i); } return PtrLWJGLVersion(); } - void LWJGLVersionList::failed(QString msg) { qWarning() << msg; diff --git a/logic/lists/LwjglVersionList.h b/logic/lists/LwjglVersionList.h index 638a0b67..99712292 100644 --- a/logic/lists/LwjglVersionList.h +++ b/logic/lists/LwjglVersionList.h @@ -17,13 +17,13 @@ #include <QObject> #include <QAbstractListModel> -#include <QSharedPointer> #include <QUrl> - #include <QNetworkReply> +#include <memory> + class LWJGLVersion; -typedef QSharedPointer<LWJGLVersion> PtrLWJGLVersion; +typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion; class LWJGLVersion : public QObject { @@ -53,8 +53,6 @@ class LWJGLVersionList : public QAbstractListModel public: explicit LWJGLVersionList(QObject *parent = 0); - static LWJGLVersionList &get(); - bool isLoaded() { return m_vlist.length() > 0; } const PtrLWJGLVersion getVersion(const QString &versionName); diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 42fb1b50..dd26714a 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -3,7 +3,7 @@ * 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 @@ -16,8 +16,6 @@ #include "MinecraftVersionList.h" #include <MultiMC.h> -#include <QDebug> - #include <QtXml> #include <QJsonDocument> @@ -34,12 +32,8 @@ #define ASSETS_URLBASE "http://assets.minecraft.net/" #define MCN_URLBASE "http://sonicrules.org/mcnweb.py" -MinecraftVersionList mcVList; - -MinecraftVersionList::MinecraftVersionList(QObject *parent) : - InstVersionList(parent) +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) { - } Task *MinecraftVersionList::getLoadTask() @@ -52,7 +46,7 @@ bool MinecraftVersionList::isLoaded() return m_loaded; } -const InstVersionPtr MinecraftVersionList::at(int i) const +const BaseVersionPtr MinecraftVersionList::at(int i) const { return m_vlist.at(i); } @@ -62,11 +56,11 @@ int MinecraftVersionList::count() const return m_vlist.count(); } -bool cmpVersions(InstVersionPtr first, InstVersionPtr second) +bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) { - const InstVersion & left = *first; - const InstVersion & right = *second; - return left > right; + auto left = std::dynamic_pointer_cast<MinecraftVersion>(first); + auto right = std::dynamic_pointer_cast<MinecraftVersion>(second); + return left->timestamp > right->timestamp; } void MinecraftVersionList::sort() @@ -76,25 +70,20 @@ void MinecraftVersionList::sort() endResetModel(); } -InstVersionPtr MinecraftVersionList::getLatestStable() const +BaseVersionPtr MinecraftVersionList::getLatestStable() const { for (int i = 0; i < m_vlist.length(); i++) { - auto ver = m_vlist.at(i).dynamicCast<MinecraftVersion>(); + auto ver = std::dynamic_pointer_cast<MinecraftVersion>(m_vlist.at(i)); if (ver->is_latest && !ver->is_snapshot) { return m_vlist.at(i); } } - return InstVersionPtr(); -} - -MinecraftVersionList &MinecraftVersionList::getMainList() -{ - return mcVList; + return BaseVersionPtr(); } -void MinecraftVersionList::updateListData(QList<InstVersionPtr > versions) +void MinecraftVersionList::updateListData(QList<BaseVersionPtr> versions) { beginResetModel(); m_vlist = versions; @@ -118,7 +107,6 @@ inline QDateTime timeFromS3Time(QString str) return QDateTime::fromString(str, Qt::ISODate); } - MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) { m_list = vlist; @@ -131,9 +119,13 @@ MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) legacyWhitelist.insert("1.4.6"); legacyWhitelist.insert("1.4.5"); legacyWhitelist.insert("1.4.4"); + legacyWhitelist.insert("1.4.3"); legacyWhitelist.insert("1.4.2"); + legacyWhitelist.insert("1.4.1"); + legacyWhitelist.insert("1.4"); legacyWhitelist.insert("1.3.2"); legacyWhitelist.insert("1.3.1"); + legacyWhitelist.insert("1.3"); legacyWhitelist.insert("1.2.5"); legacyWhitelist.insert("1.2.4"); legacyWhitelist.insert("1.2.3"); @@ -156,91 +148,91 @@ void MCVListLoadTask::executeTask() connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); } - void MCVListLoadTask::list_downloaded() { - if(vlistReply->error() != QNetworkReply::QNetworkReply::NoError) + if (vlistReply->error() != QNetworkReply::NoError) { vlistReply->deleteLater(); emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); return; } - + QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(vlistReply->readAll(), &jsonError); vlistReply->deleteLater(); - + if (jsonError.error != QJsonParseError::NoError) { emitFailed("Error parsing version list JSON:" + jsonError.errorString()); return; } - if(!jsonDoc.isObject()) + if (!jsonDoc.isObject()) { emitFailed("Error parsing version list JSON: jsonDoc is not an object"); return; } - + QJsonObject root = jsonDoc.object(); - + // Get the ID of the latest release and the latest snapshot. - if(!root.value("latest").isObject()) + if (!root.value("latest").isObject()) { emitFailed("Error parsing version list JSON: version list is missing 'latest' object"); return; } - + QJsonObject latest = root.value("latest").toObject(); - + QString latestReleaseID = latest.value("release").toString(""); QString latestSnapshotID = latest.value("snapshot").toString(""); - if(latestReleaseID.isEmpty()) + if (latestReleaseID.isEmpty()) { emitFailed("Error parsing version list JSON: latest release field is missing"); return; } - if(latestSnapshotID.isEmpty()) + if (latestSnapshotID.isEmpty()) { emitFailed("Error parsing version list JSON: latest snapshot field is missing"); return; } // Now, get the array of versions. - if(!root.value("versions").isArray()) + if (!root.value("versions").isArray()) { - emitFailed("Error parsing version list JSON: version list object is missing 'versions' array"); + emitFailed( + "Error parsing version list JSON: version list object is missing 'versions' array"); return; } QJsonArray versions = root.value("versions").toArray(); - - QList<InstVersionPtr > tempList; + + QList<BaseVersionPtr> tempList; for (int i = 0; i < versions.count(); i++) { bool is_snapshot = false; bool is_latest = false; - + // Load the version info. - if(!versions[i].isObject()) + if (!versions[i].isObject()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } QJsonObject version = versions[i].toObject(); QString versionID = version.value("id").toString(""); QString versionTimeStr = version.value("releaseTime").toString(""); QString versionTypeStr = version.value("type").toString(""); - if(versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } - + // Parse the timestamp. QDateTime versionTime = timeFromS3Time(versionTimeStr); - if(!versionTime.isValid()) + if (!versionTime.isValid()) { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } // Parse the type. @@ -248,23 +240,25 @@ void MCVListLoadTask::list_downloaded() // OneSix or Legacy. use filter to determine type if (versionTypeStr == "release") { - versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix; + versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy + : MinecraftVersion::OneSix; is_latest = (versionID == latestReleaseID); is_snapshot = false; } - else if(versionTypeStr == "snapshot") // It's a snapshot... yay + else if (versionTypeStr == "snapshot") // It's a snapshot... yay { - versionType = legacyWhitelist.contains(versionID)?MinecraftVersion::Legacy:MinecraftVersion::OneSix; + versionType = legacyWhitelist.contains(versionID) ? MinecraftVersion::Legacy + : MinecraftVersion::OneSix; is_latest = (versionID == latestSnapshotID); is_snapshot = true; } - else if(versionTypeStr == "old_alpha") + else if (versionTypeStr == "old_alpha") { versionType = MinecraftVersion::Nostalgia; is_latest = false; is_snapshot = false; } - else if(versionTypeStr == "old_beta") + else if (versionTypeStr == "old_beta") { versionType = MinecraftVersion::Legacy; is_latest = false; @@ -272,15 +266,15 @@ void MCVListLoadTask::list_downloaded() } else { - //FIXME: log this somewhere + // FIXME: log this somewhere continue; } // Get the download URL. QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/"; - + // Now, we construct the version object and add it to the list. - QSharedPointer<MinecraftVersion> mcVersion(new MinecraftVersion()); - mcVersion->name = mcVersion->descriptor = versionID; + std::shared_ptr<MinecraftVersion> mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); mcVersion->download_url = dlUrl; mcVersion->is_latest = is_latest; @@ -289,12 +283,7 @@ void MCVListLoadTask::list_downloaded() tempList.append(mcVersion); } m_list->updateListData(tempList); - + emitSucceeded(); return; } - -// FIXME: we should have a local cache of the version list and a local cache of version data -bool MCVListLoadTask::loadFromVList() -{ -} diff --git a/logic/lists/MinecraftVersionList.h b/logic/lists/MinecraftVersionList.h index 0477379f..ed68efbb 100644 --- a/logic/lists/MinecraftVersionList.h +++ b/logic/lists/MinecraftVersionList.h @@ -20,14 +20,14 @@ #include <QSet> #include <QSharedPointer> -#include "InstVersionList.h" +#include "BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/MinecraftVersion.h" class MCVListLoadTask; class QNetworkReply; -class MinecraftVersionList : public InstVersionList +class MinecraftVersionList : public BaseVersionList { Q_OBJECT public: @@ -37,25 +37,19 @@ public: virtual Task *getLoadTask(); virtual bool isLoaded(); - virtual const InstVersionPtr at(int i) const; + virtual const BaseVersionPtr at(int i) const; virtual int count() const; virtual void sort(); - virtual InstVersionPtr getLatestStable() const; - - /*! - * Gets the main version list instance. - */ - static MinecraftVersionList &getMainList(); - + virtual BaseVersionPtr getLatestStable() const; protected: - QList<InstVersionPtr > m_vlist; + QList<BaseVersionPtr> m_vlist; - bool m_loaded; + bool m_loaded = false; protected slots: - virtual void updateListData(QList<InstVersionPtr > versions); + virtual void updateListData(QList<BaseVersionPtr> versions); }; class MCVListLoadTask : public Task @@ -72,9 +66,6 @@ protected slots: void list_downloaded(); protected: - //! Loads versions from Mojang's official version list. - bool loadFromVList(); - QNetworkReply *vlistReply; MinecraftVersionList *m_list; MinecraftVersion *m_currentStable; diff --git a/logic/net/ByteArrayDownload.cpp b/logic/net/ByteArrayDownload.cpp new file mode 100644 index 00000000..ba771eef --- /dev/null +++ b/logic/net/ByteArrayDownload.cpp @@ -0,0 +1,65 @@ +#include "ByteArrayDownload.h" +#include "MultiMC.h" +#include <logger/QsLog.h> + +ByteArrayDownload::ByteArrayDownload(QUrl url) : Download() +{ + m_url = url; + m_status = Job_NotStarted; +} + +void ByteArrayDownload::start() +{ + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void ByteArrayDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void ByteArrayDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + QLOG_ERROR() << "Error getting URL:" << m_url.toString().toLocal8Bit() + << "Network error: " << error; + m_status = Job_Failed; +} + +void ByteArrayDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + // nothing went wrong... + m_status = Job_Finished; + m_data = m_reply->readAll(); + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void ByteArrayDownload::downloadReadyRead() +{ + // ~_~ +} diff --git a/logic/net/ByteArrayDownload.h b/logic/net/ByteArrayDownload.h new file mode 100644 index 00000000..cfc6a8d0 --- /dev/null +++ b/logic/net/ByteArrayDownload.h @@ -0,0 +1,24 @@ +#pragma once +#include "Download.h" + +class ByteArrayDownload: public Download +{ + Q_OBJECT +public: + ByteArrayDownload(QUrl url); + +public: + /// if not saving to file, downloaded data is placed here + QByteArray m_data; + +public slots: + virtual void start(); + +protected slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadError(QNetworkReply::NetworkError error); + void downloadFinished(); + void downloadReadyRead(); +}; + +typedef std::shared_ptr<ByteArrayDownload> ByteArrayDownloadPtr; diff --git a/logic/net/CacheDownload.cpp b/logic/net/CacheDownload.cpp new file mode 100644 index 00000000..309eb345 --- /dev/null +++ b/logic/net/CacheDownload.cpp @@ -0,0 +1,136 @@ +#include "MultiMC.h" +#include "CacheDownload.h" +#include <pathutils.h> + +#include <QCryptographicHash> +#include <QFileInfo> +#include <QDateTime> +#include <logger/QsLog.h> + +CacheDownload::CacheDownload(QUrl url, MetaEntryPtr entry) + : Download(), md5sum(QCryptographicHash::Md5) +{ + m_url = url; + m_entry = entry; + m_target_path = entry->getFullPath(); + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void CacheDownload::start() +{ + if (!m_entry->stale) + { + emit succeeded(index_within_job); + return; + } + m_output_file.setFileName(m_target_path); + // if there already is a file and md5 checking is in effect and it can be opened + if (!ensureFilePathExists(m_target_path)) + { + emit failed(index_within_job); + return; + } + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + if(m_entry->remote_changed_timestamp.size()) + request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->remote_changed_timestamp.toLatin1()); + if(m_entry->etag.size()) + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Cached)"); + + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void CacheDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void CacheDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + QLOG_ERROR() << "Failed" << m_url.toString() << "with reason" << error; + m_status = Job_Failed; +} +void CacheDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + + // nothing went wrong... + m_status = Job_Finished; + if (m_opened_for_saving) + { + // save the data to the downloadable if we aren't saving to file + m_output_file.close(); + m_entry->md5sum = md5sum.result().toHex().constData(); + } + else + { + if (m_output_file.open(QIODevice::ReadOnly)) + { + m_entry->md5sum = + QCryptographicHash::hash(m_output_file.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + m_output_file.close(); + } + } + QFileInfo output_file_info(m_target_path); + + m_entry->etag = m_reply->rawHeader("ETag").constData(); + if(m_reply->hasRawHeader("Last-Modified")) + { + m_entry->remote_changed_timestamp = m_reply->rawHeader("Last-Modified").constData(); + } + m_entry->local_changed_timestamp = + output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); + m_entry->stale = false; + MMC->metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_output_file.close(); + m_output_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void CacheDownload::downloadReadyRead() +{ + if (!m_opened_for_saving) + { + if (!m_output_file.open(QIODevice::WriteOnly)) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + QByteArray ba = m_reply->readAll(); + md5sum.addData(ba); + m_output_file.write(ba); +} diff --git a/logic/net/CacheDownload.h b/logic/net/CacheDownload.h new file mode 100644 index 00000000..295391b1 --- /dev/null +++ b/logic/net/CacheDownload.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Download.h" +#include "HttpMetaCache.h" +#include <QFile> +#include <qcryptographichash.h> + +class CacheDownload : public Download +{ + Q_OBJECT +public: + MetaEntryPtr m_entry; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QFile m_output_file; + /// the hash-as-you-download + QCryptographicHash md5sum; +public: + explicit CacheDownload(QUrl url, MetaEntryPtr entry); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +}; + +typedef std::shared_ptr<CacheDownload> CacheDownloadPtr; diff --git a/logic/net/Download.h b/logic/net/Download.h new file mode 100644 index 00000000..ca4bee9f --- /dev/null +++ b/logic/net/Download.h @@ -0,0 +1,54 @@ +#pragma once + +#include <QObject> +#include <QUrl> +#include <memory> +#include <QNetworkReply> + +enum JobStatus +{ + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed +}; + +class Download : public QObject +{ + Q_OBJECT +protected: + explicit Download() : QObject(0) {}; + +public: + virtual ~Download() {}; + +public: + /// the network reply + std::shared_ptr<QNetworkReply> m_reply; + + /// source URL + QUrl m_url; + + /// The file's status + JobStatus m_status; + + /// index within the parent job + int index_within_job = 0; + +signals: + void started(int index); + void progress(int index, qint64 current, qint64 total); + void succeeded(int index); + void failed(int index); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; + virtual void downloadError(QNetworkReply::NetworkError error) = 0; + virtual void downloadFinished() = 0; + virtual void downloadReadyRead() = 0; + +public slots: + virtual void start() = 0; +}; + +typedef std::shared_ptr<Download> DownloadPtr; diff --git a/logic/net/DownloadJob.cpp b/logic/net/DownloadJob.cpp index cad9ae72..fa3e655e 100644 --- a/logic/net/DownloadJob.cpp +++ b/logic/net/DownloadJob.cpp @@ -1,181 +1,135 @@ #include "DownloadJob.h" #include "pathutils.h" #include "MultiMC.h" +#include "FileDownload.h" +#include "ByteArrayDownload.h" +#include "CacheDownload.h" -Download::Download (QUrl url, QString target_path, QString expected_md5 ) - :Job() -{ - m_url = url; - m_target_path = target_path; - m_expected_md5 = expected_md5; - - m_check_md5 = m_expected_md5.size(); - m_save_to_file = m_target_path.size(); - m_status = Job_NotStarted; - m_opened_for_saving = false; -} - -void Download::start() -{ - if ( m_save_to_file ) - { - QString filename = m_target_path; - m_output_file.setFileName ( filename ); - // if there already is a file and md5 checking is in effect and it can be opened - if ( m_output_file.exists() && m_output_file.open ( QIODevice::ReadOnly ) ) - { - // check the md5 against the expected one - QString hash = QCryptographicHash::hash ( m_output_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); - m_output_file.close(); - // skip this file if they match - if ( m_check_md5 && hash == m_expected_md5 ) - { - qDebug() << "Skipping " << m_url.toString() << ": md5 match."; - emit succeeded(index_within_job); - return; - } - else - { - m_expected_md5 = hash; - } - } - if(!ensureFilePathExists(filename)) - { - emit failed(index_within_job); - return; - } - } - qDebug() << "Downloading " << m_url.toString(); - QNetworkRequest request ( m_url ); - request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1()); - - auto worker = MMC->qnam(); - QNetworkReply * rep = worker->get ( request ); - - m_reply = QSharedPointer<QNetworkReply> ( rep, &QObject::deleteLater ); - connect ( rep, SIGNAL ( downloadProgress ( qint64,qint64 ) ), SLOT ( downloadProgress ( qint64,qint64 ) ) ); - connect ( rep, SIGNAL ( finished() ), SLOT ( downloadFinished() ) ); - connect ( rep, SIGNAL ( error ( QNetworkReply::NetworkError ) ), SLOT ( downloadError ( QNetworkReply::NetworkError ) ) ); - connect ( rep, SIGNAL ( readyRead() ), SLOT ( downloadReadyRead() ) ); -} - -void Download::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) -{ - emit progress (index_within_job, bytesReceived, bytesTotal ); -} +#include <logger/QsLog.h> -void Download::downloadError ( QNetworkReply::NetworkError error ) +ByteArrayDownloadPtr DownloadJob::addByteArrayDownload(QUrl url) { - // error happened during download. - // TODO: log the reason why - m_status = Job_Failed; + ByteArrayDownloadPtr ptr(new ByteArrayDownload(url)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -void Download::downloadFinished() +FileDownloadPtr DownloadJob::addFileDownload(QUrl url, QString rel_target_path) { - // if the download succeeded - if ( m_status != Job_Failed ) - { - // nothing went wrong... - m_status = Job_Finished; - // save the data to the downloadable if we aren't saving to file - if ( !m_save_to_file ) - { - m_data = m_reply->readAll(); - } - else - { - m_output_file.close(); - } - - //TODO: check md5 here! - m_reply.clear(); - emit succeeded(index_within_job); - return; - } - // else the download failed - else - { - if ( m_save_to_file ) - { - m_output_file.close(); - m_output_file.remove(); - } - m_reply.clear(); - emit failed(index_within_job); - return; - } + FileDownloadPtr ptr(new FileDownload(url, rel_target_path)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -void Download::downloadReadyRead() +CacheDownloadPtr DownloadJob::addCacheDownload(QUrl url, MetaEntryPtr entry) { - if( m_save_to_file ) - { - if(!m_opened_for_saving) - { - if ( !m_output_file.open ( QIODevice::WriteOnly ) ) - { - /* - * Can't open the file... the job failed - */ - m_reply->abort(); - emit failed(index_within_job); - return; - } - m_opened_for_saving = true; - } - m_output_file.write ( m_reply->readAll() ); - } + CacheDownloadPtr ptr(new CacheDownload(url, entry)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; + return ptr; } -DownloadPtr DownloadJob::add ( QUrl url, QString rel_target_path, QString expected_md5 ) +ForgeXzDownloadPtr DownloadJob::addForgeXzDownload(QUrl url, MetaEntryPtr entry) { - DownloadPtr ptr (new Download(url, rel_target_path, expected_md5)); + ForgeXzDownloadPtr ptr(new ForgeXzDownload(url, entry)); ptr->index_within_job = downloads.size(); downloads.append(ptr); + parts_progress.append(part_info()); + total_progress++; return ptr; } -void DownloadJob::partSucceeded ( int index ) +void DownloadJob::partSucceeded(int index) { + // do progress. all slots are 1 in size at least + auto &slot = parts_progress[index]; + partProgress(index, slot.total_progress, slot.total_progress); + num_succeeded++; - if(num_failed + num_succeeded == downloads.size()) + QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_succeeded << "/" + << downloads.size(); + if (num_failed + num_succeeded == downloads.size()) { - if(num_failed) + if (num_failed) { - qDebug() << "Download JOB failed: " << this; + QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; emit failed(); } else { - qDebug() << "Download JOB succeeded: " << this; + QLOG_INFO() << m_job_name.toLocal8Bit() << "succeeded."; emit succeeded(); } } } -void DownloadJob::partFailed ( int index ) +void DownloadJob::partFailed(int index) { - num_failed++; - if(num_failed + num_succeeded == downloads.size()) + auto &slot = parts_progress[index]; + if (slot.failures == 3) { - qDebug() << "Download JOB failed: " << this; - emit failed(); + QLOG_ERROR() << "Part" << index << "failed 3 times (" << downloads[index]->m_url << ")"; + num_failed++; + if (num_failed + num_succeeded == downloads.size()) + { + QLOG_ERROR() << m_job_name.toLocal8Bit() << "failed."; + emit failed(); + } + } + else + { + QLOG_ERROR() << "Part" << index << "failed, restarting (" << downloads[index]->m_url + << ")"; + // restart the job + slot.failures++; + downloads[index]->start(); } } -void DownloadJob::partProgress ( int index, qint64 bytesReceived, qint64 bytesTotal ) +void DownloadJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { - // PROGRESS? DENIED! -} + auto &slot = parts_progress[index]; + + current_progress -= slot.current_progress; + slot.current_progress = bytesReceived; + current_progress += slot.current_progress; + total_progress -= slot.total_progress; + slot.total_progress = bytesTotal; + total_progress += slot.total_progress; + emit progress(current_progress, total_progress); +} void DownloadJob::start() { - qDebug() << "Download JOB started: " << this; - for(auto iter: downloads) + QLOG_INFO() << m_job_name.toLocal8Bit() << " started."; + for (auto iter : downloads) { - connect(iter.data(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(iter.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(iter.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(iter.get(), SIGNAL(progress(int, qint64, qint64)), + SLOT(partProgress(int, qint64, qint64))); iter->start(); } } + +QStringList DownloadJob::getFailedFiles() +{ + QStringList failed; + for (auto download : downloads) + { + if (download->m_status == Job_Failed) + { + failed.push_back(download->m_url.toString()); + } + } + return failed; +} diff --git a/logic/net/DownloadJob.h b/logic/net/DownloadJob.h index adeef646..91b014ad 100644 --- a/logic/net/DownloadJob.h +++ b/logic/net/DownloadJob.h @@ -1,98 +1,31 @@ #pragma once #include <QtNetwork> +#include "Download.h" +#include "ByteArrayDownload.h" +#include "FileDownload.h" +#include "CacheDownload.h" +#include "HttpMetaCache.h" +#include "ForgeXzDownload.h" +#include "logic/tasks/ProgressProvider.h" class DownloadJob; -class Download; -typedef QSharedPointer<DownloadJob> DownloadJobPtr; -typedef QSharedPointer<Download> DownloadPtr; - -enum JobStatus -{ - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed -}; - -class Job : public QObject -{ - Q_OBJECT -protected: - explicit Job(): QObject(0){}; -public: - virtual ~Job() {}; - -public slots: - virtual void start() = 0; -}; - -class Download: public Job -{ - friend class DownloadJob; - Q_OBJECT -protected: - Download(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); -public: - /// the network reply - QSharedPointer<QNetworkReply> m_reply; - /// source URL - QUrl m_url; - - /// if true, check the md5sum against a provided md5sum - /// also, if a file exists, perform an md5sum first and don't download only if they don't match - bool m_check_md5; - /// the expected md5 checksum - QString m_expected_md5; - - /// save to file? - bool m_save_to_file; - /// is the saving file already open? - bool m_opened_for_saving; - /// if saving to file, use the one specified in this string - QString m_target_path; - /// this is the output file, if any - QFile m_output_file; - /// if not saving to file, downloaded data is placed here - QByteArray m_data; - - int currentProgress = 0; - int totalProgress = 0; - - /// The file's status - JobStatus m_status; - - int index_within_job = 0; -signals: - void started(int index); - void progress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); -public slots: - virtual void start(); - -private slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);; - void downloadError(QNetworkReply::NetworkError error); - void downloadFinished(); - void downloadReadyRead(); -}; +typedef std::shared_ptr<DownloadJob> DownloadJobPtr; /** * A single file for the downloader/cache to process. */ -class DownloadJob : public Job +class DownloadJob : public ProgressProvider { Q_OBJECT public: - explicit DownloadJob() - :Job(){}; - explicit DownloadJob(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()) - :Job() - { - add(url, rel_target_path, expected_md5); - }; + explicit DownloadJob(QString job_name) + :ProgressProvider(), m_job_name(job_name){}; + + ByteArrayDownloadPtr addByteArrayDownload(QUrl url); + FileDownloadPtr addFileDownload(QUrl url, QString rel_target_path); + CacheDownloadPtr addCacheDownload(QUrl url, MetaEntryPtr entry); + ForgeXzDownloadPtr addForgeXzDownload(QUrl url, MetaEntryPtr entry); - DownloadPtr add(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); DownloadPtr operator[](int index) { return downloads[index]; @@ -107,6 +40,20 @@ public: { return downloads.size(); } + virtual void getProgress(qint64& current, qint64& total) + { + current = current_progress; + total = total_progress; + }; + virtual QString getStatus() const + { + return m_job_name; + }; + virtual bool isRunning() const + { + return m_running; + }; + QStringList getFailedFiles(); signals: void started(); void progress(qint64 current, qint64 total); @@ -119,8 +66,19 @@ private slots: void partSucceeded(int index); void partFailed(int index); private: + struct part_info + { + qint64 current_progress = 0; + qint64 total_progress = 1; + int failures = 0; + }; + QString m_job_name; QList<DownloadPtr> downloads; + QList<part_info> parts_progress; + qint64 current_progress = 0; + qint64 total_progress = 0; int num_succeeded = 0; int num_failed = 0; + bool m_running = false; }; diff --git a/logic/net/FileDownload.cpp b/logic/net/FileDownload.cpp new file mode 100644 index 00000000..3f38b0fa --- /dev/null +++ b/logic/net/FileDownload.cpp @@ -0,0 +1,112 @@ +#include "MultiMC.h" +#include "FileDownload.h" +#include <pathutils.h> +#include <QCryptographicHash> +#include <logger/QsLog.h> + + +FileDownload::FileDownload ( QUrl url, QString target_path ) + :Download() +{ + m_url = url; + m_target_path = target_path; + m_check_md5 = false; + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void FileDownload::start() +{ + QString filename = m_target_path; + m_output_file.setFileName ( filename ); + // if there already is a file and md5 checking is in effect and it can be opened + if ( m_output_file.exists() && m_output_file.open ( QIODevice::ReadOnly ) ) + { + // check the md5 against the expected one + QString hash = QCryptographicHash::hash ( m_output_file.readAll(), QCryptographicHash::Md5 ).toHex().constData(); + m_output_file.close(); + // skip this file if they match + if ( m_check_md5 && hash == m_expected_md5 ) + { + QLOG_INFO() << "Skipping " << m_url.toString() << ": md5 match."; + emit succeeded(index_within_job); + return; + } + else + { + m_expected_md5 = hash; + } + } + if(!ensureFilePathExists(filename)) + { + emit failed(index_within_job); + return; + } + + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request ( m_url ); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Uncached)"); + + auto worker = MMC->qnam(); + QNetworkReply * rep = worker->get ( request ); + + m_reply = std::shared_ptr<QNetworkReply> ( rep ); + connect ( rep, SIGNAL ( downloadProgress ( qint64,qint64 ) ), SLOT ( downloadProgress ( qint64,qint64 ) ) ); + connect ( rep, SIGNAL ( finished() ), SLOT ( downloadFinished() ) ); + connect ( rep, SIGNAL ( error ( QNetworkReply::NetworkError ) ), SLOT ( downloadError ( QNetworkReply::NetworkError ) ) ); + connect ( rep, SIGNAL ( readyRead() ), SLOT ( downloadReadyRead() ) ); +} + +void FileDownload::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) +{ + emit progress (index_within_job, bytesReceived, bytesTotal ); +} + +void FileDownload::downloadError ( QNetworkReply::NetworkError error ) +{ + // error happened during download. + // TODO: log the reason why + m_status = Job_Failed; +} + +void FileDownload::downloadFinished() +{ + // if the download succeeded + if ( m_status != Job_Failed ) + { + // nothing went wrong... + m_status = Job_Finished; + m_output_file.close(); + + m_reply.reset(); + emit succeeded(index_within_job); + return; + } + // else the download failed + else + { + m_output_file.close(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void FileDownload::downloadReadyRead() +{ + if(!m_opened_for_saving) + { + if ( !m_output_file.open ( QIODevice::WriteOnly ) ) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + m_output_file.write ( m_reply->readAll() ); +} diff --git a/logic/net/FileDownload.h b/logic/net/FileDownload.h new file mode 100644 index 00000000..9abb590d --- /dev/null +++ b/logic/net/FileDownload.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Download.h" +#include <QFile> + +class FileDownload : public Download +{ + Q_OBJECT +public: + /// if true, check the md5sum against a provided md5sum + /// also, if a file exists, perform an md5sum first and don't download only if they don't match + bool m_check_md5; + /// the expected md5 checksum + QString m_expected_md5; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QFile m_output_file; + +public: + explicit FileDownload(QUrl url, QString target_path); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +}; + +typedef std::shared_ptr<FileDownload> FileDownloadPtr; diff --git a/logic/net/ForgeXzDownload.cpp b/logic/net/ForgeXzDownload.cpp new file mode 100644 index 00000000..0e5287d8 --- /dev/null +++ b/logic/net/ForgeXzDownload.cpp @@ -0,0 +1,279 @@ +#include "MultiMC.h" +#include "ForgeXzDownload.h" +#include <pathutils.h> + +#include <QCryptographicHash> +#include <QFileInfo> +#include <QDateTime> +#include <logger/QsLog.h> + +ForgeXzDownload::ForgeXzDownload(QUrl url, MetaEntryPtr entry) + : Download() +{ + QString urlstr = url.toString(); + urlstr.append(".pack.xz"); + m_url = QUrl(urlstr); + m_entry = entry; + m_target_path = entry->getFullPath(); + m_status = Job_NotStarted; + m_opened_for_saving = false; +} + +void ForgeXzDownload::start() +{ + if (!m_entry->stale) + { + emit succeeded(index_within_job); + return; + } + // can we actually create the real, final file? + if (!ensureFilePathExists(m_target_path)) + { + emit failed(index_within_job); + return; + } + QLOG_INFO() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader,"MultiMC/5.0 (Cached)"); + + auto worker = MMC->qnam(); + QNetworkReply *rep = worker->get(request); + + m_reply = std::shared_ptr<QNetworkReply>(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), + SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); +} + +void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit progress(index_within_job, bytesReceived, bytesTotal); +} + +void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error) +{ + // error happened during download. + // TODO: log the reason why + m_status = Job_Failed; +} + +void ForgeXzDownload::downloadFinished() +{ + // if the download succeeded + if (m_status != Job_Failed) + { + // nothing went wrong... + m_status = Job_Finished; + if (m_opened_for_saving) + { + // we actually downloaded something! process and isntall it + decompressAndInstall(); + return; + } + else + { + // something bad happened + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } + } + // else the download failed + else + { + m_pack200_xz_file.close(); + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(index_within_job); + return; + } +} + +void ForgeXzDownload::downloadReadyRead() +{ + + if (!m_opened_for_saving) + { + if (!m_pack200_xz_file.open()) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(index_within_job); + return; + } + m_opened_for_saving = true; + } + m_pack200_xz_file.write(m_reply->readAll()); +} + +#include "xz.h" +#include "unpack200.h" +#include <stdexcept> + +const size_t buffer_size = 8196; + +void ForgeXzDownload::decompressAndInstall() +{ + // rewind the downloaded temp file + m_pack200_xz_file.seek(0); + // de-xz'd file + QTemporaryFile pack200_file; + pack200_file.open(); + + bool xz_success = false; + // first, de-xz + { + uint8_t in[buffer_size]; + uint8_t out[buffer_size]; + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + xz_crc32_init(); + xz_crc64_init(); + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == nullptr) + { + xz_dec_end(s); + emit failed(index_within_job); + return; + } + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = buffer_size; + while (!xz_success) + { + if (b.in_pos == b.in_size) + { + b.in_size = m_pack200_xz_file.read((char*)in, sizeof(in)); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + if (pack200_file.write((char*)out, b.out_pos) != b.out_pos) + { + // msg = "Write error\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + + if (ret == XZ_UNSUPPORTED_CHECK) + { + // unsupported check. this is OK, but we should log this + continue; + } + + if (pack200_file.write((char*)out, b.out_pos) != b.out_pos ) + { + // write error + pack200_file.close(); + xz_dec_end(s); + return; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + xz_success = true; + break; + + case XZ_MEM_ERROR: + QLOG_ERROR() << "Memory allocation failed\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_MEMLIMIT_ERROR: + QLOG_ERROR() << "Memory usage limit reached\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_FORMAT_ERROR: + QLOG_ERROR() << "Not a .xz file\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_OPTIONS_ERROR: + QLOG_ERROR() << "Unsupported options in the .xz headers\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + QLOG_ERROR() << "File is corrupt\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + + default: + QLOG_ERROR() << "Bug!\n"; + xz_dec_end(s); + emit failed(index_within_job); + return; + } + } + } + + // revert pack200 + pack200_file.close(); + QString pack_name = pack200_file.fileName(); + try + { + unpack_200(pack_name.toStdString(), m_target_path.toStdString()); + } + catch(std::runtime_error & err) + { + QLOG_ERROR() << "Error unpacking " << pack_name.toUtf8() << " : " << err.what(); + QFile f(m_target_path); + if(f.exists()) + f.remove(); + emit failed(index_within_job); + return; + } + + QFile jar_file(m_target_path); + + if (!jar_file.open(QIODevice::ReadOnly)) + { + jar_file.remove(); + emit failed(index_within_job); + return; + } + m_entry->md5sum = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + jar_file.close(); + + QFileInfo output_file_info(m_target_path); + m_entry->etag = m_reply->rawHeader("ETag").constData(); + m_entry->local_changed_timestamp = + output_file_info.lastModified().toUTC().toMSecsSinceEpoch(); + m_entry->stale = false; + MMC->metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(index_within_job); +} diff --git a/logic/net/ForgeXzDownload.h b/logic/net/ForgeXzDownload.h new file mode 100644 index 00000000..5d677947 --- /dev/null +++ b/logic/net/ForgeXzDownload.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Download.h" +#include "HttpMetaCache.h" +#include <QFile> +#include <QTemporaryFile> + +class ForgeXzDownload : public Download +{ + Q_OBJECT +public: + MetaEntryPtr m_entry; + /// is the saving file already open? + bool m_opened_for_saving; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QTemporaryFile m_pack200_xz_file; + +public: + explicit ForgeXzDownload(QUrl url, MetaEntryPtr entry); + +protected slots: + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead(); + +public slots: + virtual void start(); +private: + void decompressAndInstall(); +}; + +typedef std::shared_ptr<ForgeXzDownload> ForgeXzDownloadPtr; diff --git a/logic/net/HttpMetaCache.cpp b/logic/net/HttpMetaCache.cpp index 87741dc9..5ba5b98d 100644 --- a/logic/net/HttpMetaCache.cpp +++ b/logic/net/HttpMetaCache.cpp @@ -1,38 +1,130 @@ +#include "MultiMC.h" #include "HttpMetaCache.h" #include <pathutils.h> + +#include <QFileInfo> #include <QFile> -#include <qjsondocument.h> -#include <qjsonarray.h> -#include <qjsonobject.h> -#include <qfileinfo.h> -#include <qtemporaryfile.h> -#include <qsavefile.h> +#include <QTemporaryFile> +#include <QSaveFile> +#include <QDateTime> +#include <QCryptographicHash> + +#include <logger/QsLog.h> + +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> + +QString MetaEntry::getFullPath() +{ + return PathCombine(MMC->metacache()->getBasePath(base), path); +} + HttpMetaCache::HttpMetaCache(QString path) + :QObject() { m_index_file = path; + saveBatchingTimer.setSingleShot(true); + saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer,SIGNAL(timeout()),SLOT(SaveNow())); } + HttpMetaCache::~HttpMetaCache() { - Save(); + saveBatchingTimer.stop(); + SaveNow(); } -void HttpMetaCache::addEntry ( QString base, QString resource_path, QString etag ) +MetaEntryPtr HttpMetaCache::getEntry ( QString base, QString resource_path ) { // no base. no base path. can't store if(!m_entries.contains(base)) - return; - QString real_path = PathCombine(m_entries[base].base_path, resource_path); + { + // TODO: log problem + return MetaEntryPtr(); + } + EntryMap & map = m_entries[base]; + if(map.entry_list.contains(resource_path)) + { + return map.entry_list[resource_path]; + } + return MetaEntryPtr(); +} + +MetaEntryPtr HttpMetaCache::resolveEntry ( QString base, QString resource_path, QString expected_etag ) +{ + auto entry = getEntry(base, resource_path); + // it's not present? generate a default stale entry + if(!entry) + { + return staleEntry(base, resource_path); + } + + auto & selected_base = m_entries[base]; + QString real_path = PathCombine(selected_base.base_path, resource_path); QFileInfo finfo(real_path); - // just ignore it, it's garbage if it's not a proper file + // is the file really there? if not -> stale if(!finfo.isFile() || !finfo.isReadable()) { - // TODO: log problem - return; + // if the file doesn't exist, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + + if(!expected_etag.isEmpty() && expected_etag != entry->etag) + { + // if the etag doesn't match expected, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); } - Save(); + // if the file changed, check md5sum + qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); + if(file_last_changed != entry->local_changed_timestamp) + { + QFile input(real_path); + input.open(QIODevice::ReadOnly); + QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData(); + if(entry->md5sum != md5sum) + { + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // md5sums matched... keep entry and save the new state to file + entry->local_changed_timestamp = file_last_changed; + SaveEventually(); + } + + // entry passed all the checks we cared about. + return entry; +} + +bool HttpMetaCache::updateEntry ( MetaEntryPtr stale_entry ) +{ + if(!m_entries.contains(stale_entry->base)) + { + QLOG_ERROR() << "Cannot add entry with unknown base: " << stale_entry->base.toLocal8Bit(); + return false; + } + if(stale_entry->stale) + { + QLOG_ERROR() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + return false; + } + m_entries[stale_entry->base].entry_list[stale_entry->path] = stale_entry; + SaveEventually(); + return true; +} + +MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) +{ + auto foo = new MetaEntry; + foo->base = base; + foo->path = resource_path; + foo->stale = true; + return MetaEntryPtr(foo); } void HttpMetaCache::addBase ( QString base, QString base_root ) @@ -46,6 +138,16 @@ void HttpMetaCache::addBase ( QString base, QString base_root ) m_entries[base] = foo; } +QString HttpMetaCache::getBasePath ( QString base ) +{ + if(m_entries.contains(base)) + { + return m_entries[base].base_path; + } + return QString(); +} + + void HttpMetaCache::Load() { QFile index(m_index_file); @@ -65,12 +167,12 @@ void HttpMetaCache::Load() // read the entry array auto entries_val =root.value("entries"); - if(!version_val.isArray()) + if(!entries_val.isArray()) return; - QJsonArray array = json.array(); + QJsonArray array = entries_val.toArray(); for(auto element: array) { - if(!element.isObject()); + if(!element.isObject()) return; auto element_obj = element.toObject(); QString base = element_obj.value("base").toString(); @@ -82,12 +184,22 @@ void HttpMetaCache::Load() QString path = foo->path = element_obj.value("path").toString(); foo->md5sum = element_obj.value("md5sum").toString(); foo->etag = element_obj.value("etag").toString(); - foo->last_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->remote_changed_timestamp = element_obj.value("remote_changed_timestamp").toString(); + // presumed innocent until closer examination + foo->stale = false; entrymap.entry_list[path] = MetaEntryPtr( foo ); } } -void HttpMetaCache::Save() +void HttpMetaCache::SaveEventually() +{ + // reset the save timer + saveBatchingTimer.stop(); + saveBatchingTimer.start(30000); +} + +void HttpMetaCache::SaveNow() { QSaveFile tfile(m_index_file); if(!tfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) @@ -104,7 +216,9 @@ void HttpMetaCache::Save() entryObj.insert("path", QJsonValue(entry->path)); entryObj.insert("md5sum", QJsonValue(entry->md5sum)); entryObj.insert("etag", QJsonValue(entry->etag)); - entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->last_changed_timestamp))); + entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp))); + if(!entry->remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp)); entriesArr.append(entryObj); } } @@ -118,14 +232,3 @@ void HttpMetaCache::Save() return; tfile.commit(); } - - -MetaEntryPtr HttpMetaCache::getEntryForResource ( QString base, QString resource_path ) -{ - if(!m_entries.contains(base)) - return MetaEntryPtr(); - auto & entrymap = m_entries[base]; - if(!entrymap.entry_list.contains(resource_path)) - return MetaEntryPtr(); - return entrymap.entry_list[resource_path]; -} diff --git a/logic/net/HttpMetaCache.h b/logic/net/HttpMetaCache.h index 161483ad..557d9298 100644 --- a/logic/net/HttpMetaCache.h +++ b/logic/net/HttpMetaCache.h @@ -2,6 +2,7 @@ #include <QString> #include <QSharedPointer> #include <QMap> +#include <qtimer.h> struct MetaEntry { @@ -9,23 +10,46 @@ struct MetaEntry QString path; QString md5sum; QString etag; - quint64 last_changed_timestamp = 0; + qint64 local_changed_timestamp = 0; + QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time + bool stale = true; + QString getFullPath(); }; -typedef QSharedPointer<MetaEntry> MetaEntryPtr; +typedef std::shared_ptr<MetaEntry> MetaEntryPtr; -class HttpMetaCache +class HttpMetaCache : public QObject { + Q_OBJECT public: // supply path to the cache index file HttpMetaCache(QString path); ~HttpMetaCache(); - MetaEntryPtr getEntryForResource(QString base, QString resource_path); - void addEntry(QString base, QString resource_path, QString etag); + + // get the entry solely from the cache + // you probably don't want this, unless you have some specific caching needs. + MetaEntryPtr getEntry(QString base, QString resource_path); + + // get the entry from cache and verify that it isn't stale (within reason) + MetaEntryPtr resolveEntry(QString base, QString resource_path, + QString expected_etag = QString()); + + // add a previously resolved stale entry + bool updateEntry(MetaEntryPtr stale_entry); + void addBase(QString base, QString base_root); -private: - void Save(); + + // (re)start a timer that calls SaveNow later. + void SaveEventually(); void Load(); + QString getBasePath(QString base); +public +slots: + void SaveNow(); + +private: + // create a new stale entry, given the parameters + MetaEntryPtr staleEntry(QString base, QString resource_path); struct EntryMap { QString base_path; @@ -33,4 +57,5 @@ private: }; QMap<QString, EntryMap> m_entries; QString m_index_file; + QTimer saveBatchingTimer; };
\ No newline at end of file diff --git a/logic/net/LoginTask.cpp b/logic/net/LoginTask.cpp new file mode 100644 index 00000000..4098783b --- /dev/null +++ b/logic/net/LoginTask.cpp @@ -0,0 +1,285 @@ +/* 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 "LoginTask.h" +#include "MultiMC.h" +#include <settingsobject.h> + +#include <QStringList> + +#include <QNetworkReply> +#include <QNetworkRequest> + +#include <QUrl> +#include <QUrlQuery> +#include <QJsonParseError> +#include <QJsonObject> + +LoginTask::LoginTask(const UserInfo &uInfo, QObject *parent) : Task(parent), uInfo(uInfo) +{ +} + +void LoginTask::executeTask() +{ + yggdrasilLogin(); +} + +void LoginTask::legacyLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processLegacyReply(QNetworkReply *))); + + QUrl loginURL("https://login.minecraft.net/"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, + "application/x-www-form-urlencoded"); + + QUrlQuery params; + params.addQueryItem("user", uInfo.username); + params.addQueryItem("password", uInfo.password); + params.addQueryItem("version", "13"); + + netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); +} + +void LoginTask::parseLegacyReply(QByteArray data) +{ + QString responseStr = QString::fromUtf8(data); + + QStringList strings = responseStr.split(":"); + if (strings.count() >= 4) + { + // strings[1] is the download ticket. It isn't used anymore. + QString username = strings[2]; + QString sessionID = strings[3]; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + result = {username, sessionID, username, QString()}; + emitSucceeded(); + } + else + { + if (responseStr.toLower() == "bad login") + emitFailed(tr("Invalid username or password.")); + else if (responseStr.toLower() == "old version") + emitFailed(tr("Launcher outdated, please update.")); + else + emitFailed(tr("Login failed: %1").arg(responseStr)); + } +} + +void LoginTask::processLegacyReply(QNetworkReply *reply) +{ + processReply(reply, &LoginTask::parseLegacyReply, &LoginTask::parseLegacyError); +} + +void LoginTask::processYggdrasilReply(QNetworkReply *reply) +{ + processReply(reply, &LoginTask::parseYggdrasilReply, &LoginTask::parseYggdrasilError); +} + +void LoginTask::processReply(QNetworkReply *reply, std::function<void (LoginTask*, QByteArray)> parser, std::function<QString (LoginTask*, QNetworkReply*)> errorHandler) +{ + if (netReply != reply) + return; + // Check for errors. + switch (reply->error()) + { + case QNetworkReply::NoError: + { + // Check the response code. + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + switch (responseCode) + { + case 200: + parser(this, reply->readAll()); + break; + + default: + emitFailed(tr("Login failed: Unknown HTTP code %1 encountered.").arg(responseCode)); + break; + } + + break; + } + + case QNetworkReply::OperationCanceledError: + emitFailed(tr("Login canceled.")); + break; + + default: + emitFailed(errorHandler(this, reply)); + break; + } +} + +QString LoginTask::parseLegacyError(QNetworkReply *reply) +{ + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + switch (responseCode) + { + case 403: + return tr("Invalid username or password."); + + case 503: + return tr("The login servers are currently unavailable. Check " + "http://help.mojang.com/ for more info."); + + default: + QLOG_DEBUG() << "Login failed with QNetworkReply code:" << reply->error(); + return tr("Login failed: %1").arg(reply->errorString()); + } +} + +QString LoginTask::parseYggdrasilError(QNetworkReply *reply) +{ + QByteArray data = reply->readAll(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + + // If there are JSON errors fall back to using the legacy error handling using HTTP status codes + if (jsonError.error != QJsonParseError::NoError) + { + return parseLegacyError(reply); + } + + if (!jsonDoc.isObject()) + { + return parseLegacyError(reply); + } + + QJsonObject root = jsonDoc.object(); + + //QString error = root.value("error").toString(); + QString errorMessage = root.value("errorMessage").toString(); + + if(errorMessage.isEmpty()) + { + return parseLegacyError(reply); + } + + return tr("Login failed: ") + errorMessage; +} + +void LoginTask::yggdrasilLogin() +{ + setStatus(tr("Logging in...")); + auto worker = MMC->qnam(); + connect(worker.get(), SIGNAL(finished(QNetworkReply *)), this, + SLOT(processYggdrasilReply(QNetworkReply *))); + + /* + { + // agent def. version might be incremented at some point + "agent":{"name":"Minecraft","version":1}, + "username": "mojang account name", + "password": "mojang account password", + // client token is optional. but we supply one anyway + "clientToken": "client identifier" + } + */ + + QUrl loginURL("https://authserver.mojang.com/authenticate"); + QNetworkRequest netRequest(loginURL); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + auto settings = MMC->settings(); + QString clientToken = settings->get("YggdrasilClientToken").toString(); + // escape the {} + clientToken.remove('{'); + clientToken.remove('}'); + // create the request + QString requestConstent; + requestConstent += "{"; + requestConstent += " \"agent\":{\"name\":\"Minecraft\",\"version\":1},\n"; + requestConstent += " \"username\":\"" + uInfo.username + "\",\n"; + requestConstent += " \"password\":\"" + uInfo.password + "\",\n"; + requestConstent += " \"clientToken\":\"" + clientToken + "\"\n"; + requestConstent += "}"; + netReply = worker->post(netRequest, requestConstent.toUtf8()); +} + +/* +{ + "accessToken": "random access token", // hexadecimal + "clientToken": "client identifier", // identical to the one received + "availableProfiles": [ // only present if the agent field was received + { + "id": "profile identifier", // hexadecimal + "name": "player name" + } + ], + "selectedProfile": { // only present if the agent field was received + "id": "profile identifier", + "name": "player name" + } +} +*/ +void LoginTask::parseYggdrasilReply(QByteArray data) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(tr("Login failed: %1").arg(jsonError.errorString())); + return; + } + + if (!jsonDoc.isObject()) + { + emitFailed(tr("Login failed: BAD FORMAT #1")); + return; + } + + QJsonObject root = jsonDoc.object(); + + QString accessToken = root.value("accessToken").toString(); + QString clientToken = root.value("clientToken").toString(); + QString playerID; + QString playerName; + auto selectedProfile = root.value("selectedProfile"); + if(selectedProfile.isObject()) + { + auto selectedProfileO = selectedProfile.toObject(); + playerID = selectedProfileO.value("id").toString(); + playerName = selectedProfileO.value("name").toString(); + } + QString sessionID = "token:" + accessToken + ":" + playerID; + /* + struct LoginResponse + { + QString username; + QString session_id; + QString player_name; + QString player_id; + QString client_id; + }; + */ + + result = {uInfo.username, sessionID, playerName, playerID, accessToken}; + emitSucceeded(); +} diff --git a/logic/tasks/LoginTask.h b/logic/net/LoginTask.h index bde672b8..aa925999 100644 --- a/logic/tasks/LoginTask.h +++ b/logic/net/LoginTask.h @@ -3,7 +3,7 @@ * 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 @@ -13,10 +13,9 @@ * limitations under the License. */ -#ifndef LOGINTASK_H -#define LOGINTASK_H +#pragma once -#include "Task.h" +#include "logic/tasks/Task.h" #include <QSharedPointer> struct UserInfo @@ -28,8 +27,10 @@ struct UserInfo struct LoginResponse { QString username; - QString sessionID; - qint64 latestVersion; + QString session_id; // session id is a combination of player id and the access token + QString player_name; + QString player_id; + QString access_token; }; class QNetworkReply; @@ -38,21 +39,29 @@ class LoginTask : public Task { Q_OBJECT public: - explicit LoginTask(const UserInfo& uInfo, QObject *parent = 0); + explicit LoginTask(const UserInfo &uInfo, QObject *parent = 0); LoginResponse getResult() { return result; - }; - + } + protected slots: - void processNetReply(QNetworkReply* reply); - + void legacyLogin(); + void processLegacyReply(QNetworkReply *reply); + void parseLegacyReply(QByteArray data); + QString parseLegacyError(QNetworkReply *reply); + + void yggdrasilLogin(); + void processYggdrasilReply(QNetworkReply *reply); + void parseYggdrasilReply(QByteArray data); + QString parseYggdrasilError(QNetworkReply *reply); + + void processReply(QNetworkReply *reply, std::function<void(LoginTask*, QByteArray)>, std::function<QString(LoginTask*, QNetworkReply*)>); + protected: void executeTask(); - + LoginResponse result; - QNetworkReply* netReply; + QNetworkReply *netReply; UserInfo uInfo; }; - -#endif // LOGINTASK_H diff --git a/logic/tasks/LoginTask.cpp b/logic/tasks/LoginTask.cpp deleted file mode 100644 index ad9de7f5..00000000 --- a/logic/tasks/LoginTask.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* 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 "LoginTask.h" -#include "MultiMC.h" - -#include <QStringList> - -#include <QNetworkReply> -#include <QNetworkRequest> - -#include <QUrl> -#include <QUrlQuery> - -LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), uInfo(uInfo){} - -void LoginTask::executeTask() -{ - setStatus("Logging in..."); - auto worker = MMC->qnam(); - connect(worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); - - QUrl loginURL("https://login.minecraft.net/"); - QNetworkRequest netRequest(loginURL); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QUrlQuery params; - params.addQueryItem("user", uInfo.username); - params.addQueryItem("password", uInfo.password); - params.addQueryItem("version", "13"); - - netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); -} - -void LoginTask::processNetReply(QNetworkReply *reply) -{ - if(netReply != reply) - return; - // Check for errors. - switch (reply->error()) - { - case QNetworkReply::NoError: - { - // Check the response code. - int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - QString responseStr(reply->readAll()); - - QStringList strings = responseStr.split(":"); - if (strings.count() >= 4) - { - bool parseSuccess; - qint64 latestVersion = strings[0].toLongLong(&parseSuccess); - if (parseSuccess) - { - // strings[1] is the download ticket. It isn't used anymore. - QString username = strings[2]; - QString sessionID = strings[3]; - - result = {username, sessionID, latestVersion}; - emitSucceeded(); - } - else - { - emitFailed("Failed to parse Minecraft version string."); - } - } - else - { - if (responseStr.toLower() == "bad login") - emitFailed("Invalid username or password."); - else if (responseStr.toLower() == "old version") - emitFailed("Launcher outdated, please update."); - else - emitFailed("Login failed: " + responseStr); - } - } - else if (responseCode == 503) - { - emitFailed("The login servers are currently unavailable. Check http://help.mojang.com/ for more info."); - } - else - { - emitFailed(QString("Login failed: Unknown HTTP error %1 occurred.").arg(QString::number(responseCode))); - } - break; - } - - case QNetworkReply::OperationCanceledError: - emitFailed("Login canceled."); - break; - - default: - emitFailed("Login failed: " + reply->errorString()); - break; - } -} diff --git a/logic/tasks/ProgressProvider.h b/logic/tasks/ProgressProvider.h new file mode 100644 index 00000000..e158eb54 --- /dev/null +++ b/logic/tasks/ProgressProvider.h @@ -0,0 +1,20 @@ +#pragma once +#include <QObject> +class ProgressProvider : public QObject +{ + Q_OBJECT +protected: + explicit ProgressProvider(QObject* parent = 0): QObject(parent){} +signals: + void started(); + void progress(qint64 current, qint64 total); + void succeeded(); + void failed(QString reason); + void status(QString status); +public: + virtual QString getStatus() const = 0; + virtual void getProgress(qint64 ¤t, qint64 &total) = 0; + virtual bool isRunning() const = 0; +public slots: + virtual void start() = 0; +}; diff --git a/logic/tasks/Task.cpp b/logic/tasks/Task.cpp index 7c148591..2f137c55 100644 --- a/logic/tasks/Task.cpp +++ b/logic/tasks/Task.cpp @@ -14,72 +14,60 @@ */ #include "Task.h" +#include <logger/QsLog.h> Task::Task(QObject *parent) : - QObject(parent) + ProgressProvider(parent) { } QString Task::getStatus() const { - return status; + return m_status; } -void Task::setStatus(const QString &status) +void Task::setStatus(const QString &new_status) { - this->status = status; - emitStatusChange(status); + m_status = new_status; + emit status(new_status); } -int Task::getProgress() const +void Task::setProgress(int new_progress) { - return progress; + m_progress = new_progress; + emit progress(new_progress, 100); } -void Task::setProgress(int progress) +void Task::getProgress(qint64& current, qint64& total) { - this->progress = progress; - emitProgressChange(progress); + current = m_progress; + total = 100; } -void Task::startTask() -{ - emitStarted(); - executeTask(); -} -void Task::emitStarted() +void Task::start() { - running = true; + m_running = true; emit started(); + executeTask(); } void Task::emitFailed(QString reason) { - running = false; + m_running = false; + QLOG_ERROR() << "Task failed: " << reason; emit failed(reason); } void Task::emitSucceeded() { - running = false; + m_running = false; emit succeeded(); } bool Task::isRunning() const { - return running; -} - - -void Task::emitStatusChange(const QString &status) -{ - emit statusChanged(status); -} - -void Task::emitProgressChange(int progress) -{ - emit progressChanged(progress); + return m_running; } diff --git a/logic/tasks/Task.h b/logic/tasks/Task.h index 91852b0f..cfe71c51 100644 --- a/logic/tasks/Task.h +++ b/logic/tasks/Task.h @@ -13,53 +13,37 @@ * limitations under the License. */ -#ifndef TASK_H -#define TASK_H +#pragma once #include <QObject> #include <QString> +#include "ProgressProvider.h" -class Task : public QObject +class Task : public ProgressProvider { Q_OBJECT public: explicit Task(QObject *parent = 0); - QString getStatus() const; - int getProgress() const; - bool isRunning() const; + virtual QString getStatus() const; + virtual void getProgress(qint64& current, qint64& total); + virtual bool isRunning() const; public slots: - void startTask(); - -protected slots: - void setStatus(const QString& status); - void setProgress(int progress); - -signals: - void started(); - void failed(QString reason); - void succeeded(); - - void statusChanged(Task* task, const QString& status); - void progressChanged(Task* task, int progress); - - void statusChanged(const QString& status); - void progressChanged(int progress); + virtual void start(); protected: virtual void executeTask() = 0; - virtual void emitStarted(); - virtual void emitFailed(QString reason); virtual void emitSucceeded(); + virtual void emitFailed(QString reason); + +protected slots: + void setStatus(const QString& status); + void setProgress(int progress); - virtual void emitStatusChange(const QString &status); - virtual void emitProgressChange(int progress); - - QString status; - int progress; - bool running = false; +protected: + QString m_status; + int m_progress = 0; + bool m_running = false; }; - -#endif // TASK_H |