From afaa1dc223ec87b685778ee0aed81cb6caaa05c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 7 Aug 2013 01:38:18 +0200 Subject: Get rid of QNAM (now subclassed and less needy). Basic LWJGL download and extraction. --- backend/BaseInstance.h | 3 +- backend/BaseUpdate.cpp | 18 ++++ backend/BaseUpdate.h | 62 +++++++++++ backend/CMakeLists.txt | 14 ++- backend/LegacyInstance.cpp | 10 +- backend/LegacyInstance.h | 4 +- backend/LegacyUpdate.cpp | 185 +++++++++++++++++++++++++++++++++ backend/LegacyUpdate.h | 52 +++++++++ backend/MinecraftVersion.cpp | 3 + backend/MinecraftVersion.h | 1 + backend/OneSixAssets.cpp | 4 +- backend/OneSixAssets.h | 3 +- backend/OneSixInstance.cpp | 2 +- backend/OneSixInstance.h | 3 +- backend/OneSixUpdate.cpp | 20 +--- backend/OneSixUpdate.h | 35 +------ backend/OneSixVersion.h | 17 +-- backend/VersionFactory.cpp | 32 +++--- backend/lists/LwjglVersionList.cpp | 13 ++- backend/lists/LwjglVersionList.h | 21 +--- backend/lists/MinecraftVersionList.cpp | 16 +-- backend/lists/MinecraftVersionList.h | 2 - backend/net/DownloadJob.cpp | 138 ++++++++++++++++++++++++ backend/net/DownloadJob.h | 51 +++++++++ backend/net/JobQueue.h | 180 ++++++++++++++++++++++++++++++++ backend/net/NetWorker.cpp | 12 +++ backend/net/NetWorker.h | 20 ++++ backend/tasks/LoginTask.cpp | 20 ++-- backend/tasks/LoginTask.h | 3 - 29 files changed, 809 insertions(+), 135 deletions(-) create mode 100644 backend/BaseUpdate.cpp create mode 100644 backend/BaseUpdate.h create mode 100644 backend/LegacyUpdate.cpp create mode 100644 backend/LegacyUpdate.h create mode 100644 backend/net/DownloadJob.cpp create mode 100644 backend/net/DownloadJob.h create mode 100644 backend/net/JobQueue.h create mode 100644 backend/net/NetWorker.cpp create mode 100644 backend/net/NetWorker.h (limited to 'backend') diff --git a/backend/BaseInstance.h b/backend/BaseInstance.h index 7454c17e..3a344cea 100644 --- a/backend/BaseInstance.h +++ b/backend/BaseInstance.h @@ -25,6 +25,7 @@ #include "libmmc_config.h" +class BaseUpdate; class MinecraftProcess; class OneSixUpdate; class InstanceList; @@ -117,7 +118,7 @@ public: virtual SettingsObject &settings() const; /// returns a valid update task if update is needed, NULL otherwise - virtual OneSixUpdate* doUpdate() = 0; + virtual BaseUpdate* doUpdate() = 0; /// returns a valid minecraft process, ready for launch virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0; diff --git a/backend/BaseUpdate.cpp b/backend/BaseUpdate.cpp new file mode 100644 index 00000000..8a4aced3 --- /dev/null +++ b/backend/BaseUpdate.cpp @@ -0,0 +1,18 @@ +#include "BaseUpdate.h" + +BaseUpdate::BaseUpdate ( BaseInstance* inst, QObject* parent ) : Task ( parent ) +{ + m_inst = inst; +} + +void BaseUpdate::error ( const QString& msg ) +{ + emit gameUpdateError(msg); +} + +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 +} \ No newline at end of file diff --git a/backend/BaseUpdate.h b/backend/BaseUpdate.h new file mode 100644 index 00000000..83eb0089 --- /dev/null +++ b/backend/BaseUpdate.h @@ -0,0 +1,62 @@ +/* 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 +#include +#include + +#include "net/DownloadJob.h" + +#include "tasks/Task.h" +#include "libmmc_config.h" + +class MinecraftVersion; +class BaseInstance; + +/*! + * The game update task is the task that handles downloading instances' files. + */ +class LIBMULTIMC_EXPORT BaseUpdate : public Task +{ + Q_OBJECT +public: + explicit BaseUpdate(BaseInstance *inst, QObject *parent = 0); + + virtual void executeTask() = 0; + +signals: + /*! + * \brief Signal emitted when the game update is complete. + */ + void gameUpdateComplete(); + + /*! + * \brief Signal emitted if an error occurrs during the update. + * \param errorMsg An error message to be displayed to the user. + */ + void gameUpdateError(const QString &errorMsg); + +protected slots: + virtual void error(const QString &msg); + void updateDownloadProgress(qint64 current, qint64 total); + +protected: + JobListQueue download_queue; + BaseInstance *m_inst; +}; + + diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt index 4fbe7bed..ea19fbbf 100644 --- a/backend/CMakeLists.txt +++ b/backend/CMakeLists.txt @@ -24,13 +24,20 @@ libmmc_config.h InstanceVersion.h MinecraftVersion.h InstanceFactory.h +BaseUpdate.h BaseInstance.h BaseInstance_p.h MinecraftProcess.h +# network stuffs +net/DownloadJob.h +net/JobQueue.h +net/NetWorker.h + # legacy instances LegacyInstance.h LegacyInstance_p.h +LegacyUpdate.h # 1.6 instances OneSixAssets.h @@ -56,11 +63,17 @@ SET(LIBINST_SOURCES InstanceVersion.cpp MinecraftVersion.cpp InstanceFactory.cpp +BaseUpdate.cpp BaseInstance.cpp MinecraftProcess.cpp +# network stuffs +net/NetWorker.cpp +net/DownloadJob.cpp + # legacy instances LegacyInstance.cpp +LegacyUpdate.cpp # 1.6 instances OneSixAssets.cpp @@ -93,4 +106,3 @@ add_library(backend SHARED ${LIBINST_SOURCES} ${LIBINST_HEADERS}) qt5_use_modules(backend Core Network Xml) target_link_libraries(backend libUtil libSettings quazip) - diff --git a/backend/LegacyInstance.cpp b/backend/LegacyInstance.cpp index dc6cf0db..bf5c674b 100644 --- a/backend/LegacyInstance.cpp +++ b/backend/LegacyInstance.cpp @@ -1,6 +1,7 @@ #include "LegacyInstance.h" #include "LegacyInstance_p.h" #include "MinecraftProcess.h" +#include "LegacyUpdate.h" #include #include #include @@ -16,7 +17,7 @@ LegacyInstance::LegacyInstance(const QString& rootDir, SettingsObject* settings, settings->registerSetting(new Setting("NeedsRebuild", true)); settings->registerSetting(new Setting("ShouldUpdate", false)); settings->registerSetting(new Setting("JarVersion", "Unknown")); - settings->registerSetting(new Setting("LwjglVersion", "Mojang")); + settings->registerSetting(new Setting("LwjglVersion", "2.9.0")); settings->registerSetting(new Setting("IntendedJarVersion", "")); } @@ -31,10 +32,9 @@ QString LegacyInstance::minecraftDir() const return mcDir.filePath(); } -OneSixUpdate* LegacyInstance::doUpdate() +BaseUpdate* LegacyInstance::doUpdate() { - // legacy instances no longer update - return nullptr; + return new LegacyUpdate(this, this); } MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session) @@ -224,6 +224,6 @@ bool LegacyInstance::setIntendedVersionId ( QString version ) } bool LegacyInstance::shouldUpdate() const { - return false; + return true; } void LegacyInstance::setShouldUpdate ( bool val ) {} diff --git a/backend/LegacyInstance.h b/backend/LegacyInstance.h index e4ab6f29..e7cf347c 100644 --- a/backend/LegacyInstance.h +++ b/backend/LegacyInstance.h @@ -2,6 +2,8 @@ #include "BaseInstance.h" +class BaseUpdate; + class LIBMULTIMC_EXPORT LegacyInstance : public BaseInstance { Q_OBJECT @@ -77,7 +79,7 @@ public: virtual bool shouldUpdate() const; virtual void setShouldUpdate(bool val); - virtual OneSixUpdate* doUpdate(); + virtual BaseUpdate* doUpdate(); virtual MinecraftProcess* prepareForLaunch( QString user, QString session ); virtual void cleanupAfterRun(); diff --git a/backend/LegacyUpdate.cpp b/backend/LegacyUpdate.cpp new file mode 100644 index 00000000..533be468 --- /dev/null +++ b/backend/LegacyUpdate.cpp @@ -0,0 +1,185 @@ +#include "LegacyUpdate.h" +#include "lists/LwjglVersionList.h" +#include "BaseInstance.h" +#include "LegacyInstance.h" +#include "net/NetWorker.h" +#include +#include +#include + + +LegacyUpdate::LegacyUpdate ( BaseInstance* inst, QObject* parent ) : BaseUpdate ( inst, parent ) {} + +void LegacyUpdate::executeTask() +{ + lwjglStart(); +} + +void LegacyUpdate::lwjglStart() +{ + LegacyInstance * inst = (LegacyInstance *) m_inst; + auto &list = LWJGLVersionList::get(); + if(!list.isLoaded()) + { + error("Too soon! Let the LWJGL list load :)"); + emitEnded(); + return; + } + QString lwjglVer = inst->lwjglVersion(); + auto version = list.getVersion(lwjglVer); + if(!version) + { + error("Game update failed: the selected LWJGL version is invalid."); + emitEnded(); + } + lwjglVersion = version->name(); + QString url = version->url(); + + auto &worker = NetWorker::spawn(); + QNetworkRequest req(url); + req.setRawHeader("Host", "sourceforge.net"); + req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); + QNetworkReply * rep = worker.get ( req ); + + m_reply = QSharedPointer (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))); +} + +void LegacyUpdate::lwjglFinished(QNetworkReply* reply) +{ + if(m_reply != reply) + { + return; + } + if(reply->error() != QNetworkReply::NoError) + { + error("Failed to download: " + reply->errorString() + "\nSometimes you have to wait a bit if you download many LWJGL versions in a row. YMMV"); + emitEnded(); + return; + } + auto &worker = NetWorker::spawn(); + //Here i check if there is a cookie for me in the reply and extract it + QList cookies = qvariant_cast>(reply->header(QNetworkRequest::SetCookieHeader)); + if(cookies.count() != 0) + { + //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 + QVariant newLoc = reply->header(QNetworkRequest::LocationHeader); + if(newLoc.isValid()) + { + auto &worker = NetWorker::spawn(); + QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString(); + QNetworkRequest req(redirectedTo); + req.setRawHeader("Host", "sourceforge.net"); + 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 (rep, &QObject::deleteLater); + return; + } + QFile saveMe("lwjgl.zip"); + saveMe.open(QIODevice::WriteOnly); + saveMe.write(m_reply->readAll()); + saveMe.close(); + setStatus("Installing new LWJGL..."); + extractLwjgl(); +} +void LegacyUpdate::extractLwjgl() +{ + // make sure the directories are there + QString lwjgl_base = PathCombine("lwjgl", lwjglVersion ); + QString nativesPath = PathCombine( lwjgl_base, "natives/"); + bool success = ensurePathExists(nativesPath); + + if(!success) + { + error("Failed to extract the lwjgl libs - error when creating required folders."); + emitEnded(); + return; + } + + QuaZip zip("lwjgl.zip"); + if(!zip.open(QuaZip::mdUnzip)) + { + error("Failed to extract the lwjgl libs - not a valid archive."); + emitEnded(); + 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()) + { + if(!file.open(QIODevice::ReadOnly)) + { + zip.close(); + error("Failed to extract the lwjgl libs - error while reading archive."); + emitEnded(); + return; + } + QuaZipFileInfo info; + QString name = file.getActualFileName(); + if(name.endsWith('/')) + { + file.close(); + continue; + } + QString destFileName; + // Look for the jars + for (int i = 0; i < 3; i++) + { + if (name.endsWith(jarNames[i])) + { + destFileName = PathCombine(lwjgl_base, jarNames[i]); + } + } + // Not found? look for the natives + if(destFileName.isEmpty()) + { +#ifdef Q_OS_WIN32 + QString nativesDir = "windows"; +#elif Q_OS_MAC + QString nativesDir = "macosx"; +#else + QString nativesDir = "linux"; +#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); + destFileName = PathCombine(nativesPath, name); + } + } + // Now if destFileName is still empty, go to the next file. + if (!destFileName.isEmpty()) + { + setStatus("Installing new LWJGL - Extracting " + name); + QFile output(destFileName); + output.open(QIODevice::WriteOnly); + output.write(file.readAll()); // FIXME: wste of memory!? + output.close(); + } + file.close(); // do not forget to close! + } + zip.close(); + m_reply.clear(); + emit gameUpdateComplete(); + emitEnded(); +} + +void LegacyUpdate::lwjglFailed() +{ + error("Bad stuff happened while trying to get the lwjgl libs..."); + emitEnded(); +} + diff --git a/backend/LegacyUpdate.h b/backend/LegacyUpdate.h new file mode 100644 index 00000000..de5608d0 --- /dev/null +++ b/backend/LegacyUpdate.h @@ -0,0 +1,52 @@ +/* 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 +#include +#include + +#include "net/DownloadJob.h" +#include "tasks/Task.h" +#include "libmmc_config.h" +#include "BaseUpdate.h" + +class MinecraftVersion; +class BaseInstance; + +class LIBMULTIMC_EXPORT LegacyUpdate : public BaseUpdate +{ + Q_OBJECT +public: + explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0); + virtual void executeTask(); + +private slots: + void lwjglStart(); + void lwjglFinished( QNetworkReply* ); + void lwjglFailed(); + void extractLwjgl(); +private: + + QSharedPointer m_reply; + + // target version, determined during this task + // MinecraftVersion *targetVersion; + QString lwjglURL; + QString lwjglVersion; +}; + + diff --git a/backend/MinecraftVersion.cpp b/backend/MinecraftVersion.cpp index d6e54404..fdaf0712 100644 --- a/backend/MinecraftVersion.cpp +++ b/backend/MinecraftVersion.cpp @@ -51,6 +51,9 @@ QString MinecraftVersion::typeName() const case Snapshot: return "Snapshot"; + case Nostalgia: + return "Nostalgia"; + default: return QString("Unknown Type %1").arg(versionType()); } diff --git a/backend/MinecraftVersion.h b/backend/MinecraftVersion.h index 98b10f2d..f5cf9805 100644 --- a/backend/MinecraftVersion.h +++ b/backend/MinecraftVersion.h @@ -39,6 +39,7 @@ public: Stable, CurrentStable, Snapshot, + Nostalgia }; virtual QString descriptor() const; diff --git a/backend/OneSixAssets.cpp b/backend/OneSixAssets.cpp index 409172b2..db9e7421 100644 --- a/backend/OneSixAssets.cpp +++ b/backend/OneSixAssets.cpp @@ -2,7 +2,7 @@ #include #include #include "OneSixAssets.h" -#include "dlqueue.h" +#include "net/DownloadJob.h" inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) { @@ -138,7 +138,7 @@ void OneSixAssets::fetchFinished() QString trimmedEtag = etagStr.remove ( '"' ); nuke_whitelist.append ( keyStr ); if(trimmedEtag != client_etag) - job->add ( DownloadJob::create ( net_manager, QUrl ( prefix + keyStr ), filename ) ); + job->add ( DownloadJob::create ( QUrl ( prefix + keyStr ), filename ) ); } job->add ( JobPtr ( new NukeAndPaveJob ( fprefix, nuke_whitelist ) ) ); diff --git a/backend/OneSixAssets.h b/backend/OneSixAssets.h index 233f925a..8c345daa 100644 --- a/backend/OneSixAssets.h +++ b/backend/OneSixAssets.h @@ -1,5 +1,5 @@ #pragma once -#include "dlqueue.h" +#include "net/DownloadJob.h" class Private; @@ -16,7 +16,6 @@ public slots: public: void start(); private: - QSharedPointer net_manager {new QNetworkAccessManager()}; JobListQueue dl; JobListPtr index_job; JobListPtr files_job; diff --git a/backend/OneSixInstance.cpp b/backend/OneSixInstance.cpp index 6df2b471..bf8fd84e 100644 --- a/backend/OneSixInstance.cpp +++ b/backend/OneSixInstance.cpp @@ -18,7 +18,7 @@ OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting reloadFullVersion(); } -OneSixUpdate* OneSixInstance::doUpdate() +BaseUpdate* OneSixInstance::doUpdate() { return new OneSixUpdate(this); } diff --git a/backend/OneSixInstance.h b/backend/OneSixInstance.h index 2e08554d..12ad9e40 100644 --- a/backend/OneSixInstance.h +++ b/backend/OneSixInstance.h @@ -3,13 +3,14 @@ #include "BaseInstance.h" #include class FullVersion; +class BaseUpdate; class LIBMULTIMC_EXPORT OneSixInstance : public BaseInstance { Q_OBJECT public: explicit OneSixInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0); - virtual OneSixUpdate* doUpdate(); + virtual BaseUpdate* doUpdate(); virtual MinecraftProcess* prepareForLaunch ( QString user, QString session ); virtual void cleanupAfterRun(); diff --git a/backend/OneSixUpdate.cpp b/backend/OneSixUpdate.cpp index db3b9864..84d8dfae 100644 --- a/backend/OneSixUpdate.cpp +++ b/backend/OneSixUpdate.cpp @@ -33,11 +33,7 @@ #include "pathutils.h" -OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent) : - Task(parent) -{ - m_inst = inst; -} +OneSixUpdate::OneSixUpdate(BaseInstance *inst, QObject *parent):BaseUpdate(inst, parent){} void OneSixUpdate::executeTask() { @@ -142,7 +138,7 @@ void OneSixUpdate::jarlibStart() { QString download_path = lib->downloadPath(); QString storage_path = "libraries/" + lib->storagePath(); - jarlibDownloadJob->add(DownloadJob::create(net_manager, download_path, storage_path)); + jarlibDownloadJob->add(DownloadJob::create(download_path, storage_path)); } connect(jarlibDownloadJob.data(), SIGNAL(finished()), SLOT(jarlibFinished())); connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed())); @@ -163,15 +159,3 @@ void OneSixUpdate::jarlibFailed() emitEnded(); } -void OneSixUpdate::error(const QString &msg) -{ - emit gameUpdateError(msg); -} - -void OneSixUpdate::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 -} - diff --git a/backend/OneSixUpdate.h b/backend/OneSixUpdate.h index 3bab4eca..31aed8ba 100644 --- a/backend/OneSixUpdate.h +++ b/backend/OneSixUpdate.h @@ -16,36 +16,25 @@ #pragma once #include - #include - -#include #include -#include "dlqueue.h" +#include "net/DownloadJob.h" #include "tasks/Task.h" #include "libmmc_config.h" +#include "BaseUpdate.h" class MinecraftVersion; class BaseInstance; -/*! - * The game update task is the task that handles downloading instances' files. - */ -class LIBMULTIMC_EXPORT OneSixUpdate : public Task +class LIBMULTIMC_EXPORT OneSixUpdate : public BaseUpdate { Q_OBJECT public: explicit OneSixUpdate(BaseInstance *inst, QObject *parent = 0); - virtual void executeTask(); -public slots: - virtual void error(const QString &msg); - private slots: - void updateDownloadProgress(qint64 current, qint64 total); - void versionFileStart(); void versionFileFinished(); void versionFileFailed(); @@ -54,25 +43,7 @@ private slots: void jarlibFinished(); void jarlibFailed(); - -signals: - /*! - * \brief Signal emitted when the game update is complete. - */ - void gameUpdateComplete(); - - /*! - * \brief Signal emitted if an error occurrs during the update. - * \param errorMsg An error message to be displayed to the user. - */ - void gameUpdateError(const QString &errorMsg); - private: - BaseInstance *m_inst; - - QString m_subStatusMsg; - - QSharedPointer net_manager {new QNetworkAccessManager()}; JobListPtr legacyDownloadJob; JobListPtr specificVersionDownloadJob; JobListPtr jarlibDownloadJob; diff --git a/backend/OneSixVersion.h b/backend/OneSixVersion.h index 160d0426..1f8090ab 100644 --- a/backend/OneSixVersion.h +++ b/backend/OneSixVersion.h @@ -13,23 +13,14 @@ enum OpSys OpSys OpSys_fromString(QString); -#ifdef Q_OS_MAC - #define currentSystem Os_OSX -#endif - -#ifdef Q_OS_LINUX - #define currentSystem Os_Linux -#endif - #ifdef Q_OS_WIN32 #define currentSystem Os_Windows +#elif Q_OS_MAC + #define currentSystem Os_OSX +#else + #define currentSystem Os_Linux #endif -#ifndef currentSystem - #define currentSystem Os_Other -#endif - - enum RuleAction { Allow, diff --git a/backend/VersionFactory.cpp b/backend/VersionFactory.cpp index 7bec4370..a431ca26 100644 --- a/backend/VersionFactory.cpp +++ b/backend/VersionFactory.cpp @@ -80,6 +80,16 @@ QSharedPointer FullVersionFactory::parse4(QJsonObject root, QShared fullVersion->minecraftArguments = minecraftArgsValue.toString(); } + auto minecraftTypeValue = root.value("type"); + if(minecraftTypeValue.isString()) + { + QString copy = fullVersion->type = minecraftTypeValue.toString(); + if(copy == "old_aplha" || copy == "old_beta") + { + fullVersion->isLegacy = true; + } + } + fullVersion->releaseTime = root.value("releaseTime").toString(); fullVersion->time = root.value("time").toString(); @@ -107,7 +117,7 @@ QSharedPointer FullVersionFactory::parse4(QJsonObject root, QShared auto urlVal = libObj.value("url"); if(urlVal.isString()) { - library->setBaseUrl(urlVal.toString()); + library->setBaseUrl(nameVal.toString()); } // Extract excludes (if any) @@ -175,19 +185,15 @@ QSharedPointer FullVersionFactory::parse(QByteArray data) } QJsonObject root = jsonDoc.object(); - readVersion->minimumLauncherVersion = root.value("minimumLauncherVersion").toDouble(); - switch(readVersion->minimumLauncherVersion) + 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 { - case 1: - case 2: - case 3: - case 4: - return parse4(root, readVersion); - // ADD MORE HERE :D - default: - error_string = "Version file was for an unrecognized launcher version. RIP"; - m_error = FullVersionFactory::UnsupportedVersion; - return QSharedPointer(); + error_string = "Version file was for an unrecognized launcher version. RIP"; + m_error = FullVersionFactory::UnsupportedVersion; + return QSharedPointer(); } } diff --git a/backend/lists/LwjglVersionList.cpp b/backend/lists/LwjglVersionList.cpp index 824d0906..73a27a58 100644 --- a/backend/lists/LwjglVersionList.cpp +++ b/backend/lists/LwjglVersionList.cpp @@ -14,6 +14,7 @@ */ #include "LwjglVersionList.h" +#include #include @@ -53,7 +54,7 @@ QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const return version->name(); case Qt::ToolTipRole: - return version->url().toString(); + return version->url(); default: return QVariant(); @@ -90,7 +91,8 @@ void LWJGLVersionList::loadList() Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); setLoading(true); - reply = netMgr.get(QNetworkRequest(QUrl(RSS_URL))); + auto & worker = NetWorker::spawn(); + reply = worker.get(QNetworkRequest(QUrl(RSS_URL))); connect(reply, SIGNAL(finished()), SLOT(netRequestComplete())); } @@ -144,9 +146,9 @@ void LWJGLVersionList::netRequestComplete() // Make sure it's a download link. if (link.endsWith("/download") && link.contains(lwjglRegex)) { - QString name = link.mid(lwjglRegex.indexIn(link)); + QString name = link.mid(lwjglRegex.indexIn(link) + 6); // Subtract 4 here to remove the .zip file extension. - name = name.left(lwjglRegex.matchedLength() - 4); + name = name.left(lwjglRegex.matchedLength() - 10); QUrl url(link); if (!url.isValid()) @@ -179,7 +181,8 @@ const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName) { for (int i = 0; i < count(); i++) { - if (at(i)->name() == versionName) + QString name = at(i)->name(); + if ( name == versionName) return at(i); } return PtrLWJGLVersion(); diff --git a/backend/lists/LwjglVersionList.h b/backend/lists/LwjglVersionList.h index f3e7799a..2360f181 100644 --- a/backend/lists/LwjglVersionList.h +++ b/backend/lists/LwjglVersionList.h @@ -20,7 +20,6 @@ #include #include -#include #include #include "libmmc_config.h" @@ -32,32 +31,22 @@ class LIBMULTIMC_EXPORT LWJGLVersion : public QObject { Q_OBJECT - /*! - * The name of the LWJGL version. - */ - Q_PROPERTY(QString name READ name) - - /*! - * The URL for this version of LWJGL. - */ - Q_PROPERTY(QUrl url READ url) - - LWJGLVersion(const QString &name, const QUrl &url, QObject *parent = 0) : + LWJGLVersion(const QString &name, const QString &url, QObject *parent = 0) : QObject(parent), m_name(name), m_url(url) { } public: - static PtrLWJGLVersion Create(const QString &name, const QUrl &url, QObject *parent = 0) + static PtrLWJGLVersion Create(const QString &name, const QString &url, QObject *parent = 0) { return PtrLWJGLVersion(new LWJGLVersion(name, url, parent)); }; QString name() const { return m_name; } - QUrl url() const { return m_url; } + QString url() const { return m_url; } protected: QString m_name; - QUrl m_url; + QString m_url; }; class LIBMULTIMC_EXPORT LWJGLVersionList : public QAbstractListModel @@ -112,8 +101,6 @@ private: QList m_vlist; QNetworkReply *m_netReply; - - QNetworkAccessManager netMgr; QNetworkReply *reply; bool m_loading; diff --git a/backend/lists/MinecraftVersionList.cpp b/backend/lists/MinecraftVersionList.cpp index 7f220086..e14d7a25 100644 --- a/backend/lists/MinecraftVersionList.cpp +++ b/backend/lists/MinecraftVersionList.cpp @@ -14,6 +14,7 @@ */ #include "MinecraftVersionList.h" +#include #include @@ -29,8 +30,6 @@ #include -#include "netutils.h" - #define MCVLIST_URLBASE "http://s3.amazonaws.com/Minecraft.Download/versions/" #define ASSETS_URLBASE "http://assets.minecraft.net/" #define MCN_URLBASE "http://sonicrules.org/mcnweb.py" @@ -160,21 +159,18 @@ MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) { m_list = vlist; m_currentStable = NULL; - netMgr = nullptr; vlistReply = nullptr; } MCVListLoadTask::~MCVListLoadTask() { - if(netMgr) - netMgr->deleteLater(); } void MCVListLoadTask::executeTask() { setStatus("Loading instance version list..."); - netMgr = new QNetworkAccessManager(); - vlistReply = netMgr->get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json"))); + auto & worker = NetWorker::spawn(); + vlistReply = worker.get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json"))); connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); } @@ -283,12 +279,18 @@ void MCVListLoadTask::list_downloaded() { versionType = MinecraftVersion::Snapshot; } + else if(versionTypeStr == "old_beta" || versionTypeStr == "old_alpha") + { + versionType = MinecraftVersion::Nostalgia; + } else { //FIXME: log this somewhere continue; } + //FIXME: detect if snapshots are old or not + // Get the download URL. QString dlUrl = QString(MCVLIST_URLBASE) + versionID + "/"; diff --git a/backend/lists/MinecraftVersionList.h b/backend/lists/MinecraftVersionList.h index 078c7c66..8707016a 100644 --- a/backend/lists/MinecraftVersionList.h +++ b/backend/lists/MinecraftVersionList.h @@ -24,7 +24,6 @@ #include "libmmc_config.h" class MCVListLoadTask; -class QNetworkAccessManager; class QNetworkReply; class LIBMULTIMC_EXPORT MinecraftVersionList : public InstVersionList @@ -76,7 +75,6 @@ protected: //! Loads versions from Mojang's official version list. bool loadFromVList(); - QNetworkAccessManager *netMgr; QNetworkReply *vlistReply; MinecraftVersionList *m_list; diff --git a/backend/net/DownloadJob.cpp b/backend/net/DownloadJob.cpp new file mode 100644 index 00000000..ef842dfd --- /dev/null +++ b/backend/net/DownloadJob.cpp @@ -0,0 +1,138 @@ +#include "DownloadJob.h" +#include "pathutils.h" +#include "NetWorker.h" + +DownloadJob::DownloadJob (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; +} + +JobPtr DownloadJob::create (QUrl url, + QString target_path, + QString expected_md5 ) +{ + return JobPtr ( new DownloadJob ( url, target_path, expected_md5 ) ); +} + +void DownloadJob::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 finish(); + return; + } + else + { + m_expected_md5 = hash; + } + } + if(!ensurePathExists(filename)) + { + emit fail(); + return; + } + } + qDebug() << "Downloading " << m_url.toString(); + QNetworkRequest request ( m_url ); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_expected_md5.toLatin1()); + + auto &worker = NetWorker::spawn(); + QNetworkReply * rep = worker.get ( request ); + + m_reply = QSharedPointer ( 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 DownloadJob::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal ) +{ + emit progress ( bytesReceived, bytesTotal ); +} + +void DownloadJob::downloadError ( QNetworkReply::NetworkError error ) +{ + // error happened during download. + // TODO: log the reason why + m_status = Job_Failed; +} + +void DownloadJob::downloadFinished() +{ + // 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 finish(); + return; + } + // else the download failed + else + { + if ( m_save_to_file ) + { + m_output_file.close(); + m_output_file.remove(); + } + m_reply.clear(); + emit fail(); + return; + } +} + +void DownloadJob::downloadReadyRead() +{ + 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 fail(); + return; + } + m_opened_for_saving = true; + } + m_output_file.write ( m_reply->readAll() ); + } +} diff --git a/backend/net/DownloadJob.h b/backend/net/DownloadJob.h new file mode 100644 index 00000000..cbde3852 --- /dev/null +++ b/backend/net/DownloadJob.h @@ -0,0 +1,51 @@ +#pragma once +#include "JobQueue.h" +#include + +/** + * A single file for the downloader/cache to process. + */ +class LIBUTIL_EXPORT DownloadJob : public Job +{ + Q_OBJECT +public: + DownloadJob(QUrl url, + QString rel_target_path = QString(), + QString expected_md5 = QString() + ); + static JobPtr create(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); +public slots: + virtual void start(); + +private slots: + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);; + void downloadError(QNetworkReply::NetworkError error); + void downloadFinished(); + void downloadReadyRead(); + +public: + /// the network reply + QSharedPointer 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; + + /// The file's status + JobStatus m_status; +}; diff --git a/backend/net/JobQueue.h b/backend/net/JobQueue.h new file mode 100644 index 00000000..26f49307 --- /dev/null +++ b/backend/net/JobQueue.h @@ -0,0 +1,180 @@ +#pragma once +#include +#include "libutil_config.h" + +enum JobStatus +{ + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed +}; + +class JobList; + +class LIBUTIL_EXPORT Job : public QObject +{ + Q_OBJECT +protected: + explicit Job(): QObject(0){}; +public: + virtual ~Job() {}; +signals: + void finish(); + void fail(); + void progress(qint64 current, qint64 total); +public slots: + virtual void start() = 0; +}; +typedef QSharedPointer JobPtr; + +/** + * A list of jobs, to be processed one by one. + */ +class LIBUTIL_EXPORT JobList : public QObject +{ + friend class JobListQueue; + Q_OBJECT +public: + + JobList() : QObject(0) + { + m_status = Job_NotStarted; + current_job_idx = 0; + } + JobStatus getStatus() + { + return m_status; + } + void add(JobPtr dlable) + { + if(m_status == Job_NotStarted) + m_jobs.append(dlable); + //else there's a bug. TODO: catch the bugs + } + JobPtr getFirstJob() + { + if(m_jobs.size()) + return m_jobs[0]; + else + return JobPtr(); + } + void start() + { + current_job_idx = 0; + auto job = m_jobs[current_job_idx]; + + connect(job.data(), SIGNAL(progress(qint64,qint64)), SLOT(currentJobProgress(qint64,qint64))); + connect(job.data(), SIGNAL(finish()), SLOT(currentJobFinished())); + connect(job.data(), SIGNAL(fail()), SLOT(currentJobFailed())); + job->start(); + emit started(); + } +private slots: + void currentJobFinished() + { + if(current_job_idx == m_jobs.size() - 1) + { + m_status = Job_Finished; + emit finished(); + } + else + { + current_job_idx++; + auto job = m_jobs[current_job_idx]; + connect(job.data(), SIGNAL(progress(qint64,qint64)), SLOT(currentJobProgress(qint64,qint64))); + connect(job.data(), SIGNAL(finish()), SLOT(currentJobFinished())); + connect(job.data(), SIGNAL(fail()), SLOT(currentJobFailed())); + job->start(); + } + } + void currentJobFailed() + { + m_status = Job_Failed; + emit failed(); + } + void currentJobProgress(qint64 current, qint64 total) + { + if(!total) + return; + + int total_jobs = m_jobs.size(); + + if(!total_jobs) + return; + + float job_chunk = 1000.0 / float(total_jobs); + float cur = current; + float tot = total; + float last_chunk = (cur / tot) * job_chunk; + + float list_total = job_chunk * current_job_idx + last_chunk; + emit progress(qint64(list_total), 1000LL); + } +private: + QVector m_jobs; + /// The overall status of this job list + JobStatus m_status; + int current_job_idx; +signals: + void progress(qint64 current, qint64 total); + void started(); + void finished(); + void failed(); +}; +typedef QSharedPointer JobListPtr; + + +/** + * A queue of job lists! The job lists fail or finish as units. + */ +class LIBUTIL_EXPORT JobListQueue : public QObject +{ + Q_OBJECT +public: + JobListQueue(QObject *p = 0): + QObject(p), + currentIndex(0), + is_running(false){} + + void enqueue(JobListPtr job) + { + jobs.enqueue(job); + + // finish or fail, we should catch that and start the next one + connect(job.data(),SIGNAL(finished()), SLOT(startNextJob())); + connect(job.data(),SIGNAL(failed()), SLOT(startNextJob())); + + if(!is_running) + { + QTimer::singleShot(0, this, SLOT(startNextJob())); + } + } + +private slots: + void startNextJob() + { + if (jobs.isEmpty()) + { + currentJobList.clear(); + currentIndex = 0; + is_running = false; + emit finishedAllJobs(); + return; + } + + currentJobList = jobs.dequeue(); + is_running = true; + currentIndex = 0; + currentJobList->start(); + } + +signals: + void finishedAllJobs(); + +private: + JobListPtr currentJobList; + QQueue jobs; + unsigned currentIndex; + bool is_running; +}; diff --git a/backend/net/NetWorker.cpp b/backend/net/NetWorker.cpp new file mode 100644 index 00000000..1eef13d9 --- /dev/null +++ b/backend/net/NetWorker.cpp @@ -0,0 +1,12 @@ +#include "NetWorker.h" +#include + +NetWorker& NetWorker::spawn() +{ + static QThreadStorage storage; + if (!storage.hasLocalData()) + { + storage.setLocalData(new NetWorker()); + } + return *storage.localData(); +} diff --git a/backend/net/NetWorker.h b/backend/net/NetWorker.h new file mode 100644 index 00000000..98374e3b --- /dev/null +++ b/backend/net/NetWorker.h @@ -0,0 +1,20 @@ +/* + _.ooo-._ + .OOOP _ '. + dOOOO (_) \ + OOOOOb | + OOOOOOb. | + OOOOOOOOb | + YOO(_)OOO / + 'OOOOOY _.' + '""""'' +*/ + +#pragma once +#include +class NetWorker : public QNetworkAccessManager +{ + Q_OBJECT +public: + static NetWorker &spawn(); +}; \ No newline at end of file diff --git a/backend/tasks/LoginTask.cpp b/backend/tasks/LoginTask.cpp index 30e97ca9..7a1d5262 100644 --- a/backend/tasks/LoginTask.cpp +++ b/backend/tasks/LoginTask.cpp @@ -14,27 +14,23 @@ */ #include "LoginTask.h" +#include #include -#include -#include -#include +#include +#include #include #include -LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : - Task(parent), uInfo(uInfo) -{ - netMgr.reset(new QNetworkAccessManager()); -} +LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), uInfo(uInfo){} void LoginTask::executeTask() { setStatus("Logging in..."); - - connect(netMgr.data(), SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); + auto & worker = NetWorker::spawn(); + connect(&worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); QUrl loginURL("https://login.minecraft.net/"); QNetworkRequest netRequest(loginURL); @@ -45,11 +41,13 @@ void LoginTask::executeTask() params.addQueryItem("password", uInfo.password); params.addQueryItem("version", "13"); - netReply = netMgr->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); + netReply = worker.post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); } void LoginTask::processNetReply(QNetworkReply *reply) { + if(netReply != reply) + return; // Check for errors. switch (reply->error()) { diff --git a/backend/tasks/LoginTask.h b/backend/tasks/LoginTask.h index e2f72f9e..81d1b6cc 100644 --- a/backend/tasks/LoginTask.h +++ b/backend/tasks/LoginTask.h @@ -33,7 +33,6 @@ struct LoginResponse qint64 latestVersion; }; -class QNetworkAccessManager; class QNetworkReply; class LIBMULTIMC_EXPORT LoginTask : public Task @@ -54,8 +53,6 @@ protected: QNetworkReply* netReply; UserInfo uInfo; -private: - QSharedPointer netMgr; }; #endif // LOGINTASK_H -- cgit v1.2.3