summaryrefslogtreecommitdiffstats
path: root/logic
diff options
context:
space:
mode:
Diffstat (limited to 'logic')
-rw-r--r--logic/BaseInstance.cpp5
-rw-r--r--logic/BaseInstance.h7
-rw-r--r--logic/BaseUpdate.cpp4
-rw-r--r--logic/BaseVersion.h (renamed from logic/InstanceVersion.h)43
-rw-r--r--logic/EnabledItemFilter.cpp30
-rw-r--r--logic/EnabledItemFilter.h16
-rw-r--r--logic/ForgeInstaller.cpp140
-rw-r--r--logic/ForgeInstaller.h25
-rw-r--r--logic/InstanceFactory.cpp10
-rw-r--r--logic/InstanceFactory.h6
-rw-r--r--logic/InstanceLauncher.cpp10
-rw-r--r--logic/LegacyInstance.cpp6
-rw-r--r--logic/LegacyInstance.h2
-rw-r--r--logic/LegacyUpdate.cpp26
-rw-r--r--logic/MinecraftProcess.cpp103
-rw-r--r--logic/MinecraftProcess.h9
-rw-r--r--logic/MinecraftVersion.h29
-rw-r--r--logic/ModList.h3
-rw-r--r--logic/NostalgiaInstance.h1
-rw-r--r--logic/OneSixAssets.cpp29
-rw-r--r--logic/OneSixInstance.cpp154
-rw-r--r--logic/OneSixInstance.h10
-rw-r--r--logic/OneSixInstance_p.h1
-rw-r--r--logic/OneSixLibrary.cpp161
-rw-r--r--logic/OneSixLibrary.h104
-rw-r--r--logic/OneSixRule.cpp72
-rw-r--r--logic/OneSixRule.h72
-rw-r--r--logic/OneSixUpdate.cpp137
-rw-r--r--logic/OneSixVersion.cpp346
-rw-r--r--logic/OneSixVersion.h194
-rw-r--r--logic/OpSys.cpp23
-rw-r--r--logic/OpSys.h22
-rw-r--r--logic/VersionFactory.cpp195
-rw-r--r--logic/VersionFactory.h24
-rw-r--r--logic/lists/BaseVersionList.cpp (renamed from logic/lists/InstVersionList.cpp)36
-rw-r--r--logic/lists/BaseVersionList.h (renamed from logic/lists/InstVersionList.h)14
-rw-r--r--logic/lists/ForgeVersionList.cpp281
-rw-r--r--logic/lists/ForgeVersionList.h108
-rw-r--r--logic/lists/LwjglVersionList.cpp8
-rw-r--r--logic/lists/LwjglVersionList.h2
-rw-r--r--logic/lists/MinecraftVersionList.cpp36
-rw-r--r--logic/lists/MinecraftVersionList.h23
-rw-r--r--logic/net/ByteArrayDownload.cpp62
-rw-r--r--logic/net/ByteArrayDownload.h24
-rw-r--r--logic/net/CacheDownload.cpp127
-rw-r--r--logic/net/CacheDownload.h34
-rw-r--r--logic/net/Download.h54
-rw-r--r--logic/net/DownloadJob.cpp220
-rw-r--r--logic/net/DownloadJob.h122
-rw-r--r--logic/net/FileDownload.cpp111
-rw-r--r--logic/net/FileDownload.h35
-rw-r--r--logic/net/ForgeXzDownload.cpp278
-rw-r--r--logic/net/ForgeXzDownload.h35
-rw-r--r--logic/net/HttpMetaCache.cpp158
-rw-r--r--logic/net/HttpMetaCache.h36
-rw-r--r--logic/net/LoginTask.cpp269
-rw-r--r--logic/net/LoginTask.h (renamed from logic/tasks/LoginTask.h)34
-rw-r--r--logic/tasks/LoginTask.cpp111
-rw-r--r--logic/tasks/ProgressProvider.h20
-rw-r--r--logic/tasks/Task.cpp48
-rw-r--r--logic/tasks/Task.h48
61 files changed, 3130 insertions, 1223 deletions
diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp
index e166449f..ec86596a 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
+QSharedPointer<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..0056327a 100644
--- a/logic/BaseInstance.h
+++ b/logic/BaseInstance.h
@@ -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;
@@ -134,7 +135,7 @@ public:
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
- virtual InstVersionList *versionList() const;
+ virtual QSharedPointer<BaseVersionList> versionList() const;
/*!
* \brief Gets this instance's settings object.
@@ -147,7 +148,7 @@ public:
virtual BaseUpdate* doUpdate() = 0;
/// returns a valid minecraft process, ready for launch
- virtual MinecraftProcess* prepareForLaunch(QString user, QString session) = 0;
+ virtual MinecraftProcess* prepareForLaunch(LoginResponse response) = 0;
/// do any necessary cleanups after the instance finishes. also runs before 'prepareForLaunch'
virtual void cleanupAfterRun() = 0;
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/InstanceVersion.h b/logic/BaseVersion.h
index eecd9c4e..be717fee 100644
--- a/logic/InstanceVersion.h
+++ b/logic/BaseVersion.h
@@ -19,50 +19,27 @@
/*!
* An abstract base class for versions.
*/
-struct InstVersion
+struct BaseVersion
{
/*!
- * 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;
+ virtual QString descriptor() = 0;
+
/*!
* The name of this version as it is displayed to the user.
* For example: "1.5.1"
*/
- QString name;
+ virtual QString name() = 0;
+
/*!
- * Gets the version's timestamp.
- * This is primarily used for sorting versions in a list.
+ * This should return a string that describes
+ * the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
- qint64 timestamp;
-
- virtual QString typeString() const
- {
- return "InstVersion";
- }
+ virtual QString typeString() const = 0;
};
-typedef QSharedPointer<InstVersion> InstVersionPtr;
+typedef QSharedPointer<BaseVersion> BaseVersionPtr;
-Q_DECLARE_METATYPE( InstVersionPtr ) \ No newline at end of file
+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..9ae3f1e1
--- /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)
+{
+ QSharedPointer<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(QSharedPointer<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..f4ceaaef
--- /dev/null
+++ b/logic/ForgeInstaller.h
@@ -0,0 +1,25 @@
+#pragma once
+#include <QString>
+#include <QSharedPointer>
+
+class OneSixVersion;
+
+class ForgeInstaller
+{
+public:
+ ForgeInstaller(QString filename, QString universal_url);
+
+ bool apply(QSharedPointer<OneSixVersion> to);
+
+private:
+ // the version, read from the installer
+ QSharedPointer<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..b5832ce5 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"
@@ -68,7 +68,7 @@ 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);
@@ -89,19 +89,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..93b87f23 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 );
diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp
index 0672d2c8..4f367980 100644
--- a/logic/LegacyInstance.cpp
+++ b/logic/LegacyInstance.cpp
@@ -29,7 +29,7 @@ BaseUpdate* LegacyInstance::doUpdate()
return new LegacyUpdate(this, this);
}
-MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session)
+MinecraftProcess* LegacyInstance::prepareForLaunch(LoginResponse response)
{
MinecraftProcess * proc = new MinecraftProcess(this);
@@ -73,8 +73,8 @@ MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session
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;
diff --git a/logic/LegacyInstance.h b/logic/LegacyInstance.h
index b36026fc..2eab9035 100644
--- a/logic/LegacyInstance.h
+++ b/logic/LegacyInstance.h
@@ -57,7 +57,7 @@ public:
virtual void setShouldUpdate(bool val);
virtual BaseUpdate* doUpdate();
- virtual MinecraftProcess* prepareForLaunch( QString user, QString session );
+ virtual MinecraftProcess* prepareForLaunch(LoginResponse response);
virtual void cleanupAfterRun();
virtual QDialog * createModEditDialog ( QWidget* parent );
diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp
index 626ad1e0..d8e622dd 100644
--- a/logic/LegacyUpdate.cpp
+++ b/logic/LegacyUpdate.cpp
@@ -34,15 +34,15 @@ void LegacyUpdate::lwjglStart()
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);
+ auto version = list->getVersion(lwjglVersion);
if(!version)
{
emitFailed("Game update failed: the selected LWJGL version is invalid.");
@@ -59,8 +59,8 @@ void LegacyUpdate::lwjglStart()
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(downloadProgress(qint64,qint64)), SIGNAL(progress(qint64,qint64)));
+ connect(worker.data(), SIGNAL(finished(QNetworkReply*)), SLOT(lwjglFinished(QNetworkReply*)));
//connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
}
@@ -77,7 +77,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply* reply)
"\nSometimes you have to wait a bit if you download many LWJGL versions in a row. YMMV");
return;
}
- auto *worker = MMC->qnam();
+ 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)
@@ -97,7 +97,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply* reply)
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)));
+ connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SIGNAL(progress(qint64,qint64)));
m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater);
return;
}
@@ -170,7 +170,7 @@ void LegacyUpdate::extractLwjgl()
if (name.contains(nativesDir))
{
int lastSlash = name.lastIndexOf('/');
- int lastBackSlash = name.lastIndexOf('/');
+ int lastBackSlash = name.lastIndexOf('\\');
if(lastSlash != -1)
name = name.mid(lastSlash+1);
else if(lastBackSlash != -1)
@@ -227,10 +227,12 @@ void LegacyUpdate::jarStart()
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();
}
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/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.h b/logic/NostalgiaInstance.h
index f2df1828..1436e48d 100644
--- a/logic/NostalgiaInstance.h
+++ b/logic/NostalgiaInstance.h
@@ -9,3 +9,4 @@ public:
explicit NostalgiaInstance(const QString &rootDir, SettingsObject * settings, QObject *parent = 0);
virtual QString getStatusbarDescription();
};
+
diff --git a/logic/OneSixAssets.cpp b/logic/OneSixAssets.cpp
index c65ee607..ca7a5534 100644
--- a/logic/OneSixAssets.cpp
+++ b/logic/OneSixAssets.cpp
@@ -3,6 +3,8 @@
#include <QtXml/QtXml>
#include "OneSixAssets.h"
#include "net/DownloadJob.h"
+#include "net/HttpMetaCache.h"
+#include "MultiMC.h"
inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
{
@@ -65,7 +67,7 @@ void OneSixAssets::fetchXMLFinished()
nuke_whitelist.clear();
auto firstJob = index_job->first();
- QByteArray ba = firstJob->m_data;
+ QByteArray ba = firstJob.dynamicCast<ByteArrayDownload>()->m_data;
QString xmlErrorMsg;
QDomDocument doc;
@@ -76,10 +78,12 @@ void OneSixAssets::fetchXMLFinished()
//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 +108,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 +129,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..6e39b5b5 100644
--- a/logic/OneSixInstance.cpp
+++ b/logic/OneSixInstance.cpp
@@ -2,7 +2,7 @@
#include "OneSixInstance_p.h"
#include "OneSixUpdate.h"
#include "MinecraftProcess.h"
-#include "VersionFactory.h"
+#include "OneSixVersion.h"
#include <setting.h>
#include <pathutils.h>
@@ -10,8 +10,9 @@
#include <JlCompress.h>
#include <gui/OneSixModEditDialog.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 +20,7 @@ OneSixInstance::OneSixInstance ( const QString& rootDir, SettingsObject* setting
reloadFullVersion();
}
-BaseUpdate* OneSixInstance::doUpdate()
+BaseUpdate *OneSixInstance::doUpdate()
{
return new OneSixUpdate(this);
}
@@ -34,10 +35,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 +49,24 @@ 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:
+ token_mapping["auth_username"] = response.username;
+ token_mapping["auth_session"] = response.session_id;
+ 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";
*/
-
+
token_mapping["profile_name"] = name();
token_mapping["version_name"] = version->id;
@@ -75,8 +74,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 +83,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())
+ if (JlCompress::extractWithExceptions(path, natives_dir_raw, lib->extract_excludes)
+ .isEmpty())
{
return nullptr;
}
@@ -116,11 +116,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 +134,16 @@ 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));
+
// 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,10 +156,10 @@ void OneSixInstance::cleanupAfterRun()
dir.removeRecursively();
}
-QSharedPointer< ModList > OneSixInstance::loaderModList()
+QSharedPointer<ModList> OneSixInstance::loaderModList()
{
I_D(OneSixInstance);
- if(!d->loader_mod_list)
+ if (!d->loader_mod_list)
{
d->loader_mod_list.reset(new ModList(loaderModsDir()));
}
@@ -168,10 +168,10 @@ QSharedPointer< ModList > OneSixInstance::loaderModList()
return d->loader_mod_list;
}
-QSharedPointer< ModList > OneSixInstance::resourcePackList()
+QSharedPointer<ModList> OneSixInstance::resourcePackList()
{
I_D(OneSixInstance);
- if(!d->resource_pack_list)
+ if (!d->resource_pack_list)
{
d->resource_pack_list.reset(new ModList(resourcePacksDir()));
}
@@ -180,13 +180,12 @@ QSharedPointer< ModList > OneSixInstance::resourcePackList()
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);
@@ -198,48 +197,81 @@ 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;
- }
- };
+ 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;
+ }
return false;
}
-QSharedPointer< OneSixVersion > OneSixInstance::getFullVersion()
+QSharedPointer<OneSixVersion> OneSixInstance::getFullVersion()
{
I_D(OneSixInstance);
return d->version;
@@ -255,16 +287,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 +318,4 @@ QString OneSixInstance::instanceConfigFolder() const
{
return PathCombine(minecraftRoot(), "config");
}
+
diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h
index a4c67ed1..33091188 100644
--- a/logic/OneSixInstance.h
+++ b/logic/OneSixInstance.h
@@ -23,7 +23,7 @@ public:
virtual QString instanceConfigFolder() const;
virtual BaseUpdate* doUpdate();
- virtual MinecraftProcess* prepareForLaunch ( QString user, QString session );
+ virtual MinecraftProcess* prepareForLaunch ( LoginResponse response );
virtual void cleanupAfterRun();
virtual QString intendedVersionId() const;
@@ -41,6 +41,12 @@ public:
bool reloadFullVersion();
/// get the current full version info
QSharedPointer<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?
+ bool versionIsCustom();
virtual QString defaultBaseJar() const;
virtual QString defaultCustomBaseJar() const;
@@ -48,5 +54,5 @@ public:
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..7b1ca82e 100644
--- a/logic/OneSixInstance_p.h
+++ b/logic/OneSixInstance_p.h
@@ -2,6 +2,7 @@
#include "BaseInstance_p.h"
#include "OneSixVersion.h"
+#include "OneSixLibrary.h"
#include "ModList.h"
struct OneSixInstancePrivate: public BaseInstancePrivate
diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp
new file mode 100644
index 00000000..63d42646
--- /dev/null
+++ b/logic/OneSixLibrary.cpp
@@ -0,0 +1,161 @@
+#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<QSharedPointer<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 != "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..2a16d8e1
--- /dev/null
+++ b/logic/OneSixLibrary.h
@@ -0,0 +1,104 @@
+#pragma once
+#include <QString>
+#include <QStringList>
+#include <QMap>
+#include <QSharedPointer>
+#include <QJsonObject>
+#include "OpSys.h"
+
+class Rule;
+
+class OneSixLibrary
+{
+private:
+ // basic values used internally (so far)
+ QString m_name;
+ QString m_base_url = "https://s3.amazonaws.com/Minecraft.Download/libraries/";
+ QList<QSharedPointer<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<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 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..545cd641
--- /dev/null
+++ b/logic/OneSixRule.cpp
@@ -0,0 +1,72 @@
+#include "OneSixRule.h"
+#include <QJsonObject>
+#include <QJsonArray>
+
+QList<QSharedPointer<Rule>> rulesFromJsonV4(QJsonObject &objectWithRules)
+{
+ QList<QSharedPointer<Rule>> rules;
+ auto rulesVal = objectWithRules.value("rules");
+ if (!rulesVal.isArray())
+ return rules;
+
+ 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));
+ 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..23d20ff4
--- /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<QSharedPointer<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 QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp)
+ {
+ return QSharedPointer<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 QSharedPointer<ImplicitRule> create(RuleAction result)
+ {
+ return QSharedPointer<ImplicitRule> (new ImplicitRule(result));
+ }
+};
diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp
index 428d6ef7..41d8f599 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>
@@ -26,19 +26,20 @@
#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 +47,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 =
+ MMC->minecraftlist()->findVersion(intendedVersion).dynamicCast<MinecraftVersion>();
+ if (targetVersion == nullptr)
{
// don't do anything if it was invalid
emitSucceeded();
return;
}
-
- if(m_inst->shouldUpdate())
+
+ if (m_inst->shouldUpdate())
{
versionFileStart();
}
@@ -69,45 +71,65 @@ 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()));
+ urlstr += targetVersion->descriptor() + "/" + targetVersion->descriptor() + ".json";
+ auto job = new DownloadJob("Version index");
+ job->addByteArrayDownload(QUrl(urlstr));
+ specificVersionDownloadJob.reset(job);
+ 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)));
+ connect(specificVersionDownloadJob.data(), 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 = DlJob.dynamicCast<ByteArrayDownload>()->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 +140,47 @@ 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();
-
+
// 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);
+ 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.data(), SIGNAL(progress(qint64, qint64)),
+ SIGNAL(progress(qint64, qint64)));
jarlibDownloadJob->start();
}
@@ -159,6 +192,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/OneSixVersion.cpp b/logic/OneSixVersion.cpp
index 56e272e2..64a47562 100644
--- a/logic/OneSixVersion.cpp
+++ b/logic/OneSixVersion.cpp
@@ -1,132 +1,310 @@
#include "OneSixVersion.h"
+#include "OneSixLibrary.h"
+#include "OneSixRule.h"
-RuleAction RuleAction_fromString(QString name)
+QSharedPointer<OneSixVersion> fromJsonV4(QJsonObject root,
+ QSharedPointer<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;
+ QSharedPointer<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 )
+QSharedPointer<OneSixVersion> OneSixVersion::fromJson(QJsonObject root)
{
- m_name = name;
-}
-void Library::setBaseUrl ( QString base_url )
-{
- m_base_url = base_url;
+ QSharedPointer<OneSixVersion> readVersion(new OneSixVersion());
+ int launcher_ver = readVersion->minimumLauncherVersion =
+ root.value("minimumLauncherVersion").toDouble();
+
+ // ADD MORE HERE :D
+ if (launcher_ver > 0 && launcher_ver <= 7)
+ return fromJsonV4(root, readVersion);
+ else
+ {
+ return QSharedPointer<OneSixVersion>();
+ }
}
-void Library::setIsNative()
+
+QSharedPointer<OneSixVersion> OneSixVersion::fromFile(QString filepath)
{
- m_is_native = true;
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ return QSharedPointer<OneSixVersion>();
+ }
+
+ auto data = file.readAll();
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
+
+ if (jsonError.error != QJsonParseError::NoError)
+ {
+ return QSharedPointer<OneSixVersion>();
+ }
+
+ if (!jsonDoc.isObject())
+ {
+ return QSharedPointer<OneSixVersion>();
+ }
+ QJsonObject root = jsonDoc.object();
+ auto version = fromJson(root);
+ 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<QSharedPointer<OneSixLibrary>> OneSixVersion::getActiveNormalLibs()
{
- m_rules = rules;
+ QList<QSharedPointer<OneSixLibrary>> output;
+ for (auto lib : libraries)
+ {
+ if (lib->isActive() && !lib->isNative())
+ {
+ output.append(lib);
+ }
+ }
+ return output;
}
-bool Library::isActive()
+
+QList<QSharedPointer<OneSixLibrary>> OneSixVersion::getActiveNativeLibs()
{
- return m_is_active;
+ QList<QSharedPointer<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..69268ecf 100644
--- a/logic/OneSixVersion.h
+++ b/logic/OneSixVersion.h
@@ -1,155 +1,36 @@
#pragma once
#include <QtCore>
+class OneSixLibrary;
-class Library;
-
-enum OpSys
-{
- 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
+class OneSixVersion : public QAbstractListModel
{
-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) {}
-public:
- static QSharedPointer<OsRule> create(RuleAction result, OpSys system, QString version_regexp)
- {
- return QSharedPointer<OsRule> (new OsRule(result, system, version_regexp));
- }
-};
-
-class ImplicitRule : public Rule
-{
-protected:
- virtual bool applies ( Library* )
- {
- return true;
- }
- ImplicitRule(RuleAction result)
- : Rule(result) {}
+ // serialization/deserialization
public:
- static QSharedPointer<ImplicitRule> create(RuleAction result)
- {
- return QSharedPointer<ImplicitRule> (new ImplicitRule(result));
- }
-};
+ bool toOriginalFile();
+ static QSharedPointer<OneSixVersion> fromJson(QJsonObject root);
+ static QSharedPointer<OneSixVersion> fromFile(QString filepath);
-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();
-};
+ QList<QSharedPointer<OneSixLibrary>> getActiveNormalLibs();
+ QList<QSharedPointer<OneSixLibrary>> getActiveNativeLibs();
+ // called when something starts/stops messing with the object
+ // FIXME: these are ugly in every possible way.
+ void externalUpdateStart();
+ void externalUpdateFinish();
-
-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 +43,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<QSharedPointer<OneSixLibrary>> libraries;
+
/*
FIXME: add support for those rules here? Looks like a pile of quick hacks to me though.
@@ -197,17 +79,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..e2adbf3b
--- /dev/null
+++ b/logic/lists/ForgeVersionList.cpp
@@ -0,0 +1,281 @@
+/* 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>
+
+#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 = m_vlist[index.row()].dynamicCast<ForgeVersion>();
+ 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");
+ job->addCacheDownload(QUrl(JSON_URL), forgeListEntry);
+ listJob.reset(job);
+ connect(listJob.data(), SIGNAL(succeeded()), SLOT(list_downloaded()));
+ connect(listJob.data(), SIGNAL(failed()), SLOT(versionFileFailed()));
+ connect(listJob.data(), SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
+ listJob->start();
+}
+
+void ForgeListLoadTask::list_downloaded()
+{
+ QByteArray data;
+ {
+ auto DlJob = listJob->first();
+ auto filename = DlJob.dynamicCast<CacheDownload>()->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.
+ QSharedPointer<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..613de8a6
--- /dev/null
+++ b/logic/lists/ForgeVersionList.h
@@ -0,0 +1,108 @@
+/* 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 QSharedPointer<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();
+
+protected:
+ DownloadJobPtr listJob;
+ ForgeVersionList *m_list;
+};
diff --git a/logic/lists/LwjglVersionList.cpp b/logic/lists/LwjglVersionList.cpp
index d7826a82..b3e2dab4 100644
--- a/logic/lists/LwjglVersionList.cpp
+++ b/logic/lists/LwjglVersionList.cpp
@@ -24,14 +24,6 @@
#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss"
-LWJGLVersionList mainVersionList;
-
-LWJGLVersionList &LWJGLVersionList::get()
-{
- return mainVersionList;
-}
-
-
LWJGLVersionList::LWJGLVersionList(QObject *parent) :
QAbstractListModel(parent)
{
diff --git a/logic/lists/LwjglVersionList.h b/logic/lists/LwjglVersionList.h
index 638a0b67..23e92a1a 100644
--- a/logic/lists/LwjglVersionList.h
+++ b/logic/lists/LwjglVersionList.h
@@ -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..35f7251e 100644
--- a/logic/lists/MinecraftVersionList.cpp
+++ b/logic/lists/MinecraftVersionList.cpp
@@ -34,10 +34,8 @@
#define ASSETS_URLBASE "http://assets.minecraft.net/"
#define MCN_URLBASE "http://sonicrules.org/mcnweb.py"
-MinecraftVersionList mcVList;
-
MinecraftVersionList::MinecraftVersionList(QObject *parent) :
- InstVersionList(parent)
+ BaseVersionList(parent)
{
}
@@ -52,7 +50,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 +60,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 = first.dynamicCast<MinecraftVersion>();
+ auto right = second.dynamicCast<MinecraftVersion>();
+ return left->timestamp > right->timestamp;
}
void MinecraftVersionList::sort()
@@ -76,7 +74,7 @@ void MinecraftVersionList::sort()
endResetModel();
}
-InstVersionPtr MinecraftVersionList::getLatestStable() const
+BaseVersionPtr MinecraftVersionList::getLatestStable() const
{
for (int i = 0; i < m_vlist.length(); i++)
{
@@ -86,15 +84,10 @@ InstVersionPtr MinecraftVersionList::getLatestStable() const
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;
@@ -159,7 +152,7 @@ void MCVListLoadTask::executeTask()
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());
@@ -214,7 +207,7 @@ void MCVListLoadTask::list_downloaded()
}
QJsonArray versions = root.value("versions").toArray();
- QList<InstVersionPtr > tempList;
+ QList<BaseVersionPtr > tempList;
for (int i = 0; i < versions.count(); i++)
{
bool is_snapshot = false;
@@ -280,7 +273,7 @@ void MCVListLoadTask::list_downloaded()
// Now, we construct the version object and add it to the list.
QSharedPointer<MinecraftVersion> mcVersion(new MinecraftVersion());
- mcVersion->name = mcVersion->descriptor = versionID;
+ mcVersion->m_name = mcVersion->m_descriptor = versionID;
mcVersion->timestamp = versionTime.toMSecsSinceEpoch();
mcVersion->download_url = dlUrl;
mcVersion->is_latest = is_latest;
@@ -293,8 +286,3 @@ void MCVListLoadTask::list_downloaded()
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..61ecc298
--- /dev/null
+++ b/logic/net/ByteArrayDownload.cpp
@@ -0,0 +1,62 @@
+#include "ByteArrayDownload.h"
+#include "MultiMC.h"
+#include <QDebug>
+
+ByteArrayDownload::ByteArrayDownload ( QUrl url )
+ : Download()
+{
+ m_url = url;
+ m_status = Job_NotStarted;
+}
+
+void ByteArrayDownload::start()
+{
+ qDebug() << "Downloading " << m_url.toString();
+ QNetworkRequest request ( m_url );
+ 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 ByteArrayDownload::downloadProgress ( qint64 bytesReceived, qint64 bytesTotal )
+{
+ emit progress (index_within_job, bytesReceived, bytesTotal );
+}
+
+void ByteArrayDownload::downloadError ( QNetworkReply::NetworkError error )
+{
+ // error happened during download.
+ qDebug() << "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.clear();
+ emit succeeded(index_within_job);
+ return;
+ }
+ // else the download failed
+ else
+ {
+ m_reply.clear();
+ 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..428b21db
--- /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 QSharedPointer<ByteArrayDownload> ByteArrayDownloadPtr;
diff --git a/logic/net/CacheDownload.cpp b/logic/net/CacheDownload.cpp
new file mode 100644
index 00000000..dc2e0448
--- /dev/null
+++ b/logic/net/CacheDownload.cpp
@@ -0,0 +1,127 @@
+#include "MultiMC.h"
+#include "CacheDownload.h"
+#include <pathutils.h>
+
+#include <QCryptographicHash>
+#include <QFileInfo>
+#include <QDateTime>
+#include <QDebug>
+
+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;
+ }
+ qDebug() << "Downloading " << m_url.toString();
+ QNetworkRequest request(m_url);
+ request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.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 CacheDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ emit progress(index_within_job, bytesReceived, bytesTotal);
+}
+
+void CacheDownload::downloadError(QNetworkReply::NetworkError error)
+{
+ // error happened during download.
+ // TODO: log the reason why
+ 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();
+ m_entry->last_changed_timestamp =
+ output_file_info.lastModified().toUTC().toMSecsSinceEpoch();
+ m_entry->stale = false;
+ MMC->metacache()->updateEntry(m_entry);
+
+ m_reply.clear();
+ emit succeeded(index_within_job);
+ return;
+ }
+ // else the download failed
+ else
+ {
+ m_output_file.close();
+ m_output_file.remove();
+ m_reply.clear();
+ 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..f95dabd5
--- /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 QSharedPointer<CacheDownload> CacheDownloadPtr;
diff --git a/logic/net/Download.h b/logic/net/Download.h
new file mode 100644
index 00000000..91f09dec
--- /dev/null
+++ b/logic/net/Download.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <QObject>
+#include <QUrl>
+#include <QSharedPointer>
+#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
+ QSharedPointer<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 QSharedPointer<Download> DownloadPtr;
diff --git a/logic/net/DownloadJob.cpp b/logic/net/DownloadJob.cpp
index cad9ae72..03a69555 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 <QDebug>
-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())
+ qDebug() << 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;
+ qDebug() << m_job_name.toLocal8Bit() << " failed.";
emit failed();
}
else
{
- qDebug() << "Download JOB succeeded: " << this;
+ qDebug() << 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();
+ qDebug() << "Part " << index << " failed 3 times (" << downloads[index]->m_url << ")";
+ num_failed++;
+ if (num_failed + num_succeeded == downloads.size())
+ {
+ qDebug() << m_job_name.toLocal8Bit() << " failed.";
+ emit failed();
+ }
+ }
+ else
+ {
+ qDebug() << "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)
+ qDebug() << m_job_name.toLocal8Bit() << " started.";
+ for (auto iter : downloads)
{
connect(iter.data(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
+ connect(iter.data(), SIGNAL(failed(int)), SLOT(partFailed(int)));
+ connect(iter.data(), 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..8c32950a 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();
-};
/**
* 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..aad4a991
--- /dev/null
+++ b/logic/net/FileDownload.cpp
@@ -0,0 +1,111 @@
+#include "MultiMC.h"
+#include "FileDownload.h"
+#include <pathutils.h>
+#include <QCryptographicHash>
+#include <QDebug>
+
+
+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 )
+ {
+ 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 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.clear();
+ emit succeeded(index_within_job);
+ return;
+ }
+ // else the download failed
+ else
+ {
+ m_output_file.close();
+ m_reply.clear();
+ 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..190bee58
--- /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 QSharedPointer<FileDownload> FileDownloadPtr;
diff --git a/logic/net/ForgeXzDownload.cpp b/logic/net/ForgeXzDownload.cpp
new file mode 100644
index 00000000..dde36f4e
--- /dev/null
+++ b/logic/net/ForgeXzDownload.cpp
@@ -0,0 +1,278 @@
+#include "MultiMC.h"
+#include "ForgeXzDownload.h"
+#include <pathutils.h>
+
+#include <QCryptographicHash>
+#include <QFileInfo>
+#include <QDateTime>
+#include <QDebug>
+
+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;
+ }
+ qDebug() << "Downloading " << m_url.toString();
+ QNetworkRequest request(m_url);
+ request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->etag.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 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.clear();
+ emit failed(index_within_job);
+ return;
+ }
+ }
+ // else the download failed
+ else
+ {
+ m_pack200_xz_file.close();
+ m_pack200_xz_file.remove();
+ m_reply.clear();
+ 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:
+ qDebug() << "Memory allocation failed\n";
+ xz_dec_end(s);
+ emit failed(index_within_job);
+ return;
+
+ case XZ_MEMLIMIT_ERROR:
+ qDebug() << "Memory usage limit reached\n";
+ xz_dec_end(s);
+ emit failed(index_within_job);
+ return;
+
+ case XZ_FORMAT_ERROR:
+ qDebug() << "Not a .xz file\n";
+ xz_dec_end(s);
+ emit failed(index_within_job);
+ return;
+
+ case XZ_OPTIONS_ERROR:
+ qDebug() << "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:
+ qDebug() << "File is corrupt\n";
+ xz_dec_end(s);
+ emit failed(index_within_job);
+ return;
+
+ default:
+ qDebug() << "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)
+ {
+ qDebug() << "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->last_changed_timestamp =
+ output_file_info.lastModified().toUTC().toMSecsSinceEpoch();
+ m_entry->stale = false;
+ MMC->metacache()->updateEntry(m_entry);
+
+ m_reply.clear();
+ emit succeeded(index_within_job);
+}
diff --git a/logic/net/ForgeXzDownload.h b/logic/net/ForgeXzDownload.h
new file mode 100644
index 00000000..8cb47783
--- /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 QSharedPointer<ForgeXzDownload> ForgeXzDownloadPtr;
diff --git a/logic/net/HttpMetaCache.cpp b/logic/net/HttpMetaCache.cpp
index 87741dc9..46801ab3 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 <QDebug>
+
+#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->last_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->last_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))
+ {
+ qDebug() << "Cannot add entry with unknown base: " << stale_entry->base.toLocal8Bit();
+ return false;
+ }
+ if(stale_entry->stale)
+ {
+ qDebug() << "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();
@@ -83,11 +185,20 @@ void HttpMetaCache::Load()
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();
+ // 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))
@@ -118,14 +229,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..daf6c43f 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,45 @@ struct MetaEntry
QString path;
QString md5sum;
QString etag;
- quint64 last_changed_timestamp = 0;
+ qint64 last_changed_timestamp = 0;
+ bool stale = true;
+ QString getFullPath();
};
typedef QSharedPointer<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 +56,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..2a45400e
--- /dev/null
+++ b/logic/net/LoginTask.cpp
@@ -0,0 +1,269 @@
+/* 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.data(), 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::processLegacyReply(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)
+ {
+ parseLegacyReply(reply->readAll());
+ }
+ else if (responseCode == 503)
+ {
+ emitFailed(tr("The login servers are currently unavailable. Check "
+ "http://help.mojang.com/ for more info."));
+ }
+ else
+ {
+ emitFailed(tr("Login failed: Unknown HTTP error %1 occurred.")
+ .arg(QString::number(responseCode)));
+ }
+ break;
+ }
+
+ case QNetworkReply::OperationCanceledError:
+ emitFailed(tr("Login canceled."));
+ break;
+
+ default:
+ emitFailed(tr("Login failed: %1").arg(reply->errorString()));
+ break;
+ }
+}
+
+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::yggdrasilLogin()
+{
+ setStatus(tr("Logging in..."));
+ auto worker = MMC->qnam();
+ connect(worker.data(), 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());
+}
+
+void LoginTask::processYggdrasilReply(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)
+ {
+ parseYggdrasilReply(reply->readAll());
+ }
+ else if (responseCode == 503)
+ {
+ emitFailed(tr("The login servers are currently unavailable. Check "
+ "http://help.mojang.com/ for more info."));
+ }
+ else
+ {
+ emitFailed(tr("Login failed: Unknown HTTP error %1 occurred.")
+ .arg(QString::number(responseCode)));
+ }
+ break;
+ }
+
+ case QNetworkReply::OperationCanceledError:
+ emitFailed(tr("Login canceled."));
+ break;
+
+ default:
+ emitFailed(tr("Login failed: %1").arg(reply->errorString()));
+ break;
+ }
+}
+
+/*
+{
+ "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};
+ emitSucceeded();
+}
diff --git a/logic/tasks/LoginTask.h b/logic/net/LoginTask.h
index bde672b8..ba87142d 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,9 @@ struct UserInfo
struct LoginResponse
{
QString username;
- QString sessionID;
- qint64 latestVersion;
+ QString session_id;
+ QString player_name;
+ QString player_id;
};
class QNetworkReply;
@@ -38,21 +38,25 @@ 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);
+
+ void yggdrasilLogin();
+ void processYggdrasilReply(QNetworkReply *reply);
+ void parseYggdrasilReply(QByteArray data);
+
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 &current, 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..c75bcb8f 100644
--- a/logic/tasks/Task.cpp
+++ b/logic/tasks/Task.cpp
@@ -16,70 +16,56 @@
#include "Task.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;
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