diff options
45 files changed, 1321 insertions, 915 deletions
diff --git a/AppSettings.cpp b/AppSettings.cpp deleted file mode 100644 index 8b0265eb..00000000 --- a/AppSettings.cpp +++ /dev/null @@ -1,73 +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 "AppSettings.h" - -#include <setting.h> - -#include <QPoint> -#include <QApplication> -//#include <QColor> - -AppSettings::AppSettings(QObject *parent) : - INISettingsObject(QApplication::applicationDirPath() + "/multimc.cfg",parent) -{ - // Updates - registerSetting(new Setting("UseDevBuilds", false)); - registerSetting(new Setting("AutoUpdate", true)); - - // Folders - registerSetting(new Setting("InstanceDir", "instances")); - registerSetting(new Setting("CentralModsDir", "mods")); - registerSetting(new Setting("LWJGLDir", "lwjgl")); - - // Console - registerSetting(new Setting("ShowConsole", true)); - registerSetting(new Setting("AutoCloseConsole", true)); - - // Toolbar settings - registerSetting(new Setting("InstanceToolbarVisible", true)); - registerSetting(new Setting("InstanceToolbarPosition", QPoint())); - - // Console Colors -// registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); -// registerSetting(new Setting("StdOutColor", QColor(Qt::black))); -// registerSetting(new Setting("StdErrColor", QColor(Qt::red))); - - // Window Size - registerSetting(new Setting("LaunchMaximized", false)); - registerSetting(new Setting("MinecraftWinWidth", 854)); - registerSetting(new Setting("MinecraftWinHeight", 480)); - - // Auto login - registerSetting(new Setting("AutoLogin", false)); - - // Memory - registerSetting(new Setting("MinMemAlloc", 512)); - registerSetting(new Setting("MaxMemAlloc", 1024)); - registerSetting(new Setting("PermGen", 64)); - - // Java Settings - registerSetting(new Setting("JavaPath", "java")); - registerSetting(new Setting("JvmArgs", "")); - - // Custom Commands - registerSetting(new Setting("PreLaunchCommand", "")); - registerSetting(new Setting("PostExitCommand", "")); - - // Misc - registerSetting(new Setting("TheCat", false)); // The Cat - registerSetting(new Setting("NoHide", false)); // Shall the main window hide on instance launch -} diff --git a/AppSettings.h b/AppSettings.h deleted file mode 100644 index d8f21de1..00000000 --- a/AppSettings.h +++ /dev/null @@ -1,29 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <QObject> - -#include <inisettingsobject.h> - -class AppSettings : public INISettingsObject -{ - Q_OBJECT -public: - explicit AppSettings(QObject *parent = 0); -}; - - diff --git a/AppVersion.cpp b/AppVersion.cpp deleted file mode 100644 index 86b00920..00000000 --- a/AppVersion.cpp +++ /dev/null @@ -1,46 +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 "AppVersion.h" - -#include "config.h" - -AppVersion AppVersion::current(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD); - -AppVersion::AppVersion(int major, int minor, int revision, int build, QObject *parent) : - QObject(parent) -{ - this->major = major; - this->minor = minor; - this->revision = revision; - this->build = build; -} - -AppVersion::AppVersion(const AppVersion& ver) -{ - this->major = ver.major; - this->minor = ver.minor; - this->revision = ver.revision; - this->build = ver.build; -} - -QString AppVersion::toString() const -{ - return QString("%1.%2.%3.%4").arg( - QString::number(major), - QString::number(minor), - QString::number(revision), - QString::number(build)); -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c9cf983..cbc3d842 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,9 +147,8 @@ ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) ######## Headers ######## SET(MULTIMC_HEADERS -multimc_pragma.h -AppVersion.h -AppSettings.h +MultiMC.h +MultiMCVersion.h gui/mainwindow.h gui/settingsdialog.h @@ -181,9 +180,16 @@ logic/MinecraftProcess.h logic/Mod.h logic/ModList.h +# Basic instance launcher for starting from terminal +logic/InstanceLauncher.h + # network stuffs +logic/net/Download.h +logic/net/FileDownload.h +logic/net/ByteArrayDownload.h +logic/net/CacheDownload.h logic/net/DownloadJob.h -logic/net/NetWorker.h +logic/net/HttpMetaCache.h # legacy instances logic/LegacyInstance.h @@ -207,7 +213,7 @@ logic/lists/InstanceList.h logic/lists/InstVersionList.h logic/lists/MinecraftVersionList.h logic/lists/LwjglVersionList.h -logic/IconListModel.h +logic/lists/IconList.h # Tasks logic/tasks/Task.h @@ -217,9 +223,7 @@ logic/tasks/LoginTask.h ######## Sources ######## SET(MULTIMC_SOURCES -main.cpp -AppVersion.cpp -AppSettings.cpp +MultiMC.cpp gui/mainwindow.cpp gui/settingsdialog.cpp @@ -248,9 +252,15 @@ logic/MinecraftProcess.cpp logic/Mod.cpp logic/ModList.cpp +# Basic instance launcher for starting from terminal +logic/InstanceLauncher.cpp + # network stuffs - to be moved into a depend lib ~_~ -logic/net/NetWorker.cpp +logic/net/FileDownload.cpp +logic/net/ByteArrayDownload.cpp +logic/net/CacheDownload.cpp logic/net/DownloadJob.cpp +logic/net/HttpMetaCache.cpp # legacy instances logic/LegacyInstance.cpp @@ -272,7 +282,7 @@ logic/lists/InstanceList.cpp logic/lists/InstVersionList.cpp logic/lists/MinecraftVersionList.cpp logic/lists/LwjglVersionList.cpp -logic/IconListModel.cpp +logic/lists/IconList.cpp # Tasks logic/tasks/Task.cpp diff --git a/MultiMC.cpp b/MultiMC.cpp new file mode 100644 index 00000000..c93701b8 --- /dev/null +++ b/MultiMC.cpp @@ -0,0 +1,242 @@ + +#include "MultiMC.h" +#include <iostream> +#include <QDir> +#include <QNetworkAccessManager> + +#include "gui/mainwindow.h" +#include "logic/lists/InstanceList.h" +#include "logic/lists/IconList.h" +#include "logic/InstanceLauncher.h" +#include "logic/net/HttpMetaCache.h" + + +#include "pathutils.h" +#include "cmdutils.h" +#include <inisettingsobject.h> +#include <setting.h> + +#include "config.h" +using namespace Util::Commandline; + +MultiMC::MultiMC ( int& argc, char** argv ) + :QApplication ( argc, argv ) +{ + setOrganizationName("Forkk"); + setApplicationName("MultiMC 5"); + + // Print app header + std::cout << "MultiMC 5" << std::endl; + std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; + + // Commandline parsing + QHash<QString, QVariant> args; + { + Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); + + // --help + parser.addSwitch("help"); + parser.addShortOpt("help", 'h'); + parser.addDocumentation("help", "display this help and exit."); + // --version + parser.addSwitch("version"); + parser.addShortOpt("version", 'V'); + parser.addDocumentation("version", "display program version and exit."); + // --dir + parser.addOption("dir", applicationDirPath()); + parser.addShortOpt("dir", 'd'); + parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)"); + // --update + parser.addOption("update"); + parser.addShortOpt("update", 'u'); + parser.addDocumentation("update", "replaces the given file with the running executable", "<path>"); + // --quietupdate + parser.addSwitch("quietupdate"); + parser.addShortOpt("quietupdate", 'U'); + parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates"); + // --launch + parser.addOption("launch"); + parser.addShortOpt("launch", 'l'); + parser.addDocumentation("launch", "tries to launch the given instance", "<inst>"); + + // parse the arguments + try + { + args = parser.parse(arguments()); + } + catch(ParsingError e) + { + std::cerr << "CommandLineError: " << e.what() << std::endl; + std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; + m_status = MultiMC::Failed; + return; + } + + // display help and exit + if (args["help"].toBool()) + { + std::cout << qPrintable(parser.compileHelp(arguments()[0])); + m_status = MultiMC::Succeeded; + return; + } + + // display version and exit + if (args["version"].toBool()) + { + std::cout << "Version " << VERSION_STR << std::endl; + std::cout << "Git " << GIT_COMMIT << std::endl; + std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; + m_status = MultiMC::Succeeded; + return; + } + + // update + // Note: cwd is always the current executable path! + if (!args["update"].isNull()) + { + std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; + QString cwd = QDir::currentPath(); + QDir::setCurrent(applicationDirPath()); + QFile file(applicationFilePath()); + file.copy(args["update"].toString()); + if(args["quietupdate"].toBool()) + { + m_status = MultiMC::Succeeded; + return; + } + QDir::setCurrent(cwd); + } + } + + // change directory + QDir::setCurrent(args["dir"].toString()); + + // load settings + initGlobalSettings(); + + // and instances + m_instances = new InstanceList(m_settings->get("InstanceDir").toString(),this); + std::cout << "Loading Instances..." << std::endl; + m_instances->loadList(); + + // init the http meta cache + initHttpMetaCache(); + + // create the global network manager + m_qnam = new QNetworkAccessManager(this); + + // Register meta types. + qRegisterMetaType<LoginResponse>("LoginResponse"); + + // launch instance, if that's what should be done + if (!args["launch"].isNull()) + { + if(InstanceLauncher(args["launch"].toString()).launch()) + m_status = MultiMC::Succeeded; + else + m_status = MultiMC::Failed; + return; + } + m_status = MultiMC::Initialized; +} + +MultiMC::~MultiMC() +{ + delete m_settings; + delete m_metacache; +} + +void MultiMC::initGlobalSettings() +{ + m_settings = new INISettingsObject("multimc.cfg", this); + // Updates + m_settings->registerSetting(new Setting("UseDevBuilds", false)); + m_settings->registerSetting(new Setting("AutoUpdate", true)); + + // Folders + m_settings->registerSetting(new Setting("InstanceDir", "instances")); + m_settings->registerSetting(new Setting("CentralModsDir", "mods")); + m_settings->registerSetting(new Setting("LWJGLDir", "lwjgl")); + + // Console + m_settings->registerSetting(new Setting("ShowConsole", true)); + m_settings->registerSetting(new Setting("AutoCloseConsole", true)); + + // Toolbar settings + m_settings->registerSetting(new Setting("InstanceToolbarVisible", true)); + m_settings->registerSetting(new Setting("InstanceToolbarPosition", QPoint())); + + // Console Colors +// m_settings->registerSetting(new Setting("SysMessageColor", QColor(Qt::blue))); +// m_settings->registerSetting(new Setting("StdOutColor", QColor(Qt::black))); +// m_settings->registerSetting(new Setting("StdErrColor", QColor(Qt::red))); + + // Window Size + m_settings->registerSetting(new Setting("LaunchMaximized", false)); + m_settings->registerSetting(new Setting("MinecraftWinWidth", 854)); + m_settings->registerSetting(new Setting("MinecraftWinHeight", 480)); + + // Auto login + m_settings->registerSetting(new Setting("AutoLogin", false)); + + // Memory + m_settings->registerSetting(new Setting("MinMemAlloc", 512)); + m_settings->registerSetting(new Setting("MaxMemAlloc", 1024)); + m_settings->registerSetting(new Setting("PermGen", 64)); + + // Java Settings + m_settings->registerSetting(new Setting("JavaPath", "java")); + m_settings->registerSetting(new Setting("JvmArgs", "")); + + // Custom Commands + m_settings->registerSetting(new Setting("PreLaunchCommand", "")); + m_settings->registerSetting(new Setting("PostExitCommand", "")); + + // The cat + m_settings->registerSetting(new Setting("TheCat", false)); + + // Shall the main window hide on instance launch + m_settings->registerSetting(new Setting("NoHide", false)); +} + +void MultiMC::initHttpMetaCache() +{ + m_metacache = new HttpMetaCache("metacache"); + m_metacache->addBase("assets", QDir("assets").absolutePath()); + m_metacache->addBase("versions", QDir("versions").absolutePath()); + m_metacache->addBase("libraries", QDir("libraries").absolutePath()); + m_metacache->Load(); +} + + +IconList* MultiMC::icons() +{ + if ( !m_icons ) + { + m_icons = new IconList; + } + return m_icons; +} + + +int main(int argc, char *argv[]) +{ + // initialize Qt + MultiMC app(argc, argv); + + // show main window + MainWindow mainWin; + mainWin.show(); + + switch(app.status()) + { + case MultiMC::Initialized: + return app.exec(); + case MultiMC::Failed: + return 1; + case MultiMC::Succeeded: + return 0; + } +} + +#include "MultiMC.moc"
\ No newline at end of file diff --git a/MultiMC.h b/MultiMC.h new file mode 100644 index 00000000..0c8b673b --- /dev/null +++ b/MultiMC.h @@ -0,0 +1,76 @@ +#pragma once + +#include <QApplication> +#include "MultiMCVersion.h" +#include "config.h" + +class HttpMetaCache; +class SettingsObject; +class InstanceList; +class IconList; +class QNetworkAccessManager; + +#if defined(MMC) +#undef MMC +#endif +#define MMC (static_cast<MultiMC *>(QCoreApplication::instance())) + +class MultiMC : public QApplication +{ + Q_OBJECT +public: + enum Status + { + Failed, + Succeeded, + Initialized, + }; + +public: + MultiMC ( int& argc, char** argv ); + virtual ~MultiMC(); + + SettingsObject * settings() + { + return m_settings; + }; + + InstanceList * instances() + { + return m_instances; + }; + + IconList * icons(); + + Status status() + { + return m_status; + } + + MultiMCVersion version() + { + return m_version; + } + + QNetworkAccessManager * qnam() + { + return m_qnam; + } + + HttpMetaCache * metacache() + { + return m_metacache; + } +private: + void initGlobalSettings(); + + void initHttpMetaCache(); +private: + SettingsObject * m_settings = nullptr; + InstanceList * m_instances = nullptr; + IconList * m_icons = nullptr; + QNetworkAccessManager * m_qnam = nullptr; + HttpMetaCache * m_metacache = nullptr; + Status m_status = MultiMC::Failed; + MultiMCVersion m_version = {VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD}; +};
\ No newline at end of file diff --git a/MultiMC.pro b/MultiMC.pro deleted file mode 100644 index b82b2294..00000000 --- a/MultiMC.pro +++ /dev/null @@ -1,47 +0,0 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2013-01-08T14:14:58 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = MultiMC -TEMPLATE = app - - -SOURCES += main.cpp\ - gui/mainwindow.cpp \ - data/instancebase.cpp \ - util/pathutils.cpp \ - data/instancelist.cpp \ - data/stdinstance.cpp \ - data/inifile.cpp \ - gui/settingsdialog.cpp \ - gui/modeditwindow.cpp \ - util/appsettings.cpp \ - gui/instancesettings.cpp - -HEADERS += gui/mainwindow.h \ - data/instancebase.h \ - util/pathutils.h \ - data/instancelist.h \ - data/stdinstance.h \ - data/inifile.h \ - gui/settingsdialog.h \ - gui/modeditwindow.h \ - util/apputils.h \ - util/appsettings.h \ - gui/instancesettings.h - -FORMS += gui/mainwindow.ui \ - gui/settingsdialog.ui \ - gui/modeditwindow.ui \ - gui/instancesettings.ui \ - gui/logindialog.ui \ - gui/consolewindow.ui - -RESOURCES += \ - multimc.qrc diff --git a/AppVersion.h b/MultiMCVersion.h index 86f53950..70a2259e 100644 --- a/AppVersion.h +++ b/MultiMCVersion.h @@ -15,26 +15,26 @@ #pragma once -#include <QObject> +#include <QString> /*! * \brief The Version class represents a MultiMC version number. */ -class AppVersion : public QObject +struct MultiMCVersion { - Q_OBJECT -public: - explicit AppVersion(int major = 0, int minor = 0, int revision = 0, - int build = 0, QObject *parent = 0); - - AppVersion(const AppVersion& ver); - /*! * \brief Converts the Version to a string. * \return The version number in string format (major.minor.revision.build). */ - QString toString() const; - + QString toString() const + { + return QString("%1.%2.%3.%4").arg( + QString::number(major), + QString::number(minor), + QString::number(revision), + QString::number(build)); + } + /*! * \brief The major version number. * For MultiMC 5, this will always be 5. @@ -59,7 +59,5 @@ public: * a new build is run. */ int build; - - static AppVersion current; }; diff --git a/depends/classparser/src/javautils.cpp b/depends/classparser/src/javautils.cpp index 4a359031..3a5c6934 100644 --- a/depends/classparser/src/javautils.cpp +++ b/depends/classparser/src/javautils.cpp @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "multimc_pragma.h" #include "classfile.h" #include "javautils.h" diff --git a/depends/settings/include/settingsobject.h b/depends/settings/include/settingsobject.h index a2f03699..e8bf5c83 100644 --- a/depends/settings/include/settingsobject.h +++ b/depends/settings/include/settingsobject.h @@ -13,8 +13,7 @@ * limitations under the License. */ -#ifndef SETTINGSOBJECT_H -#define SETTINGSOBJECT_H +#pragma once #include <QObject> #include <QMap> @@ -183,10 +182,3 @@ protected: private: QMap<QString, Setting *> m_settings; }; - -/*! - * \brief A global settings object. - */ -LIBSETTINGS_EXPORT extern SettingsObject *globalSettings; - -#endif // SETTINGSOBJECT_H diff --git a/depends/settings/src/settingsobject.cpp b/depends/settings/src/settingsobject.cpp index bf7b8825..2ca544ad 100644 --- a/depends/settings/src/settingsobject.cpp +++ b/depends/settings/src/settingsobject.cpp @@ -18,8 +18,6 @@ #include <QVariant> -SettingsObject *globalSettings; - SettingsObject::SettingsObject(QObject *parent) : QObject(parent) { diff --git a/gui/IconPickerDialog.cpp b/gui/IconPickerDialog.cpp index b75eb30c..2dd80292 100644 --- a/gui/IconPickerDialog.cpp +++ b/gui/IconPickerDialog.cpp @@ -1,7 +1,8 @@ +#include <MultiMC.h> #include "IconPickerDialog.h" #include "instancedelegate.h" #include "ui_IconPickerDialog.h" -#include "logic/IconListModel.h" +#include "logic/lists/IconList.h" #include <QKeyEvent> #include <QPushButton> #include <QFileDialog> @@ -38,8 +39,7 @@ IconPickerDialog::IconPickerDialog(QWidget *parent) : contentsWidget->installEventFilter(this); - IconList * list = IconList::instance(); - contentsWidget->setModel(list); + contentsWidget->setModel(MMC->icons()); auto buttonAdd = ui->buttonBox->addButton("Add Icon",QDialogButtonBox::ResetRole); auto buttonRemove = ui->buttonBox->addButton("Remove Icon",QDialogButtonBox::ResetRole); @@ -71,7 +71,6 @@ bool IconPickerDialog::eventFilter ( QObject* obj, QEvent* evt) return QDialog::eventFilter(obj ,evt); } QKeyEvent *keyEvent = static_cast<QKeyEvent*>(evt); - IconList * list = IconList::instance(); switch(keyEvent->key()) { case Qt::Key_Delete: @@ -89,14 +88,12 @@ bool IconPickerDialog::eventFilter ( QObject* obj, QEvent* evt) void IconPickerDialog::addNewIcon() { QStringList fileNames = QFileDialog::getOpenFileNames(this, "Select Icons", QString(), "Icons (*.png *.jpg *.jpeg)"); - IconList * list = IconList::instance(); - list->installIcons(fileNames); + MMC->icons()->installIcons(fileNames); } void IconPickerDialog::removeSelectedIcon() { - IconList * list = IconList::instance(); - list->deleteIcon(selectedIconKey); + MMC->icons()->deleteIcon(selectedIconKey); } @@ -119,7 +116,7 @@ void IconPickerDialog::selectionChanged ( QItemSelection selected, QItemSelectio int IconPickerDialog::exec ( QString selection ) { - IconList * list = IconList::instance(); + IconList * list = MMC->icons(); auto contentsWidget = ui->iconView; selectedIconKey = selection; diff --git a/gui/aboutdialog.cpp b/gui/aboutdialog.cpp index 873cc175..9792d4fe 100644 --- a/gui/aboutdialog.cpp +++ b/gui/aboutdialog.cpp @@ -1,9 +1,7 @@ #include "aboutdialog.h" #include "ui_aboutdialog.h" -#include <AppVersion.h> - #include <QIcon> -#include <QApplication> +#include <MultiMC.h> AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), @@ -12,10 +10,10 @@ AboutDialog::AboutDialog(QWidget *parent) : ui->setupUi(this); ui->icon->setPixmap(QIcon(":/icons/multimc/scalable/apps/multimc.svg").pixmap(64)); - ui->title->setText("MultiMC " + AppVersion::current.toString()); + ui->title->setText("MultiMC " + MMC->version().toString()); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); - QApplication::instance()->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt())); + MMC->connect(ui->aboutQt, SIGNAL(clicked()), SLOT(aboutQt())); } AboutDialog::~AboutDialog() diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 152773e7..7ddc66a9 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -16,6 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include <MultiMC.h> #include "mainwindow.h" #include "ui_mainwindow.h" @@ -23,7 +24,7 @@ #include <QMenu> #include <QMessageBox> #include <QInputDialog> -#include <QApplication> + #include <QDesktopServices> #include <QUrl> #include <QDir> @@ -48,33 +49,30 @@ #include "gui/consolewindow.h" #include "gui/instancesettings.h" -#include "AppSettings.h" -#include "AppVersion.h" - #include "logic/lists/InstanceList.h" +#include "logic/lists/MinecraftVersionList.h" +#include "logic/lists/LwjglVersionList.h" +#include "logic/lists/IconList.h" + #include "logic/tasks/LoginTask.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" #include "logic/OneSixAssets.h" #include "logic/OneSixUpdate.h" -#include "logic/lists/MinecraftVersionList.h" -#include "logic/lists/LwjglVersionList.h" -#include <logic/IconListModel.h> -#include <logic/LegacyInstance.h> + +#include "logic/LegacyInstance.h" #include "instancedelegate.h" #include "IconPickerDialog.h" #include "LabeledToolButton.h" #include "EditNotesDialog.h" -MainWindow::MainWindow ( QWidget *parent ) : - QMainWindow ( parent ), - ui ( new Ui::MainWindow ), - instList ( globalSettings->get ( "InstanceDir" ).toString() ) +MainWindow::MainWindow ( QWidget *parent ) + :QMainWindow ( parent ), ui ( new Ui::MainWindow ) { ui->setupUi ( this ); - setWindowTitle ( QString ( "MultiMC %1" ).arg ( AppVersion::current.toString() ) ); + setWindowTitle ( QString ( "MultiMC %1" ).arg ( MMC->version().toString() ) ); // Set the selected instance to null m_selectedInstance = nullptr; @@ -126,7 +124,7 @@ MainWindow::MainWindow ( QWidget *parent ) : //proxymodel->setDynamicSortFilter ( true ); // FIXME: instList should be global-ish, or at least not tied to the main window... maybe the application itself? - proxymodel->setSourceModel ( &instList ); + proxymodel->setSourceModel ( MMC->instances() ); proxymodel->sort ( 0 ); view->setFrameShape ( QFrame::NoFrame ); view->setModel ( proxymodel ); @@ -135,7 +133,7 @@ MainWindow::MainWindow ( QWidget *parent ) : } // The cat background { - bool cat_enable = globalSettings->get("TheCat").toBool(); + bool cat_enable = MMC->settings()->get("TheCat").toBool(); ui->actionCAT->setChecked(cat_enable); connect(ui->actionCAT, SIGNAL(toggled(bool)), SLOT(onCatToggled(bool))); setCatBackground(cat_enable); @@ -151,12 +149,11 @@ MainWindow::MainWindow ( QWidget *parent ) : ); // model reset -> selection is invalid. All the instance pointers are wrong. // FIXME: stop using POINTERS everywhere - connect(&instList,SIGNAL(dataIsInvalid()),SLOT(selectionBad())); + connect(MMC->instances() ,SIGNAL(dataIsInvalid()),SLOT(selectionBad())); // run the things that load and download other things... FIXME: this is NOT the place // FIXME: invisible actions in the background = NOPE. { - instList.loadList(); if (!MinecraftVersionList::getMainList().isLoaded()) { m_versionLoadTask = MinecraftVersionList::getMainList().getLoadTask(); @@ -212,7 +209,7 @@ bool MainWindow::eventFilter ( QObject* obj, QEvent* ev ) void MainWindow::onCatToggled ( bool state ) { setCatBackground(state); - globalSettings->set("TheCat", state); + MMC->settings()->set("TheCat", state); } void MainWindow::setCatBackground ( bool enabled ) @@ -264,7 +261,7 @@ void MainWindow::on_actionAddInstance_triggered() BaseInstance *newInstance = NULL; QString instDirName = DirNameFromString(newInstDlg.instName()); - QString instDir = PathCombine(globalSettings->get("InstanceDir").toString(), instDirName); + QString instDir = PathCombine(MMC->settings()->get("InstanceDir").toString(), instDirName); auto &loader = InstanceFactory::get(); @@ -275,7 +272,7 @@ void MainWindow::on_actionAddInstance_triggered() case InstanceFactory::NoCreateError: newInstance->setName(newInstDlg.instName()); newInstance->setIconKey(newInstDlg.iconKey()); - instList.add(InstancePtr(newInstance)); + MMC->instances()->add(InstancePtr(newInstance)); return; case InstanceFactory::InstExists: @@ -305,8 +302,7 @@ void MainWindow::on_actionChangeInstIcon_triggered() if(dlg.result() == QDialog::Accepted) { m_selectedInstance->setIconKey(dlg.selectedIconKey); - IconList * iconListModel = IconList::instance(); - auto ico =iconListModel->getIcon(dlg.selectedIconKey); + auto ico = MMC->icons()->getIcon(dlg.selectedIconKey); ui->actionChangeInstIcon->setIcon(ico); } } @@ -328,18 +324,18 @@ void MainWindow::on_actionChangeInstGroup_triggered() void MainWindow::on_actionViewInstanceFolder_triggered() { - QString str = globalSettings->get ( "InstanceDir" ).toString(); + QString str = MMC->settings()->get ( "InstanceDir" ).toString(); openDirInDefaultProgram ( str ); } void MainWindow::on_actionRefresh_triggered() { - instList.loadList(); + MMC->instances()->loadList(); } void MainWindow::on_actionViewCentralModsFolder_triggered() { - openDirInDefaultProgram ( globalSettings->get ( "CentralModsDir" ).toString() , true); + openDirInDefaultProgram ( MMC->settings()->get ( "CentralModsDir" ).toString() , true); } void MainWindow::on_actionConfig_Folder_triggered() @@ -365,7 +361,7 @@ void MainWindow::on_actionSettings_triggered() void MainWindow::on_actionReportBug_triggered() { - openWebPage ( QUrl ( "http://jira.forkk.net/browse/MMC" ) ); + openWebPage ( QUrl ( "http://multimc.myjetbrains.com/youtrack/dashboard#newissue=yes" ) ); } void MainWindow::on_actionNews_triggered() @@ -542,7 +538,7 @@ void MainWindow::launchInstance(BaseInstance *instance, LoginResponse response) return; // Prepare GUI: If it shall stay open disable the required parts - if (globalSettings->get("NoHide").toBool()) + if (MMC->settings()->get("NoHide").toBool()) { ui->actionLaunchInstance->setEnabled(false); } @@ -647,8 +643,7 @@ void MainWindow::instanceChanged( const QModelIndex& current, const QModelIndex& ui->actionEditInstMods->setEnabled(m_selectedInstance->menuActionEnabled("actionEditInstMods")); statusBar()->clearMessage(); statusBar()->showMessage(m_selectedInstance->getStatusbarDescription()); - IconList * iconListModel = IconList::instance(); - auto ico =iconListModel->getIcon(iconKey); + auto ico =MMC->icons()->getIcon(iconKey); ui->actionChangeInstIcon->setIcon(ico); } else @@ -664,8 +659,7 @@ void MainWindow::selectionBad() statusBar()->clearMessage(); ui->instanceToolBar->setEnabled(false); renameButton->setText("Rename Instance"); - IconList * iconListModel = IconList::instance(); - auto ico =iconListModel->getIcon(iconKey); + auto ico = MMC->icons()->getIcon(iconKey); ui->actionChangeInstIcon->setIcon(ico); } diff --git a/gui/mainwindow.h b/gui/mainwindow.h index e8a6cbcf..2b0e1d34 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -135,7 +135,6 @@ private: KCategoryDrawer * drawer; KCategorizedView * view; InstanceProxyModel * proxymodel; - InstanceList instList; MinecraftProcess *proc; ConsoleWindow *console; OneSixAssets *assets_downloader; diff --git a/gui/newinstancedialog.cpp b/gui/newinstancedialog.cpp index f0a70a1b..ac3bcd7d 100644 --- a/gui/newinstancedialog.cpp +++ b/gui/newinstancedialog.cpp @@ -13,12 +13,13 @@ * limitations under the License. */ +#include <MultiMC.h> #include "newinstancedialog.h" #include "ui_newinstancedialog.h" #include "logic/InstanceFactory.h" #include "logic/InstanceVersion.h" -#include "logic/IconListModel.h" +#include "logic/lists/IconList.h" #include "logic/lists/MinecraftVersionList.h" #include "logic/tasks/Task.h" @@ -49,8 +50,7 @@ NewInstanceDialog::NewInstanceDialog(QWidget *parent) : */ setSelectedVersion(MinecraftVersionList::getMainList().getLatestStable()); InstIconKey = "infinity"; - IconList * list = IconList::instance(); - ui->iconButton->setIcon(list->getIcon(InstIconKey)); + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); } NewInstanceDialog::~NewInstanceDialog() @@ -113,9 +113,8 @@ void NewInstanceDialog::on_iconButton_clicked() if(dlg.result() == QDialog::Accepted) { - IconList * list = IconList::instance(); InstIconKey = dlg.selectedIconKey; - ui->iconButton->setIcon(list->getIcon(InstIconKey)); + ui->iconButton->setIcon(MMC->icons()->getIcon(InstIconKey)); } } diff --git a/gui/settingsdialog.cpp b/gui/settingsdialog.cpp index 52093039..a1fbb8e7 100644 --- a/gui/settingsdialog.cpp +++ b/gui/settingsdialog.cpp @@ -16,8 +16,8 @@ #include "settingsdialog.h" #include "ui_settingsdialog.h" -#include "AppSettings.h" - +#include <MultiMC.h> +#include <settingsobject.h> #include <QFileDialog> #include <QMessageBox> @@ -27,7 +27,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : { ui->setupUi(this); - loadSettings(globalSettings); + loadSettings(MMC->settings()); updateCheckboxStuff(); } @@ -85,7 +85,7 @@ void SettingsDialog::on_maximizedCheckBox_clicked(bool checked) void SettingsDialog::on_buttonBox_accepted() { - applySettings(globalSettings); + applySettings(MMC->settings()); } void SettingsDialog::applySettings(SettingsObject *s) diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index bd3229c8..e166449f 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -18,6 +18,7 @@ #include <QFileInfo> #include <QDir> +#include <MultiMC.h> #include "inisettingsobject.h" #include "setting.h" @@ -52,6 +53,8 @@ BaseInstance::BaseInstance( BaseInstancePrivate* d_in, settings().registerSetting(new Setting("UseCustomBaseJar", true)); settings().registerSetting(new Setting("CustomBaseJar", "")); + auto globalSettings = MMC->settings(); + // Java Settings settings().registerSetting(new Setting("OverrideJava", false)); settings().registerSetting(new OverrideSetting("JavaPath", globalSettings->getSetting("JavaPath"))); diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp new file mode 100644 index 00000000..312f4c69 --- /dev/null +++ b/logic/InstanceLauncher.cpp @@ -0,0 +1,74 @@ +#include "InstanceLauncher.h" +#include "MultiMC.h" + +#include <iostream> +#include "gui/logindialog.h" +#include "gui/taskdialog.h" +#include "gui/consolewindow.h" +#include "logic/tasks/LoginTask.h" +#include "logic/MinecraftProcess.h" +#include "lists/InstanceList.h" + + +InstanceLauncher::InstanceLauncher ( QString instId ) + :QObject(), instId ( instId ) +{} + +void InstanceLauncher::onTerminated() +{ + std::cout << "Minecraft exited" << std::endl; + MMC->quit(); +} + +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 ); + if ( !proc ) + { + //FIXME: report error + return; + } + console = new ConsoleWindow(); + console->show(); + + connect ( proc, SIGNAL ( ended() ), SLOT ( onTerminated() ) ); + connect ( proc, SIGNAL ( log ( QString,MessageLevel::Enum ) ), console, SLOT ( write ( QString,MessageLevel::Enum ) ) ); + + proc->launch(); +} + +void InstanceLauncher::doLogin ( const QString& errorMsg ) +{ + LoginDialog* loginDlg = new LoginDialog ( nullptr, errorMsg ); + loginDlg->exec(); + if ( loginDlg->result() == QDialog::Accepted ) + { + UserInfo uInfo {loginDlg->getUsername(), loginDlg->getPassword() }; + + TaskDialog* tDialog = new TaskDialog ( 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 ); + tDialog->exec ( loginTask ); + } + //onLoginComplete(LoginResponse("Offline","Offline", 1)); +} + +int InstanceLauncher::launch() +{ + std::cout << "Launching Instance '" << qPrintable ( instId ) << "'" << std::endl; + auto instance = MMC->instances()->getInstanceById(instId); + if ( instance.isNull() ) + { + std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; + return 1; + } + + std::cout << "Logging in..." << std::endl; + doLogin ( "" ); + + return MMC->exec(); +} diff --git a/logic/InstanceLauncher.h b/logic/InstanceLauncher.h new file mode 100644 index 00000000..de93e3d7 --- /dev/null +++ b/logic/InstanceLauncher.h @@ -0,0 +1,28 @@ +#pragma once + +#include <QObject> + +class MinecraftProcess; +class ConsoleWindow; + +// Commandline instance launcher +class InstanceLauncher : public QObject +{ + Q_OBJECT + +private: + QString instId; + MinecraftProcess *proc; + ConsoleWindow *console; + +public: + InstanceLauncher(QString instId); + +private slots: + void onTerminated(); + void onLoginComplete(); + void doLogin(const QString &errorMsg); + +public: + int launch(); +}; diff --git a/logic/LegacyInstance.cpp b/logic/LegacyInstance.cpp index 124cdfcb..0672d2c8 100644 --- a/logic/LegacyInstance.cpp +++ b/logic/LegacyInstance.cpp @@ -2,7 +2,7 @@ #include "LegacyInstance_p.h" #include "MinecraftProcess.h" #include "LegacyUpdate.h" -#include "IconListModel.h" +#include "lists/IconList.h" #include <setting.h> #include <pathutils.h> #include <cmdutils.h> @@ -10,6 +10,7 @@ #include <QFileInfo> #include <QDir> #include <QImage> +#include <MultiMC.h> #define LAUNCHER_FILE "MultiMCLauncher.jar" @@ -32,8 +33,7 @@ MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session { MinecraftProcess * proc = new MinecraftProcess(this); - IconList * list = IconList::instance(); - QIcon icon = list->getIcon(iconKey()); + QIcon icon = MMC->icons()->getIcon(iconKey()); auto pixmap = icon.pixmap(128,128); pixmap.save(PathCombine(minecraftRoot(), "icon.png"),"PNG"); @@ -66,7 +66,7 @@ MinecraftProcess* LegacyInstance::prepareForLaunch(QString user, QString session args << QString("-Xdock:name=\"%1\"").arg(windowTitle); #endif - QString lwjgl = QDir(globalSettings->get("LWJGLDir").toString() + "/" + lwjglVersion()).absolutePath(); + QString lwjgl = QDir(MMC->settings()->get("LWJGLDir").toString() + "/" + lwjglVersion()).absolutePath(); // launcher arguments args << QString("-Xms%1m").arg(settings().get("MinMemAlloc").toInt()); diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index 3d286373..c75a0011 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -3,7 +3,7 @@ #include "lists/MinecraftVersionList.h" #include "BaseInstance.h" #include "LegacyInstance.h" -#include "net/NetWorker.h" +#include "MultiMC.h" #include "ModList.h" #include <pathutils.h> #include <quazip.h> @@ -52,15 +52,15 @@ void LegacyUpdate::lwjglStart() QString url = version->url(); QUrl realUrl(url); QString hostname = realUrl.host(); - auto &worker = NetWorker::qnam(); + auto worker = MMC->qnam(); QNetworkRequest req(realUrl); req.setRawHeader("Host", hostname.toLatin1()); req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker.get ( req ); + 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(worker, SIGNAL(finished(QNetworkReply*)), SLOT(lwjglFinished(QNetworkReply*))); //connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } @@ -77,13 +77,13 @@ 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 = NetWorker::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) { //you must tell which cookie goes with which url - worker.cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); + worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net")); } //here you can check for the 302 or whatever other header i need @@ -96,7 +96,7 @@ void LegacyUpdate::lwjglFinished(QNetworkReply* reply) QNetworkRequest req(redirectedTo); req.setRawHeader("Host", hostname.toLatin1()); req.setHeader(QNetworkRequest::UserAgentHeader, "Wget/1.14 (linux-gnu)"); - QNetworkReply * rep = worker.get(req); + QNetworkReply * rep = worker->get(req); connect(rep, SIGNAL(downloadProgress(qint64,qint64)), SLOT(updateDownloadProgress(qint64,qint64))); m_reply = QSharedPointer<QNetworkReply> (rep, &QObject::deleteLater); return; @@ -227,7 +227,9 @@ 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())); + auto dljob = new DownloadJob("Minecraft.jar for version " + intended_version_id); + dljob->add(QUrl(urlstr), inst->defaultBaseJar()); + legacyDownloadJob.reset(); 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))); diff --git a/logic/OneSixAssets.cpp b/logic/OneSixAssets.cpp index c65ee607..5bdd29d7 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->add(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->add(QUrl ( "http://s3.amazonaws.com/Minecraft.Resources/" )); connect ( job, SIGNAL(succeeded()), SLOT ( fetchXMLFinished() ) ); index_job.reset ( job ); job->start(); diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 428d6ef7..ce71bde0 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -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> @@ -72,7 +72,9 @@ void OneSixUpdate::versionFileStart() QString urlstr("http://s3.amazonaws.com/Minecraft.Download/versions/"); urlstr += targetVersion->descriptor + "/" + targetVersion->descriptor + ".json"; - specificVersionDownloadJob.reset(new DownloadJob(QUrl(urlstr))); + auto job = new DownloadJob("Version index"); + job->add(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))); @@ -92,7 +94,7 @@ void OneSixUpdate::versionFileFinished() // 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.write(DlJob.dynamicCast<ByteArrayDownload>()->m_data); vfile1.close(); } @@ -134,16 +136,22 @@ void OneSixUpdate::jarlibStart() 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->add(QUrl(urlstr), targetstr); + jarlibDownloadJob.reset(job); auto libs = version->getActiveNativeLibs(); libs.append(version->getActiveNormalLibs()); + 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); + auto entry = metacache->resolveEntry("libraries", lib->storagePath()); + if(entry->stale) + { + jarlibDownloadJob->add(download_path, entry); + } } connect(jarlibDownloadJob.data(), SIGNAL(succeeded()), SLOT(jarlibFinished())); connect(jarlibDownloadJob.data(), SIGNAL(failed()), SLOT(jarlibFailed())); diff --git a/logic/IconListModel.cpp b/logic/lists/IconList.cpp index 4a5795d6..6988d02f 100644 --- a/logic/IconListModel.cpp +++ b/logic/lists/IconList.cpp @@ -1,4 +1,4 @@ -#include "IconListModel.h" +#include "IconList.h" #include <pathutils.h> #include <QMap> #include <QEventLoop> @@ -6,8 +6,6 @@ #include <QMimeData> #include <QUrl> #define MAX_SIZE 1024 -IconList* IconList::m_Instance = 0; -QMutex IconList::mutex; struct entry { @@ -256,25 +254,4 @@ int IconList::getIconIndex ( QString key ) return -1; } - -void IconList::drop() -{ - mutex.lock(); - delete m_Instance; - m_Instance = 0; - mutex.unlock(); -} - -IconList* IconList::instance() -{ - if ( !m_Instance ) - { - mutex.lock(); - if ( !m_Instance ) - m_Instance = new IconList; - mutex.unlock(); - } - return m_Instance; -} - -#include "IconListModel.moc"
\ No newline at end of file +#include "IconList.moc"
\ No newline at end of file diff --git a/logic/IconListModel.h b/logic/lists/IconList.h index 907dfd81..cad80cdf 100644 --- a/logic/IconListModel.h +++ b/logic/lists/IconList.h @@ -9,8 +9,9 @@ class Private; class IconList : public QAbstractListModel { public: - static IconList* instance(); - static void drop(); + IconList(); + virtual ~IconList(); + QIcon getIcon ( QString key ); int getIconIndex ( QString key ); @@ -28,14 +29,10 @@ public: void installIcons ( QStringList iconFiles ); private: - virtual ~IconList(); - IconList(); // hide copy constructor IconList ( const IconList & ) = delete; // hide assign op IconList& operator= ( const IconList & ) = delete; void reindex(); - static IconList* m_Instance; - static QMutex mutex; Private* d; }; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index 1d13e3f2..b930f781 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -22,13 +22,13 @@ #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> +#include <pathutils.h> +#include "MultiMC.h" #include "logic/lists/InstanceList.h" +#include "logic/lists/IconList.h" #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" -#include <logic/IconListModel.h> - -#include "pathutils.h" const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -81,9 +81,8 @@ QVariant InstanceList::data ( const QModelIndex& index, int role ) const } case Qt::DecorationRole: { - IconList * ic = IconList::instance(); QString key = pdata->iconKey(); - return ic->getIcon(key); + return MMC->icons()->getIcon(key); } // for now. case KCategorizedSortFilterProxyModel::CategorySortRole: @@ -413,5 +412,3 @@ bool InstanceProxyModel::subSortLessThan (const QModelIndex& left, const QModelI return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; //return pdataLeft->name() < pdataRight->name(); } - -#include "InstanceList.moc"
\ No newline at end of file diff --git a/logic/lists/LwjglVersionList.cpp b/logic/lists/LwjglVersionList.cpp index c0854628..d7826a82 100644 --- a/logic/lists/LwjglVersionList.cpp +++ b/logic/lists/LwjglVersionList.cpp @@ -14,7 +14,7 @@ */ #include "LwjglVersionList.h" -#include "logic/net/NetWorker.h" +#include "MultiMC.h" #include <QtNetwork> @@ -91,8 +91,8 @@ void LWJGLVersionList::loadList() Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); setLoading(true); - auto & worker = NetWorker::qnam(); - reply = worker.get(QNetworkRequest(QUrl(RSS_URL))); + auto worker = MMC->qnam(); + reply = worker->get(QNetworkRequest(QUrl(RSS_URL))); connect(reply, SIGNAL(finished()), SLOT(netRequestComplete())); } diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp index 4444f5b0..42fb1b50 100644 --- a/logic/lists/MinecraftVersionList.cpp +++ b/logic/lists/MinecraftVersionList.cpp @@ -14,7 +14,7 @@ */ #include "MinecraftVersionList.h" -#include <logic/net/NetWorker.h> +#include <MultiMC.h> #include <QDebug> @@ -151,8 +151,8 @@ MCVListLoadTask::~MCVListLoadTask() void MCVListLoadTask::executeTask() { setStatus("Loading instance version list..."); - auto & worker = NetWorker::qnam(); - vlistReply = worker.get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json"))); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest(QUrl(QString(MCVLIST_URLBASE) + "versions.json"))); connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); } diff --git a/logic/net/ByteArrayDownload.cpp b/logic/net/ByteArrayDownload.cpp new file mode 100644 index 00000000..6ae3f121 --- /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. + // TODO: log the reason why + 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..c0074574 --- /dev/null +++ b/logic/net/CacheDownload.cpp @@ -0,0 +1,122 @@ +#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 5c8ed4b9..cbc2c5fe 100644 --- a/logic/net/DownloadJob.cpp +++ b/logic/net/DownloadJob.cpp @@ -1,136 +1,29 @@ #include "DownloadJob.h" #include "pathutils.h" -#include "NetWorker.h" +#include "MultiMC.h" +#include "FileDownload.h" +#include "ByteArrayDownload.h" +#include "CacheDownload.h" -Download::Download (QUrl url, QString target_path, QString expected_md5 ) - :Job() +ByteArrayDownloadPtr DownloadJob::add ( QUrl url ) { - 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 = NetWorker::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 ); -} - -void Download::downloadError ( QNetworkReply::NetworkError error ) -{ - // error happened during download. - // TODO: log the reason why - m_status = Job_Failed; -} - -void Download::downloadFinished() -{ - // if the download succeeded - if ( m_status != Job_Failed ) - { - // nothing went wrong... - m_status = Job_Finished; - // save the data to the downloadable if we aren't saving to file - if ( !m_save_to_file ) - { - m_data = m_reply->readAll(); - } - else - { - m_output_file.close(); - } - - //TODO: check md5 here! - m_reply.clear(); - emit 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; - } + ByteArrayDownloadPtr ptr (new ByteArrayDownload(url)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + return ptr; } -void Download::downloadReadyRead() +FileDownloadPtr DownloadJob::add ( QUrl url, QString rel_target_path) { - 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() ); - } + FileDownloadPtr ptr (new FileDownload(url, rel_target_path)); + ptr->index_within_job = downloads.size(); + downloads.append(ptr); + return ptr; } -DownloadPtr DownloadJob::add ( QUrl url, QString rel_target_path, QString expected_md5 ) +CacheDownloadPtr DownloadJob::add ( QUrl url, MetaEntryPtr entry) { - DownloadPtr ptr (new Download(url, rel_target_path, expected_md5)); + CacheDownloadPtr ptr (new CacheDownload(url, entry)); ptr->index_within_job = downloads.size(); downloads.append(ptr); return ptr; @@ -139,14 +32,17 @@ DownloadPtr DownloadJob::add ( QUrl url, QString rel_target_path, QString expect void DownloadJob::partSucceeded ( int index ) { num_succeeded++; + qDebug() << m_job_name.toLocal8Bit() << " progress: " << num_succeeded << "/" << downloads.size(); if(num_failed + num_succeeded == downloads.size()) { if(num_failed) { + qDebug() << m_job_name.toLocal8Bit() << " failed."; emit failed(); } else { + qDebug() << m_job_name.toLocal8Bit() << " succeeded."; emit succeeded(); } } @@ -157,14 +53,8 @@ void DownloadJob::partFailed ( int index ) num_failed++; if(num_failed + num_succeeded == downloads.size()) { - if(num_failed) - { - emit failed(); - } - else - { - emit succeeded(); - } + qDebug() << m_job_name.toLocal8Bit() << " failed."; + emit failed(); } } @@ -176,6 +66,7 @@ void DownloadJob::partProgress ( int index, qint64 bytesReceived, qint64 bytesTo void DownloadJob::start() { + qDebug() << m_job_name.toLocal8Bit() << " started."; for(auto iter: downloads) { connect(iter.data(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); diff --git a/logic/net/DownloadJob.h b/logic/net/DownloadJob.h index adeef646..71466282 100644 --- a/logic/net/DownloadJob.h +++ b/logic/net/DownloadJob.h @@ -1,98 +1,28 @@ #pragma once #include <QtNetwork> +#include "Download.h" +#include "ByteArrayDownload.h" +#include "FileDownload.h" +#include "CacheDownload.h" +#include "HttpMetaCache.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 QObject { 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) + :QObject(), m_job_name(job_name){}; + + ByteArrayDownloadPtr add(QUrl url); + FileDownloadPtr add(QUrl url, QString rel_target_path); + CacheDownloadPtr add(QUrl url, MetaEntryPtr entry); - DownloadPtr add(QUrl url, QString rel_target_path = QString(), QString expected_md5 = QString()); DownloadPtr operator[](int index) { return downloads[index]; @@ -119,6 +49,7 @@ private slots: void partSucceeded(int index); void partFailed(int index); private: + QString m_job_name; QList<DownloadPtr> downloads; int num_succeeded = 0; int num_failed = 0; 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/HttpMetaCache.cpp b/logic/net/HttpMetaCache.cpp new file mode 100644 index 00000000..46801ab3 --- /dev/null +++ b/logic/net/HttpMetaCache.cpp @@ -0,0 +1,231 @@ +#include "MultiMC.h" +#include "HttpMetaCache.h" +#include <pathutils.h> + +#include <QFileInfo> +#include <QFile> +#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() +{ + saveBatchingTimer.stop(); + SaveNow(); +} + +MetaEntryPtr HttpMetaCache::getEntry ( QString base, QString resource_path ) +{ + // no base. no base path. can't store + if(!m_entries.contains(base)) + { + // 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); + + // is the file really there? if not -> stale + if(!finfo.isFile() || !finfo.isReadable()) + { + // 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); + } + + // 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 ) +{ + // TODO: report error + if(m_entries.contains(base)) + return; + // TODO: check if the base path is valid + EntryMap foo; + foo.base_path = 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); + if(!index.open(QIODevice::ReadOnly)) + return; + + QJsonDocument json = QJsonDocument::fromJson(index.readAll()); + if(!json.isObject()) + return; + auto root = json.object(); + // check file version first + auto version_val =root.value("version"); + if(!version_val.isString()) + return; + if(version_val.toString() != "1") + return; + + // read the entry array + auto entries_val =root.value("entries"); + if(!entries_val.isArray()) + return; + QJsonArray array = entries_val.toArray(); + for(auto element: array) + { + if(!element.isObject()) + return; + auto element_obj = element.toObject(); + QString base = element_obj.value("base").toString(); + if(!m_entries.contains(base)) + continue; + auto & entrymap = m_entries[base]; + auto foo = new MetaEntry; + foo->base = base; + QString path = foo->path = element_obj.value("path").toString(); + foo->md5sum = element_obj.value("md5sum").toString(); + foo->etag = element_obj.value("etag").toString(); + foo->last_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + // presumed innocent until closer examination + foo->stale = false; + entrymap.entry_list[path] = MetaEntryPtr( foo ); + } +} + +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)) + return; + QJsonObject toplevel; + toplevel.insert("version",QJsonValue(QString("1"))); + QJsonArray entriesArr; + for(auto group : m_entries) + { + for(auto entry : group.entry_list) + { + QJsonObject entryObj; + entryObj.insert("base", QJsonValue(entry->base)); + entryObj.insert("path", QJsonValue(entry->path)); + entryObj.insert("md5sum", QJsonValue(entry->md5sum)); + entryObj.insert("etag", QJsonValue(entry->etag)); + entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->last_changed_timestamp))); + entriesArr.append(entryObj); + } + } + toplevel.insert("entries",entriesArr); + QJsonDocument doc(toplevel); + QByteArray jsonData = doc.toJson(); + qint64 result = tfile.write(jsonData); + if(result == -1) + return; + if(result != jsonData.size()) + return; + tfile.commit(); +} diff --git a/logic/net/HttpMetaCache.h b/logic/net/HttpMetaCache.h new file mode 100644 index 00000000..fac6bec3 --- /dev/null +++ b/logic/net/HttpMetaCache.h @@ -0,0 +1,57 @@ +#pragma once +#include <QString> +#include <QSharedPointer> +#include <QMap> +#include <qtimer.h> + +struct MetaEntry +{ + QString base; + QString path; + QString md5sum; + QString etag; + qint64 last_changed_timestamp = 0; + bool stale = true; + QString getFullPath(); +}; + +typedef QSharedPointer<MetaEntry> MetaEntryPtr; + +class HttpMetaCache : public QObject +{ + Q_OBJECT +public: + // supply path to the cache index file + HttpMetaCache(QString path); + ~HttpMetaCache(); + + // 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); + + // (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; + QMap<QString, MetaEntryPtr> entry_list; + }; + QMap<QString, EntryMap> m_entries; + QString m_index_file; + QTimer saveBatchingTimer; +};
\ No newline at end of file diff --git a/logic/net/NetWorker.cpp b/logic/net/NetWorker.cpp deleted file mode 100644 index c5943348..00000000 --- a/logic/net/NetWorker.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "NetWorker.h" -#include <QThreadStorage> - -class NetWorker::Private -{ -public: - QNetworkAccessManager manager; -}; - -NetWorker::NetWorker ( QObject* parent ) : QObject ( parent ) -{ - d = new Private(); -} - -QNetworkAccessManager& NetWorker::qnam() -{ - auto & w = worker(); - return w.d->manager; -} - - -NetWorker& NetWorker::worker() -{ - static QThreadStorage<NetWorker *> storage; - if (!storage.hasLocalData()) - { - storage.setLocalData(new NetWorker()); - } - return *storage.localData(); -} diff --git a/logic/net/NetWorker.h b/logic/net/NetWorker.h deleted file mode 100644 index cf7e72e1..00000000 --- a/logic/net/NetWorker.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - _.ooo-._ - .OOOP _ '. - dOOOO (_) \ - OOOOOb | - OOOOOOb. | - OOOOOOOOb | - YOO(_)OOO / - 'OOOOOY _.' - '""""'' -*/ -#pragma once - -#include <QNetworkAccessManager> -#include <QUrl> - -class NetWorker : public QObject -{ - Q_OBJECT -public: - // for high level access to the sevices (preferred) - static NetWorker &worker(); - // for low-level access to the network manager object - static QNetworkAccessManager &qnam(); -public: - -private: - explicit NetWorker ( QObject* parent = 0 ); - class Private; - Private * d; -};
\ No newline at end of file diff --git a/logic/tasks/LoginTask.cpp b/logic/tasks/LoginTask.cpp index 4e2f0fb7..ad9de7f5 100644 --- a/logic/tasks/LoginTask.cpp +++ b/logic/tasks/LoginTask.cpp @@ -14,7 +14,7 @@ */ #include "LoginTask.h" -#include "logic/net/NetWorker.h" +#include "MultiMC.h" #include <QStringList> @@ -29,8 +29,8 @@ LoginTask::LoginTask( const UserInfo& uInfo, QObject* parent ) : Task(parent), u void LoginTask::executeTask() { setStatus("Logging in..."); - auto & worker = NetWorker::qnam(); - connect(&worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); + auto worker = MMC->qnam(); + connect(worker, SIGNAL(finished(QNetworkReply*)), this, SLOT(processNetReply(QNetworkReply*))); QUrl loginURL("https://login.minecraft.net/"); QNetworkRequest netRequest(loginURL); @@ -41,7 +41,7 @@ void LoginTask::executeTask() params.addQueryItem("password", uInfo.password); params.addQueryItem("version", "13"); - netReply = worker.post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); + netReply = worker->post(netRequest, params.query(QUrl::EncodeSpaces).toUtf8()); } void LoginTask::processNetReply(QNetworkReply *reply) diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 12ef1ce7..00000000 --- a/main.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* Copyright 2013 MultiMC Contributors - * - * Authors: Andrew Okin - * Orochimarufan <orochimarufan.x3@gmail.com> - * - * 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 <iostream> - -#include <QApplication> -#include <QDir> - -#include "gui/mainwindow.h" -#include "gui/logindialog.h" -#include "gui/taskdialog.h" -#include "gui/consolewindow.h" - -#include "AppSettings.h" -#include "logic/lists/InstanceList.h" -#include "logic/tasks/LoginTask.h" -#include "logic/MinecraftProcess.h" - -#include "pathutils.h" -#include "cmdutils.h" - -#include "config.h" - -using namespace Util::Commandline; - -// Commandline instance launcher -class InstanceLauncher : public QObject -{ - Q_OBJECT -private: - InstanceList instances; - QString instId; - InstancePtr instance; - MinecraftProcess *proc; - ConsoleWindow *console; -public: - InstanceLauncher(QString instId) : QObject(), instances(globalSettings->get("InstanceDir").toString()) - { - this->instId = instId; - } - -private slots: - void onTerminated() - { - std::cout << "Minecraft exited" << std::endl; - QApplication::instance()->quit(); - } - - void onLoginComplete() - { - LoginTask * task = (LoginTask *) QObject::sender(); - auto result = task->getResult(); - proc = instance->prepareForLaunch(result.username, result.sessionID); - if(!proc) - { - //FIXME: report error - return; - } - console = new ConsoleWindow(proc); - console->show(); - - connect(proc, SIGNAL(ended()), SLOT(onTerminated())); - connect(proc, SIGNAL(log(QString,MessageLevel::Enum)), console, SLOT(write(QString,MessageLevel::Enum))); - - proc->launch(); - } - - void doLogin(const QString &errorMsg) - { - LoginDialog* loginDlg = new LoginDialog(nullptr, errorMsg); - loginDlg->exec(); - if (loginDlg->result() == QDialog::Accepted) - { - UserInfo uInfo{loginDlg->getUsername(), loginDlg->getPassword()}; - - TaskDialog* tDialog = new TaskDialog(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); - tDialog->exec(loginTask); - } - //onLoginComplete(LoginResponse("Offline","Offline", 1)); - } - -public: - int launch() - { - std::cout << "Loading Instances..." << std::endl; - instances.loadList(); - - std::cout << "Launching Instance '" << qPrintable(instId) << "'" << std::endl; - instance = instances.getInstanceById(instId); - if (instance.isNull()) - { - std::cout << "Could not find instance requested. note that you have to specify the ID, not the NAME" << std::endl; - return 1; - } - - std::cout << "Logging in..." << std::endl; - doLogin(""); - - return QApplication::instance()->exec(); - } -}; - -int main(int argc, char *argv[]) -{ - // initialize Qt - QApplication app(argc, argv); - app.setOrganizationName("Forkk"); - app.setApplicationName("MultiMC 5"); - - // Print app header - std::cout << "MultiMC 5" << std::endl; - std::cout << "(c) 2013 MultiMC Contributors" << std::endl << std::endl; - - // Commandline parsing - Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); - - // --help - parser.addSwitch("help"); - parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "display this help and exit."); - // --version - parser.addSwitch("version"); - parser.addShortOpt("version", 'V'); - parser.addDocumentation("version", "display program version and exit."); - // --dir - parser.addOption("dir", app.applicationDirPath()); - parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of the binary location (use '.' for current)"); - // --update - parser.addOption("update"); - parser.addShortOpt("update", 'u'); - parser.addDocumentation("update", "replaces the given file with the running executable", "<path>"); - // --quietupdate - parser.addSwitch("quietupdate"); - parser.addShortOpt("quietupdate", 'U'); - parser.addDocumentation("quietupdate", "doesn't restart MultiMC after installing updates"); - // --launch - parser.addOption("launch"); - parser.addShortOpt("launch", 'l'); - parser.addDocumentation("launch", "tries to launch the given instance", "<inst>"); - - // parse the arguments - QHash<QString, QVariant> args; - try - { - args = parser.parse(app.arguments()); - } - catch(ParsingError e) - { - std::cerr << "CommandLineError: " << e.what() << std::endl; - std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters." << std::endl; - return 1; - } - - // display help and exit - if (args["help"].toBool()) - { - std::cout << qPrintable(parser.compileHelp(app.arguments()[0])); - return 0; - } - - // display version and exit - if (args["version"].toBool()) - { - std::cout << "Version " << VERSION_STR << std::endl; - std::cout << "Git " << GIT_COMMIT << std::endl; - std::cout << "Tag: " << JENKINS_BUILD_TAG << " " << (ARCH==x64?"x86_64":"x86") << std::endl; - return 0; - } - - // update - // Note: cwd is always the current executable path! - if (!args["update"].isNull()) - { - std::cout << "Performing MultiMC update: " << qPrintable(args["update"].toString()) << std::endl; - QString cwd = QDir::currentPath(); - QDir::setCurrent(app.applicationDirPath()); - QFile file(app.applicationFilePath()); - file.copy(args["update"].toString()); - if(args["quietupdate"].toBool()) - return 0; - QDir::setCurrent(cwd); - } - - // change directory - QDir::setCurrent(args["dir"].toString()); - - // load settings - globalSettings = new AppSettings(&app); - - // Register meta types. - qRegisterMetaType<LoginResponse>("LoginResponse"); - - // launch instance. - if (!args["launch"].isNull()) - return InstanceLauncher(args["launch"].toString()).launch(); - - // show main window - MainWindow mainWin; - mainWin.show(); - - // loop - return app.exec(); -} - -#include "main.moc" diff --git a/multimc_pragma.h b/multimc_pragma.h deleted file mode 100644 index 4650e4da..00000000 --- a/multimc_pragma.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -// This is here to keep MSVC from spamming the build output with nonsense -// Call it public domain. - -#ifdef _MSC_VER - // 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' - // C4251 can be ignored in Microsoft Visual C++ 2005 if you are deriving from a type - // in the Standard C++ Library, compiling a debug release (/MTd) and where the compiler - // error message refers to _Container_base. - // Shows up when you export classes that use STL types. Stupid. - // #pragma warning( disable: 4251 ) - - // C4273 - inconsistent DLL linkage. how about none? - #pragma warning( disable: 4273 ) - - // don't display bogus 'deprecation' and 'unsafe' warnings. - // See the idiocy: http://msdn.microsoft.com/en-us/magazine/cc163794.aspx - #define _CRT_SECURE_NO_DEPRECATE - #define _SCL_SECURE_NO_DEPRECATE - // Let me demonstrate: - /** - * [peterix@peterix dfhack]$ man wcscpy_s - * No manual entry for wcscpy_s - * - * Proprietary extensions. - */ - //'function': was declared deprecated - #pragma warning( disable: 4996 ) - - // disable stupid - forcing value to bool 'true' or 'false' (performance warning). - // When I do this, it's intentional. Always. - #pragma warning( disable: 4800 ) - - // disable more stupid - The compiler ignored an unrecognized pragma. GOOD JOB, DON'T SPAM ME WITH THAT - #pragma warning( disable: 4068 ) - - // no signed value outside enum range bs - //#pragma warning( disable: 4341) - - // just shut up already - conversion between types loses precision - //#pragma warning( disable: 4244) - - // signed/unsigned mismatch - //#pragma warning( disable: 4018) - - // nonstandard extension used: enum 'df::whatever::etc' used in qualified name - //#pragma warning( disable: 4482) -#endif |