From 8a3a0f5a529a95c7511436051b63887dff158c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 8 May 2014 21:20:10 +0200 Subject: Reorganize logic code. --- CMakeLists.txt | 107 +++--- MultiMC.cpp | 8 +- gui/MainWindow.cpp | 10 +- gui/MainWindow.h | 5 +- gui/dialogs/CopyInstanceDialog.cpp | 1 - gui/dialogs/InstanceEditDialog.cpp | 4 +- gui/dialogs/InstanceSettings.cpp | 7 +- gui/dialogs/InstanceSettings.h | 2 +- gui/dialogs/LwjglSelectDialog.cpp | 2 +- gui/dialogs/NewInstanceDialog.cpp | 2 +- gui/dialogs/SettingsDialog.cpp | 7 +- gui/dialogs/SettingsDialog.h | 2 +- gui/dialogs/VersionSelectDialog.cpp | 2 +- gui/groupview/InstanceDelegate.cpp | 2 +- logic/BaseInstaller.cpp | 9 +- logic/BaseInstance.cpp | 2 +- logic/BaseInstance.h | 2 +- logic/BaseVersionList.cpp | 121 ++++++ logic/BaseVersionList.h | 120 ++++++ logic/InstanceFactory.cpp | 27 +- logic/InstanceLauncher.cpp | 2 +- logic/InstanceList.cpp | 618 ++++++++++++++++++++++++++++++ logic/InstanceList.h | 152 ++++++++ logic/JavaChecker.cpp | 124 ------ logic/JavaChecker.h | 42 -- logic/JavaCheckerJob.cpp | 47 --- logic/JavaCheckerJob.h | 100 ----- logic/JavaUtils.cpp | 223 ----------- logic/JavaUtils.h | 43 --- logic/LegacyUpdate.cpp | 20 +- logic/LwjglVersionList.cpp | 199 ++++++++++ logic/LwjglVersionList.h | 148 +++++++ logic/MinecraftVersion.h | 92 ----- logic/OneSixFTBInstance.cpp | 6 +- logic/OneSixInstance.cpp | 21 +- logic/OneSixInstance.h | 4 +- logic/OneSixInstance_p.h | 7 +- logic/OneSixLibrary.cpp | 274 ------------- logic/OneSixLibrary.h | 148 ------- logic/OneSixRule.cpp | 89 ----- logic/OneSixRule.h | 98 ----- logic/OneSixUpdate.cpp | 19 +- logic/OneSixVersionBuilder.cpp | 248 ------------ logic/OneSixVersionBuilder.h | 46 --- logic/OpSys.cpp | 42 -- logic/OpSys.h | 37 -- logic/VersionFile.cpp | 635 ------------------------------- logic/VersionFile.h | 145 ------- logic/VersionFinal.cpp | 431 --------------------- logic/VersionFinal.h | 172 --------- logic/forge/ForgeInstaller.cpp | 4 +- logic/forge/ForgeVersionList.h | 2 +- logic/java/JavaChecker.cpp | 124 ++++++ logic/java/JavaChecker.h | 42 ++ logic/java/JavaCheckerJob.cpp | 47 +++ logic/java/JavaCheckerJob.h | 100 +++++ logic/java/JavaUtils.cpp | 221 +++++++++++ logic/java/JavaUtils.h | 43 +++ logic/java/JavaVersionList.cpp | 241 ++++++++++++ logic/java/JavaVersionList.h | 96 +++++ logic/lists/BaseVersionList.cpp | 121 ------ logic/lists/BaseVersionList.h | 120 ------ logic/lists/InstanceList.cpp | 618 ------------------------------ logic/lists/InstanceList.h | 152 -------- logic/lists/JavaVersionList.cpp | 241 ------------ logic/lists/JavaVersionList.h | 96 ----- logic/lists/LwjglVersionList.cpp | 199 ---------- logic/lists/LwjglVersionList.h | 148 ------- logic/lists/MinecraftVersionList.cpp | 330 ---------------- logic/lists/MinecraftVersionList.h | 76 ---- logic/liteloader/LiteLoaderInstaller.cpp | 4 +- logic/liteloader/LiteLoaderVersionList.h | 4 +- logic/minecraft/MinecraftVersion.h | 92 +++++ logic/minecraft/MinecraftVersionList.cpp | 330 ++++++++++++++++ logic/minecraft/MinecraftVersionList.h | 76 ++++ logic/minecraft/OneSixLibrary.cpp | 274 +++++++++++++ logic/minecraft/OneSixLibrary.h | 148 +++++++ logic/minecraft/OneSixRule.cpp | 89 +++++ logic/minecraft/OneSixRule.h | 98 +++++ logic/minecraft/OneSixVersionBuilder.cpp | 249 ++++++++++++ logic/minecraft/OneSixVersionBuilder.h | 46 +++ logic/minecraft/OpSys.cpp | 42 ++ logic/minecraft/OpSys.h | 37 ++ logic/minecraft/VersionFile.cpp | 635 +++++++++++++++++++++++++++++++ logic/minecraft/VersionFile.h | 145 +++++++ logic/minecraft/VersionFinal.cpp | 429 +++++++++++++++++++++ logic/minecraft/VersionFinal.h | 172 +++++++++ 87 files changed, 5282 insertions(+), 5283 deletions(-) create mode 100644 logic/BaseVersionList.cpp create mode 100644 logic/BaseVersionList.h create mode 100644 logic/InstanceList.cpp create mode 100644 logic/InstanceList.h delete mode 100644 logic/JavaChecker.cpp delete mode 100644 logic/JavaChecker.h delete mode 100644 logic/JavaCheckerJob.cpp delete mode 100644 logic/JavaCheckerJob.h delete mode 100644 logic/JavaUtils.cpp delete mode 100644 logic/JavaUtils.h create mode 100644 logic/LwjglVersionList.cpp create mode 100644 logic/LwjglVersionList.h delete mode 100644 logic/MinecraftVersion.h delete mode 100644 logic/OneSixLibrary.cpp delete mode 100644 logic/OneSixLibrary.h delete mode 100644 logic/OneSixRule.cpp delete mode 100644 logic/OneSixRule.h delete mode 100644 logic/OneSixVersionBuilder.cpp delete mode 100644 logic/OneSixVersionBuilder.h delete mode 100644 logic/OpSys.cpp delete mode 100644 logic/OpSys.h delete mode 100644 logic/VersionFile.cpp delete mode 100644 logic/VersionFile.h delete mode 100644 logic/VersionFinal.cpp delete mode 100644 logic/VersionFinal.h create mode 100644 logic/java/JavaChecker.cpp create mode 100644 logic/java/JavaChecker.h create mode 100644 logic/java/JavaCheckerJob.cpp create mode 100644 logic/java/JavaCheckerJob.h create mode 100644 logic/java/JavaUtils.cpp create mode 100644 logic/java/JavaUtils.h create mode 100644 logic/java/JavaVersionList.cpp create mode 100644 logic/java/JavaVersionList.h delete mode 100644 logic/lists/BaseVersionList.cpp delete mode 100644 logic/lists/BaseVersionList.h delete mode 100644 logic/lists/InstanceList.cpp delete mode 100644 logic/lists/InstanceList.h delete mode 100644 logic/lists/JavaVersionList.cpp delete mode 100644 logic/lists/JavaVersionList.h delete mode 100644 logic/lists/LwjglVersionList.cpp delete mode 100644 logic/lists/LwjglVersionList.h delete mode 100644 logic/lists/MinecraftVersionList.cpp delete mode 100644 logic/lists/MinecraftVersionList.h create mode 100644 logic/minecraft/MinecraftVersion.h create mode 100644 logic/minecraft/MinecraftVersionList.cpp create mode 100644 logic/minecraft/MinecraftVersionList.h create mode 100644 logic/minecraft/OneSixLibrary.cpp create mode 100644 logic/minecraft/OneSixLibrary.h create mode 100644 logic/minecraft/OneSixRule.cpp create mode 100644 logic/minecraft/OneSixRule.h create mode 100644 logic/minecraft/OneSixVersionBuilder.cpp create mode 100644 logic/minecraft/OneSixVersionBuilder.h create mode 100644 logic/minecraft/OpSys.cpp create mode 100644 logic/minecraft/OpSys.h create mode 100644 logic/minecraft/VersionFile.cpp create mode 100644 logic/minecraft/VersionFile.h create mode 100644 logic/minecraft/VersionFinal.cpp create mode 100644 logic/minecraft/VersionFinal.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a8686023..4459b92e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -359,17 +359,13 @@ SET(MULTIMC_SOURCES gui/groupview/InstanceDelegate.cpp gui/groupview/InstanceDelegate.h - # Base classes and infrastructure + # LOGIC - Base classes and infrastructure logic/BaseVersion.h - logic/MinecraftVersion.h logic/InstanceFactory.h logic/InstanceFactory.cpp logic/BaseInstance.h logic/BaseInstance.cpp logic/BaseInstance_p.h - - logic/MinecraftProcess.h - logic/MinecraftProcess.cpp logic/Mod.h logic/Mod.cpp logic/ModList.h @@ -379,9 +375,27 @@ SET(MULTIMC_SOURCES logic/VersionFilterData.h logic/VersionFilterData.cpp - # Basic instance launcher for starting from terminal + # Instance launch logic/InstanceLauncher.h logic/InstanceLauncher.cpp + logic/MinecraftProcess.h + logic/MinecraftProcess.cpp + + # URN parser/resolver + logic/URNResolver.cpp + logic/URNResolver.h + + # Annoying nag screen logic + logic/NagUtils.h + logic/NagUtils.cpp + + # Player skin utilities + logic/SkinUtils.h + logic/SkinUtils.cpp + + # misc model filter + logic/EnabledItemFilter.h + logic/EnabledItemFilter.cpp # JSON parsing helpers logic/MMCJson.h @@ -453,22 +467,34 @@ SET(MULTIMC_SOURCES logic/OneSixInstance_p.h # OneSix version json infrastructure - logic/OneSixVersionBuilder.h - logic/OneSixVersionBuilder.cpp - logic/VersionFile.h - logic/VersionFile.cpp - logic/VersionFinal.h - logic/VersionFinal.cpp - logic/OneSixLibrary.h - logic/OneSixLibrary.cpp - logic/OneSixRule.h - logic/OneSixRule.cpp - logic/OpSys.h - logic/OpSys.cpp - - # Mod installers + logic/minecraft/MinecraftVersion.h + logic/minecraft/OneSixVersionBuilder.h + logic/minecraft/OneSixVersionBuilder.cpp + logic/minecraft/VersionFile.h + logic/minecraft/VersionFile.cpp + logic/minecraft/VersionFinal.h + logic/minecraft/VersionFinal.cpp + logic/minecraft/OneSixLibrary.h + logic/minecraft/OneSixLibrary.cpp + logic/minecraft/OneSixRule.h + logic/minecraft/OneSixRule.cpp + logic/minecraft/MinecraftVersionList.h + logic/minecraft/MinecraftVersionList.cpp + + # Trivial operating system utilities + logic/minecraft/OpSys.h + logic/minecraft/OpSys.cpp + + # Various base classes logic/BaseInstaller.h logic/BaseInstaller.cpp + logic/BaseVersionList.h + logic/BaseVersionList.cpp + + logic/InstanceList.h + logic/InstanceList.cpp + logic/LwjglVersionList.h + logic/LwjglVersionList.cpp # FTB logic/OneSixFTBInstance.h @@ -476,18 +502,6 @@ SET(MULTIMC_SOURCES logic/LegacyFTBInstance.h logic/LegacyFTBInstance.cpp - # Lists - logic/lists/InstanceList.h - logic/lists/InstanceList.cpp - logic/lists/BaseVersionList.h - logic/lists/BaseVersionList.cpp - logic/lists/MinecraftVersionList.h - logic/lists/MinecraftVersionList.cpp - logic/lists/LwjglVersionList.h - logic/lists/LwjglVersionList.cpp - logic/lists/JavaVersionList.h - logic/lists/JavaVersionList.cpp - # the screenshots feature logic/screenshots/Screenshot.h logic/screenshots/Screenshot.cpp @@ -504,11 +518,6 @@ SET(MULTIMC_SOURCES logic/icons/IconList.h logic/icons/IconList.cpp - - # misc model/view - logic/EnabledItemFilter.h - logic/EnabledItemFilter.cpp - # Tasks logic/tasks/ProgressProvider.h logic/tasks/Task.h @@ -518,17 +527,15 @@ SET(MULTIMC_SOURCES logic/tasks/SequentialTask.h logic/tasks/SequentialTask.cpp - # Utilities - logic/JavaChecker.h - logic/JavaChecker.cpp - logic/JavaUtils.h - logic/JavaUtils.cpp - logic/NagUtils.h - logic/NagUtils.cpp - logic/SkinUtils.h - logic/SkinUtils.cpp - logic/JavaCheckerJob.h - logic/JavaCheckerJob.cpp + # Java related code + logic/java/JavaChecker.h + logic/java/JavaChecker.cpp + logic/java/JavaUtils.h + logic/java/JavaUtils.cpp + logic/java/JavaVersionList.h + logic/java/JavaVersionList.cpp + logic/java/JavaCheckerJob.h + logic/java/JavaCheckerJob.cpp # Assets logic/assets/AssetsMigrateTask.h @@ -568,10 +575,6 @@ SET(MULTIMC_SOURCES logic/liteloader/LiteLoaderInstaller.cpp logic/liteloader/LiteLoaderVersionList.h logic/liteloader/LiteLoaderVersionList.cpp - - # MultiMC URN resolver - logic/URNResolver.cpp - logic/URNResolver.h ) diff --git a/MultiMC.cpp b/MultiMC.cpp index 6b5545c1..073646ec 100644 --- a/MultiMC.cpp +++ b/MultiMC.cpp @@ -12,11 +12,11 @@ #include #include "gui/dialogs/VersionSelectDialog.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/auth/MojangAccountList.h" #include "logic/icons/IconList.h" -#include "logic/lists/LwjglVersionList.h" -#include "logic/lists/MinecraftVersionList.h" +#include "logic/LwjglVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/liteloader/LiteLoaderVersionList.h" #include "logic/forge/ForgeVersionList.h" @@ -29,7 +29,7 @@ #include "logic/net/HttpMetaCache.h" #include "logic/net/URLConstants.h" -#include "logic/JavaUtils.h" +#include "logic/java/JavaUtils.h" #include "logic/updater/UpdateChecker.h" #include "logic/updater/NotificationChecker.h" diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 95f2ac4c..679938f3 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -69,11 +69,11 @@ #include "gui/ConsoleWindow.h" -#include "logic/lists/InstanceList.h" -#include "logic/lists/MinecraftVersionList.h" -#include "logic/lists/LwjglVersionList.h" +#include "logic/InstanceList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/LwjglVersionList.h" #include "logic/icons/IconList.h" -#include "logic/lists/JavaVersionList.h" +#include "logic/java/JavaVersionList.h" #include "logic/auth/flows/AuthenticateTask.h" #include "logic/auth/flows/RefreshTask.h" @@ -91,7 +91,7 @@ #include "logic/InstanceFactory.h" #include "logic/MinecraftProcess.h" #include "logic/OneSixUpdate.h" -#include "logic/JavaUtils.h" +#include "logic/java/JavaUtils.h" #include "logic/NagUtils.h" #include "logic/SkinUtils.h" diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 0cd2e84b..cc8b7c99 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -19,11 +19,10 @@ #include #include -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" #include "logic/BaseInstance.h" - #include "logic/auth/MojangAccount.h" -#include +#include "logic/net/NetJob.h" class QToolButton; class LabeledToolButton; diff --git a/gui/dialogs/CopyInstanceDialog.cpp b/gui/dialogs/CopyInstanceDialog.cpp index 71429367..188cf274 100644 --- a/gui/dialogs/CopyInstanceDialog.cpp +++ b/gui/dialogs/CopyInstanceDialog.cpp @@ -28,7 +28,6 @@ #include "logic/InstanceFactory.h" #include "logic/BaseVersion.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" #include "logic/tasks/Task.h" #include "logic/BaseInstance.h" diff --git a/gui/dialogs/InstanceEditDialog.cpp b/gui/dialogs/InstanceEditDialog.cpp index a74f3dcb..fc2d6a75 100644 --- a/gui/dialogs/InstanceEditDialog.cpp +++ b/gui/dialogs/InstanceEditDialog.cpp @@ -34,13 +34,13 @@ #include "InstanceSettings.h" #include "logic/ModList.h" -#include "logic/VersionFinal.h" +#include "logic/minecraft/VersionFinal.h" #include "logic/EnabledItemFilter.h" #include "logic/forge/ForgeVersionList.h" #include "logic/forge/ForgeInstaller.h" #include "logic/liteloader/LiteLoaderVersionList.h" #include "logic/liteloader/LiteLoaderInstaller.h" -#include "logic/OneSixVersionBuilder.h" +#include "logic/minecraft/OneSixVersionBuilder.h" #include "logic/auth/MojangAccountList.h" #include diff --git a/gui/dialogs/InstanceSettings.cpp b/gui/dialogs/InstanceSettings.cpp index edb4a921..9f0cae3b 100644 --- a/gui/dialogs/InstanceSettings.cpp +++ b/gui/dialogs/InstanceSettings.cpp @@ -23,10 +23,11 @@ #include "gui/Platform.h" #include "gui/dialogs/VersionSelectDialog.h" -#include "logic/JavaUtils.h" #include "logic/NagUtils.h" -#include "logic/lists/JavaVersionList.h" -#include "logic/JavaChecker.h" + +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaChecker.h" #include #include diff --git a/gui/dialogs/InstanceSettings.h b/gui/dialogs/InstanceSettings.h index e296db4c..6895589f 100644 --- a/gui/dialogs/InstanceSettings.h +++ b/gui/dialogs/InstanceSettings.h @@ -17,7 +17,7 @@ #include #include "settingsobject.h" -#include "logic/JavaChecker.h" +#include "logic/java/JavaChecker.h" namespace Ui { diff --git a/gui/dialogs/LwjglSelectDialog.cpp b/gui/dialogs/LwjglSelectDialog.cpp index 046a4e2e..e64228b2 100644 --- a/gui/dialogs/LwjglSelectDialog.cpp +++ b/gui/dialogs/LwjglSelectDialog.cpp @@ -18,7 +18,7 @@ #include "ui_LwjglSelectDialog.h" #include "gui/Platform.h" -#include "logic/lists/LwjglVersionList.h" +#include "logic/LwjglVersionList.h" LWJGLSelectDialog::LWJGLSelectDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LWJGLSelectDialog) diff --git a/gui/dialogs/NewInstanceDialog.cpp b/gui/dialogs/NewInstanceDialog.cpp index c7b273af..3192a1cd 100644 --- a/gui/dialogs/NewInstanceDialog.cpp +++ b/gui/dialogs/NewInstanceDialog.cpp @@ -20,7 +20,7 @@ #include "logic/InstanceFactory.h" #include "logic/BaseVersion.h" #include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/tasks/Task.h" #include "gui/Platform.h" diff --git a/gui/dialogs/SettingsDialog.cpp b/gui/dialogs/SettingsDialog.cpp index 2dd19077..4c69c785 100644 --- a/gui/dialogs/SettingsDialog.cpp +++ b/gui/dialogs/SettingsDialog.cpp @@ -22,10 +22,11 @@ #include "gui/dialogs/VersionSelectDialog.h" #include "gui/dialogs/CustomMessageBox.h" -#include "logic/JavaUtils.h" #include "logic/NagUtils.h" -#include "logic/lists/JavaVersionList.h" -#include + +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaChecker.h" #include "logic/updater/UpdateChecker.h" diff --git a/gui/dialogs/SettingsDialog.h b/gui/dialogs/SettingsDialog.h index d8495fdd..c65e9fb5 100644 --- a/gui/dialogs/SettingsDialog.h +++ b/gui/dialogs/SettingsDialog.h @@ -18,7 +18,7 @@ #include #include -#include "logic/JavaChecker.h" +#include "logic/java/JavaChecker.h" class SettingsObject; diff --git a/gui/dialogs/VersionSelectDialog.cpp b/gui/dialogs/VersionSelectDialog.cpp index cae5a732..fd8b569d 100644 --- a/gui/dialogs/VersionSelectDialog.cpp +++ b/gui/dialogs/VersionSelectDialog.cpp @@ -24,7 +24,7 @@ #include "gui/Platform.h" #include -#include +#include #include VersionSelectDialog::VersionSelectDialog(BaseVersionList *vlist, QString title, QWidget *parent, diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp index cd26ddaa..64dc31d2 100644 --- a/gui/groupview/InstanceDelegate.cpp +++ b/gui/groupview/InstanceDelegate.cpp @@ -22,7 +22,7 @@ #include "GroupView.h" #include "logic/BaseInstance.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" QCache ListViewDelegate::m_pixmapCache; diff --git a/logic/BaseInstaller.cpp b/logic/BaseInstaller.cpp index 669fd0ac..5660eb07 100644 --- a/logic/BaseInstaller.cpp +++ b/logic/BaseInstaller.cpp @@ -13,15 +13,10 @@ * limitations under the License. */ -#include "BaseInstaller.h" - #include -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" - -#include "cmdutils.h" +#include "logic/BaseInstaller.h" +#include "logic/OneSixInstance.h" BaseInstaller::BaseInstaller() { diff --git a/logic/BaseInstance.cpp b/logic/BaseInstance.cpp index 5fa62593..59aada40 100644 --- a/logic/BaseInstance.cpp +++ b/logic/BaseInstance.cpp @@ -27,7 +27,7 @@ #include "pathutils.h" #include -#include "lists/MinecraftVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" #include "logic/icons/IconList.h" BaseInstance::BaseInstance(BaseInstancePrivate *d_in, const QString &rootDir, diff --git a/logic/BaseInstance.h b/logic/BaseInstance.h index 41ceda39..d272dc71 100644 --- a/logic/BaseInstance.h +++ b/logic/BaseInstance.h @@ -22,7 +22,7 @@ #include #include "inifile.h" -#include "lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/auth/MojangAccount.h" class QDialog; diff --git a/logic/BaseVersionList.cpp b/logic/BaseVersionList.cpp new file mode 100644 index 00000000..b34750b5 --- /dev/null +++ b/logic/BaseVersionList.cpp @@ -0,0 +1,121 @@ +/* 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 "logic/BaseVersionList.h" +#include "logic/BaseVersion.h" + +BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) +{ +} + +BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) +{ + for (int i = 0; i < count(); i++) + { + if (at(i)->descriptor() == descriptor) + return at(i); + } + return BaseVersionPtr(); +} + +BaseVersionPtr BaseVersionList::getLatestStable() const +{ + if (count() <= 0) + return BaseVersionPtr(); + else + return at(0); +} + +QVariant BaseVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + BaseVersionPtr version = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return version->name(); + + case TypeColumn: + return version->typeString(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(version); + + default: + return QVariant(); + } +} + +QVariant BaseVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case NameColumn: + return "Name"; + + case TypeColumn: + return "Type"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return "The name of the version."; + + case TypeColumn: + return "The version's type."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +int BaseVersionList::rowCount(const QModelIndex &parent) const +{ + // Return count + return count(); +} + +int BaseVersionList::columnCount(const QModelIndex &parent) const +{ + return 2; +} diff --git a/logic/BaseVersionList.h b/logic/BaseVersionList.h new file mode 100644 index 00000000..21b44e8d --- /dev/null +++ b/logic/BaseVersionList.h @@ -0,0 +1,120 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "logic/BaseVersion.h" + +class Task; + +/*! + * \brief Class that each instance type's version list derives from. + * Version lists are the lists that keep track of the available game versions + * for that instance. This list will not be loaded on startup. It will be loaded + * when the list's load function is called. Before using the version list, you + * should check to see if it has been loaded yet and if not, load the list. + * + * Note that this class also inherits from QAbstractListModel. Methods from that + * class determine how this version list shows up in a list view. Said methods + * all have a default implementation, but they can be overridden by plugins to + * change the behavior of the list. + */ +class BaseVersionList : public QAbstractListModel +{ + Q_OBJECT +public: + enum ModelRoles + { + VersionPointerRole = 0x34B1CB48 + }; + + enum VListColumns + { + // First column - Name + NameColumn = 0, + + // Second column - Type + TypeColumn, + + // Third column - Timestamp + TimeColumn + }; + + explicit BaseVersionList(QObject *parent = 0); + + /*! + * \brief Gets a task that will reload the version list. + * Simply execute the task to load the list. + * The task returned by this function should reset the model when it's done. + * \return A pointer to a task that reloads the version list. + */ + virtual Task *getLoadTask() = 0; + + //! Checks whether or not the list is loaded. If this returns false, the list should be + //loaded. + virtual bool isLoaded() = 0; + + //! Gets the version at the given index. + virtual const BaseVersionPtr at(int i) const = 0; + + //! Returns the number of versions in the list. + virtual int count() const = 0; + + //////// List Model Functions //////// + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent) const; + virtual int columnCount(const QModelIndex &parent) const; + + /*! + * \brief Finds a version by its descriptor. + * \param The descriptor of the version to find. + * \return A const pointer to the version with the given descriptor. NULL if + * one doesn't exist. + */ + virtual 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 BaseVersionPtr getLatestStable() const; + + /*! + * Sorts the version list. + */ + virtual void sort() = 0; + +protected +slots: + /*! + * Updates this list with the given list of versions. + * This is done by copying each version in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the versions are set to this + * version list. This can't be done in the load task, because the versions the load + * task creates are on the load task's thread and Qt won't allow their parents + * to be set to something created on another thread. + * To get around that problem, we invoke this method on the GUI thread, which + * then copies the versions and sets their parents correctly. + * \param versions List of versions whose parents should be set. + */ + virtual void updateListData(QList versions) = 0; +}; diff --git a/logic/InstanceFactory.cpp b/logic/InstanceFactory.cpp index 955a3f5b..c0a392e0 100644 --- a/logic/InstanceFactory.cpp +++ b/logic/InstanceFactory.cpp @@ -13,27 +13,26 @@ * limitations under the License. */ -#include "InstanceFactory.h" - #include #include - -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "LegacyFTBInstance.h" -#include "OneSixInstance.h" -#include "OneSixFTBInstance.h" -#include "OneSixInstance.h" -#include "BaseVersion.h" -#include "MinecraftVersion.h" - -#include "inifile.h" +#include #include #include -#include "pathutils.h" +#include #include "logger/QsLog.h" +#include "logic/InstanceFactory.h" + +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "logic/LegacyFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/OneSixFTBInstance.h" +#include "logic/OneSixInstance.h" +#include "logic/BaseVersion.h" +#include "logic/minecraft/MinecraftVersion.h" + InstanceFactory InstanceFactory::loader; InstanceFactory::InstanceFactory() : QObject(NULL) diff --git a/logic/InstanceLauncher.cpp b/logic/InstanceLauncher.cpp index c0079d80..9170c87f 100644 --- a/logic/InstanceLauncher.cpp +++ b/logic/InstanceLauncher.cpp @@ -22,7 +22,7 @@ #include "gui/dialogs/ProgressDialog.h" #include "logic/MinecraftProcess.h" -#include "logic/lists/InstanceList.h" +#include "logic/InstanceList.h" InstanceLauncher::InstanceLauncher(QString instId) : QObject(), instId(instId) { diff --git a/logic/InstanceList.cpp b/logic/InstanceList.cpp new file mode 100644 index 00000000..7b9c8b2b --- /dev/null +++ b/logic/InstanceList.cpp @@ -0,0 +1,618 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MultiMC.h" +#include "logic/InstanceList.h" +#include "logic/icons/IconList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/BaseInstance.h" +#include "logic/InstanceFactory.h" +#include "logger/QsLog.h" +#include "gui/groupview/GroupView.h" + +const static int GROUP_FILE_FORMAT_VERSION = 1; + +InstanceList::InstanceList(const QString &instDir, QObject *parent) + : QAbstractListModel(parent), m_instDir(instDir) +{ + connect(MMC, &MultiMC::aboutToQuit, this, &InstanceList::saveGroupList); + + if (!QDir::current().exists(m_instDir)) + { + QDir::current().mkpath(m_instDir); + } + + /* + * FIXME HACK: instances sometimes need to be created at launch. They need the versions for + * that. + * + * Remove this. it has no business of reloading the whole list. The instances which + * need it should track such events themselves and CHANGE THEIR DATA ONLY! + */ + connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this, + &InstanceList::loadList); +} + +InstanceList::~InstanceList() +{ +} + +int InstanceList::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_instances.count(); +} + +QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (row < 0 || row >= m_instances.size()) + return QModelIndex(); + return createIndex(row, column, (void *)m_instances.at(row).get()); +} + +QVariant InstanceList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + BaseInstance *pdata = static_cast(index.internalPointer()); + switch (role) + { + case InstancePointerRole: + { + QVariant v = qVariantFromValue((void *)pdata); + return v; + } + case InstanceIDRole: + { + return pdata->id(); + } + case Qt::DisplayRole: + { + return pdata->name(); + } + case Qt::ToolTipRole: + { + return pdata->instanceRoot(); + } + case Qt::DecorationRole: + { + QString key = pdata->iconKey(); + return MMC->icons()->getIcon(key); + } + // for now. + case GroupViewRoles::GroupRole: + { + return pdata->group(); + } + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const +{ + Qt::ItemFlags f; + if (index.isValid()) + { + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); + } + return f; +} + +void InstanceList::groupChanged() +{ + // save the groups. save all of them. + saveGroupList(); +} + +QStringList InstanceList::getGroups() +{ + return m_groups.toList(); +} + +void InstanceList::saveGroupList() +{ + QString groupFileName = m_instDir + "/instgroups.json"; + QFile groupFile(groupFileName); + + // if you can't open the file, fail + if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + // An error occurred. Ignore it. + QLOG_ERROR() << "Failed to save instance group file."; + return; + } + QTextStream out(&groupFile); + QMap> groupMap; + for (auto instance : m_instances) + { + QString id = instance->id(); + QString group = instance->group(); + if (group.isEmpty()) + continue; + + // keep a list/set of groups for choosing + m_groups.insert(group); + + if (!groupMap.count(group)) + { + QSet set; + set.insert(id); + groupMap[group] = set; + } + else + { + QSet &set = groupMap[group]; + set.insert(id); + } + } + QJsonObject toplevel; + toplevel.insert("formatVersion", QJsonValue(QString("1"))); + QJsonObject groupsArr; + for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++) + { + auto list = iter.value(); + auto name = iter.key(); + QJsonObject groupObj; + QJsonArray instanceArr; + groupObj.insert("hidden", QJsonValue(QString("false"))); + for (auto item : list) + { + instanceArr.append(QJsonValue(item)); + } + groupObj.insert("instances", instanceArr); + groupsArr.insert(name, groupObj); + } + toplevel.insert("groups", groupsArr); + QJsonDocument doc(toplevel); + groupFile.write(doc.toJson()); + groupFile.close(); +} + +void InstanceList::loadGroupList(QMap &groupMap) +{ + QString groupFileName = m_instDir + "/instgroups.json"; + + // if there's no group file, fail + if (!QFileInfo(groupFileName).exists()) + return; + + QFile groupFile(groupFileName); + + // if you can't open the file, fail + if (!groupFile.open(QIODevice::ReadOnly)) + { + // An error occurred. Ignore it. + QLOG_ERROR() << "Failed to read instance group file."; + return; + } + + QTextStream in(&groupFile); + QString jsonStr = in.readAll(); + groupFile.close(); + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error); + + // if the json was bad, fail + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << QString("Failed to parse instance group file: %1 at offset %2") + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); + return; + } + + // if the root of the json wasn't an object, fail + if (!jsonDoc.isObject()) + { + QLOG_WARN() << "Invalid group file. Root entry should be an object."; + return; + } + + QJsonObject rootObj = jsonDoc.object(); + + // Make sure the format version matches, otherwise fail. + if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) + return; + + // Get the groups. if it's not an object, fail + if (!rootObj.value("groups").isObject()) + { + QLOG_WARN() << "Invalid group list JSON: 'groups' should be an object."; + return; + } + + // Iterate through all the groups. + QJsonObject groupMapping = rootObj.value("groups").toObject(); + for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) + { + QString groupName = iter.key(); + + // If not an object, complain and skip to the next one. + if (!iter.value().isObject()) + { + QLOG_WARN() << QString("Group '%1' in the group list should " + "be an object.") + .arg(groupName) + .toUtf8(); + continue; + } + + QJsonObject groupObj = iter.value().toObject(); + if (!groupObj.value("instances").isArray()) + { + QLOG_WARN() << QString("Group '%1' in the group list is invalid. " + "It should contain an array " + "called 'instances'.") + .arg(groupName) + .toUtf8(); + continue; + } + + // keep a list/set of groups for choosing + m_groups.insert(groupName); + + // Iterate through the list of instances in the group. + QJsonArray instancesArray = groupObj.value("instances").toArray(); + + for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); + iter2++) + { + groupMap[(*iter2).toString()] = groupName; + } + } +} + +QList InstanceList::discoverFTBInstances() +{ + QList records; + QDir dir = QDir(MMC->settings()->get("FTBLauncherDataRoot").toString()); + QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString()); + if (!dataDir.exists()) + { + QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; + return records; + } + else if (!dir.exists()) + { + QLOG_INFO() << "The FTB launcher data directory specified does not exist. Please check your settings"; + return records; + } + dir.cd("ModPacks"); + auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); + for (auto filename : allFiles) + { + if (!filename.endsWith(".xml")) + continue; + auto fpath = dir.absoluteFilePath(filename); + QFile f(fpath); + QLOG_INFO() << "Discovering FTB instances -- " << fpath; + if (!f.open(QFile::ReadOnly)) + continue; + + // read the FTB packs XML. + QXmlStreamReader reader(&f); + while (!reader.atEnd()) + { + switch (reader.readNext()) + { + case QXmlStreamReader::StartElement: + { + if (reader.name() == "modpack") + { + QXmlStreamAttributes attrs = reader.attributes(); + FTBRecord record; + record.dirName = attrs.value("dir").toString(); + record.instanceDir = dataDir.absoluteFilePath(record.dirName); + record.templateDir = dir.absoluteFilePath(record.dirName); + QDir test(record.instanceDir); + QLOG_DEBUG() << dataDir.absolutePath() << record.instanceDir << record.dirName; + if (!test.exists()) + continue; + record.name = attrs.value("name").toString(); + record.logo = attrs.value("logo").toString(); + record.mcVersion = attrs.value("mcVersion").toString(); + record.description = attrs.value("description").toString(); + records.append(record); + } + break; + } + case QXmlStreamReader::EndElement: + break; + case QXmlStreamReader::Characters: + break; + default: + break; + } + } + f.close(); + } + return records; +} + +void InstanceList::loadFTBInstances(QMap &groupMap, + QList &tempList) +{ + auto records = discoverFTBInstances(); + if (!records.size()) + { + QLOG_INFO() << "No FTB instances to load."; + return; + } + QLOG_INFO() << "Loading FTB instances! -- got " << records.size(); + // process the records we acquired. + for (auto record : records) + { + QLOG_INFO() << "Loading FTB instance from " << record.instanceDir; + QString iconKey = record.logo; + iconKey.remove(QRegularExpression("\\..*")); + MMC->icons()->addIcon(iconKey, iconKey, PathCombine(record.templateDir, record.logo), + MMCIcon::Transient); + + if (!QFileInfo(PathCombine(record.instanceDir, "instance.cfg")).exists()) + { + QLOG_INFO() << "Converting " << record.name << " as new."; + InstancePtr instPtr; + auto &factory = InstanceFactory::get(); + auto version = MMC->minecraftlist()->findVersion(record.mcVersion); + if (!version) + { + QLOG_ERROR() << "Can't load instance " << record.instanceDir + << " because minecraft version " << record.mcVersion + << " can't be resolved."; + continue; + } + auto error = factory.createInstance(instPtr, version, record.instanceDir, + InstanceFactory::FTBInstance); + + if (!instPtr || error != InstanceFactory::NoCreateError) + continue; + + instPtr->setGroupInitial("FTB"); + instPtr->setName(record.name); + instPtr->setIconKey(iconKey); + instPtr->setIntendedVersionId(record.mcVersion); + instPtr->setNotes(record.description); + if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) + continue; + tempList.append(InstancePtr(instPtr)); + } + else + { + QLOG_INFO() << "Loading existing " << record.name; + InstancePtr instPtr; + auto error = InstanceFactory::get().loadInstance(instPtr, record.instanceDir); + if (!instPtr || error != InstanceFactory::NoLoadError) + continue; + instPtr->setGroupInitial("FTB"); + instPtr->setName(record.name); + instPtr->setIconKey(iconKey); + if (instPtr->intendedVersionId() != record.mcVersion) + instPtr->setIntendedVersionId(record.mcVersion); + instPtr->setNotes(record.description); + if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) + continue; + tempList.append(InstancePtr(instPtr)); + } + } +} + +InstanceList::InstListError InstanceList::loadList() +{ + // load the instance groups + QMap groupMap; + loadGroupList(groupMap); + + QList tempList; + { + QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, + QDirIterator::FollowSymlinks); + while (iter.hasNext()) + { + QString subDir = iter.next(); + if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) + continue; + QLOG_INFO() << "Loading MultiMC instance from " << subDir; + InstancePtr instPtr; + auto error = InstanceFactory::get().loadInstance(instPtr, subDir); + if(!continueProcessInstance(instPtr, error, subDir, groupMap)) + continue; + tempList.append(instPtr); + } + } + + if (MMC->settings()->get("TrackFTBInstances").toBool()) + { + loadFTBInstances(groupMap, tempList); + } + beginResetModel(); + m_instances.clear(); + for(auto inst: tempList) + { + inst->setParent(this); + connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this, + SLOT(instanceNuked(BaseInstance *))); + m_instances.append(inst); + } + endResetModel(); + emit dataIsInvalid(); + return NoError; +} + +/// Clear all instances. Triggers notifications. +void InstanceList::clear() +{ + beginResetModel(); + saveGroupList(); + m_instances.clear(); + endResetModel(); + emit dataIsInvalid(); +} + +void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) +{ + m_instDir = value.toString(); + loadList(); +} + +/// Add an instance. Triggers notifications, returns the new index +int InstanceList::add(InstancePtr t) +{ + beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size()); + m_instances.append(t); + t->setParent(this); + connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, + SLOT(propertiesChanged(BaseInstance *))); + connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); + connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *))); + endInsertRows(); + return count() - 1; +} + +InstancePtr InstanceList::getInstanceById(QString instId) const +{ + if (m_instances.isEmpty()) + { + return InstancePtr(); + } + + QListIterator iter(m_instances); + InstancePtr inst; + while (iter.hasNext()) + { + inst = iter.next(); + if (inst->id() == instId) + break; + } + if (inst->id() != instId) + return InstancePtr(); + else + return iter.peekPrevious(); +} + +QModelIndex InstanceList::getInstanceIndexById(const QString &id) const +{ + return index(getInstIndex(getInstanceById(id).get())); +} + +int InstanceList::getInstIndex(BaseInstance *inst) const +{ + for (int i = 0; i < m_instances.count(); i++) + { + if (inst == m_instances[i].get()) + { + return i; + } + } + return -1; +} + +bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error, + const QDir &dir, QMap &groupMap) +{ + if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) + { + QString errorMsg = QString("Failed to load instance %1: ") + .arg(QFileInfo(dir.absolutePath()).baseName()) + .toUtf8(); + + switch (error) + { + default: + errorMsg += QString("Unknown instance loader error %1").arg(error); + break; + } + QLOG_ERROR() << errorMsg.toUtf8(); + return false; + } + else if (!instPtr) + { + QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.") + .arg(QFileInfo(dir.absolutePath()).baseName()) + .toUtf8(); + return false; + } + else + { + auto iter = groupMap.find(instPtr->id()); + if (iter != groupMap.end()) + { + instPtr->setGroupInitial((*iter)); + } + QLOG_INFO() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath(); + return true; + } +} + +void InstanceList::instanceNuked(BaseInstance *inst) +{ + int i = getInstIndex(inst); + if (i != -1) + { + beginRemoveRows(QModelIndex(), i, i); + m_instances.removeAt(i); + endRemoveRows(); + } +} + +void InstanceList::propertiesChanged(BaseInstance *inst) +{ + int i = getInstIndex(inst); + if (i != -1) + { + emit dataChanged(index(i), index(i)); + } +} + +InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(parent) +{ +} + +bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + BaseInstance *pdataLeft = static_cast(left.internalPointer()); + BaseInstance *pdataRight = static_cast(right.internalPointer()); + QString sortMode = MMC->settings()->get("InstSortMode").toString(); + if (sortMode == "LastLaunch") + { + return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); + } + else + { + return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; + } +} diff --git a/logic/InstanceList.h b/logic/InstanceList.h new file mode 100644 index 00000000..f0bbb7ec --- /dev/null +++ b/logic/InstanceList.h @@ -0,0 +1,152 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "logic/BaseInstance.h" + +class BaseInstance; + +class QDir; + +struct FTBRecord +{ + QString dirName; + QString name; + QString logo; + QString mcVersion; + QString description; + QString instanceDir; + QString templateDir; +}; + +class InstanceList : public QAbstractListModel +{ + Q_OBJECT +private: + void loadGroupList(QMap &groupList); + QList discoverFTBInstances(); + void loadFTBInstances(QMap &groupMap, QList & tempList); + +private +slots: + void saveGroupList(); + +public: + explicit InstanceList(const QString &instDir, QObject *parent = 0); + virtual ~InstanceList(); + +public: + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + enum AdditionalRoles + { + InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance + InstanceIDRole = 0x34B1CB49 ///< Return id if the instance + }; + /*! + * \brief Error codes returned by functions in the InstanceList class. + * NoError Indicates that no error occurred. + * UnknownError indicates that an unspecified error occurred. + */ + enum InstListError + { + NoError = 0, + UnknownError + }; + + QString instDir() const + { + return m_instDir; + } + + /*! + * \brief Get the instance at index + */ + InstancePtr at(int i) const + { + return m_instances.at(i); + } + ; + + /*! + * \brief Get the count of loaded instances + */ + int count() const + { + return m_instances.count(); + } + ; + + /// Clear all instances. Triggers notifications. + void clear(); + + /// Add an instance. Triggers notifications, returns the new index + int add(InstancePtr t); + + /// Get an instance by ID + InstancePtr getInstanceById(QString id) const; + + QModelIndex getInstanceIndexById(const QString &id) const; + + // FIXME: instead of iterating through all instances and forming a set, keep the set around + QStringList getGroups(); +signals: + void dataIsInvalid(); + +public +slots: + void on_InstFolderChanged(const Setting &setting, QVariant value); + + /*! + * \brief Loads the instance list. Triggers notifications. + */ + InstListError loadList(); + +private +slots: + void propertiesChanged(BaseInstance *inst); + void instanceNuked(BaseInstance *inst); + void groupChanged(); + +private: + int getInstIndex(BaseInstance *inst) const; + + bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir, + QMap &groupMap); + +protected: + QString m_instDir; + QList m_instances; + QSet m_groups; +}; + +class InstanceProxyModel : public GroupedProxyModel +{ +public: + explicit InstanceProxyModel(QObject *parent = 0); + +protected: + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/logic/JavaChecker.cpp b/logic/JavaChecker.cpp deleted file mode 100644 index b87ee3d5..00000000 --- a/logic/JavaChecker.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "JavaChecker.h" -#include "MultiMC.h" -#include -#include -#include -#include -#include - -JavaChecker::JavaChecker(QObject *parent) : QObject(parent) -{ -} - -void JavaChecker::performCheck() -{ - QString checkerJar = PathCombine(MMC->bin(), "jars", "JavaCheck.jar"); - - QStringList args = {"-jar", checkerJar}; - - process.reset(new QProcess()); - process->setArguments(args); - process->setProgram(path); - process->setProcessChannelMode(QProcess::SeparateChannels); - QLOG_DEBUG() << "Running java checker!"; - QLOG_DEBUG() << "Java: " + path; - QLOG_DEBUG() << "Args: {" + args.join("|") + "}"; - - connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(finished(int, QProcess::ExitStatus))); - connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, - SLOT(error(QProcess::ProcessError))); - connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); - killTimer.setSingleShot(true); - killTimer.start(5000); - process->start(); -} - -void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) -{ - killTimer.stop(); - QProcessPtr _process; - _process.swap(process); - - JavaCheckResult result; - { - result.path = path; - result.id = id; - } - QLOG_DEBUG() << "Java checker finished with status " << status << " exit code " << exitcode; - - if (status == QProcess::CrashExit || exitcode == 1) - { - QLOG_DEBUG() << "Java checker failed!"; - emit checkFinished(result); - return; - } - - bool success = true; - QString p_stdout = _process->readAllStandardOutput(); - QLOG_DEBUG() << p_stdout; - - QMap results; - QStringList lines = p_stdout.split("\n", QString::SkipEmptyParts); - for(QString line : lines) - { - line = line.trimmed(); - - auto parts = line.split('=', QString::SkipEmptyParts); - if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) - { - success = false; - } - else - { - results.insert(parts[0], parts[1]); - } - } - - if(!results.contains("os.arch") || !results.contains("java.version") || !success) - { - QLOG_DEBUG() << "Java checker failed - couldn't extract required information."; - emit checkFinished(result); - return; - } - - auto os_arch = results["os.arch"]; - auto java_version = results["java.version"]; - bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; - - - result.valid = true; - result.is_64bit = is_64; - result.mojangPlatform = is_64 ? "64" : "32"; - result.realPlatform = os_arch; - result.javaVersion = java_version; - QLOG_DEBUG() << "Java checker succeeded."; - emit checkFinished(result); -} - -void JavaChecker::error(QProcess::ProcessError err) -{ - if(err == QProcess::FailedToStart) - { - killTimer.stop(); - QLOG_DEBUG() << "Java checker has failed to start."; - JavaCheckResult result; - { - result.path = path; - result.id = id; - } - - emit checkFinished(result); - return; - } -} - -void JavaChecker::timeout() -{ - // NO MERCY. NO ABUSE. - if(process) - { - QLOG_DEBUG() << "Java checker has been killed by timeout."; - process->kill(); - } -} diff --git a/logic/JavaChecker.h b/logic/JavaChecker.h deleted file mode 100644 index e19895f7..00000000 --- a/logic/JavaChecker.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include -#include -#include - -class JavaChecker; - - -struct JavaCheckResult -{ - QString path; - QString mojangPlatform; - QString realPlatform; - QString javaVersion; - bool valid = false; - bool is_64bit = false; - int id; -}; - -typedef std::shared_ptr QProcessPtr; -typedef std::shared_ptr JavaCheckerPtr; -class JavaChecker : public QObject -{ - Q_OBJECT -public: - explicit JavaChecker(QObject *parent = 0); - void performCheck(); - - QString path; - int id; - -signals: - void checkFinished(JavaCheckResult result); -private: - QProcessPtr process; - QTimer killTimer; -public -slots: - void timeout(); - void finished(int exitcode, QProcess::ExitStatus); - void error(QProcess::ProcessError); -}; diff --git a/logic/JavaCheckerJob.cpp b/logic/JavaCheckerJob.cpp deleted file mode 100644 index b0aea758..00000000 --- a/logic/JavaCheckerJob.cpp +++ /dev/null @@ -1,47 +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 "JavaCheckerJob.h" -#include "pathutils.h" -#include "MultiMC.h" - -#include "logger/QsLog.h" - -void JavaCheckerJob::partFinished(JavaCheckResult result) -{ - num_finished++; - QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" - << javacheckers.size(); - emit progress(num_finished, javacheckers.size()); - - javaresults.replace(result.id, result); - - if (num_finished == javacheckers.size()) - { - emit finished(javaresults); - } -} - -void JavaCheckerJob::start() -{ - QLOG_INFO() << m_job_name.toLocal8Bit() << " started."; - m_running = true; - for (auto iter : javacheckers) - { - javaresults.append(JavaCheckResult()); - connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - iter->performCheck(); - } -} diff --git a/logic/JavaCheckerJob.h b/logic/JavaCheckerJob.h deleted file mode 100644 index 132a92d4..00000000 --- a/logic/JavaCheckerJob.h +++ /dev/null @@ -1,100 +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 -#include -#include "JavaChecker.h" -#include "logic/tasks/ProgressProvider.h" - -class JavaCheckerJob; -typedef std::shared_ptr JavaCheckerJobPtr; - -class JavaCheckerJob : public ProgressProvider -{ - Q_OBJECT -public: - explicit JavaCheckerJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - total_progress++; - // if this is already running, the action needs to be started right away! - if (isRunning()) - { - emit progress(current_progress, total_progress); - connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - - base->performCheck(); - } - return true; - } - - JavaCheckerPtr operator[](int index) - { - return javacheckers[index]; - } - ; - JavaCheckerPtr first() - { - if (javacheckers.size()) - return javacheckers[0]; - return JavaCheckerPtr(); - } - int size() const - { - return javacheckers.size(); - } - virtual void getProgress(qint64 ¤t, qint64 &total) - { - current = current_progress; - total = total_progress; - } - ; - virtual QString getStatus() const - { - return m_job_name; - } - ; - virtual bool isRunning() const - { - return m_running; - } - ; - -signals: - void started(); - void progress(int current, int total); - void finished(QList); -public -slots: - virtual void start(); - // FIXME: implement - virtual void abort() {}; -private -slots: - void partFinished(JavaCheckResult result); - -private: - QString m_job_name; - QList javacheckers; - QList javaresults; - qint64 current_progress = 0; - qint64 total_progress = 0; - int num_finished = 0; - bool m_running = false; -}; diff --git a/logic/JavaUtils.cpp b/logic/JavaUtils.cpp deleted file mode 100644 index 3a3046bd..00000000 --- a/logic/JavaUtils.cpp +++ /dev/null @@ -1,223 +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 -#include -#include -#include -#include - -#include -#include - -#include "MultiMC.h" - -#include "JavaUtils.h" -#include "logger/QsLog.h" -#include "gui/dialogs/VersionSelectDialog.h" -#include "JavaCheckerJob.h" -#include "lists/JavaVersionList.h" - -JavaUtils::JavaUtils() -{ -} - -JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) -{ - JavaVersionPtr javaVersion(new JavaVersion()); - - javaVersion->id = id; - javaVersion->arch = arch; - javaVersion->path = path; - - return javaVersion; -} - -JavaVersionPtr JavaUtils::GetDefaultJava() -{ - JavaVersionPtr javaVersion(new JavaVersion()); - - javaVersion->id = "java"; - javaVersion->arch = "unknown"; - javaVersion->path = "java"; - - return javaVersion; -} - -#if WINDOWS -QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) -{ - QList javas; - - QString archType = "unknown"; - if (keyType == KEY_WOW64_64KEY) - archType = "64"; - else if (keyType == KEY_WOW64_32KEY) - archType = "32"; - - HKEY jreKey; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, - KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) - { - // Read the current type version from the registry. - // This will be used to find any key that contains the JavaHome value. - char *value = new char[0]; - DWORD valueSz = 0; - if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == - ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); - } - - QString recommended = value; - - TCHAR subKeyName[255]; - DWORD subKeyNameSize, numSubKeys, retCode; - - // Get the number of subkeys - RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, - NULL, NULL); - - // Iterate until RegEnumKeyEx fails - if (numSubKeys > 0) - { - for (int i = 0; i < numSubKeys; i++) - { - subKeyNameSize = 255; - retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, - NULL); - if (retCode == ERROR_SUCCESS) - { - // Now open the registry key for the version that we just got. - QString newKeyName = keyName + "\\" + subKeyName; - - HKEY newKey; - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, - KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) - { - // Read the JavaHome value to find where Java is installed. - value = new char[0]; - valueSz = 0; - if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, - &valueSz) == ERROR_MORE_DATA) - { - value = new char[valueSz]; - RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, - &valueSz); - - // Now, we construct the version object and add it to the list. - JavaVersionPtr javaVersion(new JavaVersion()); - - javaVersion->id = subKeyName; - javaVersion->arch = archType; - javaVersion->path = - QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); - javas.append(javaVersion); - } - - RegCloseKey(newKey); - } - } - } - } - - RegCloseKey(jreKey); - } - - return javas; -} - -QList JavaUtils::FindJavaPaths() -{ - QList java_candidates; - - QList JRE64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); - QList JDK64s = this->FindJavaFromRegistryKey( - KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); - QList JRE32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); - QList JDK32s = this->FindJavaFromRegistryKey( - KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); - - java_candidates.append(JRE64s); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/java.exe")); - java_candidates.append(JDK64s); - java_candidates.append(JRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/java.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/java.exe")); - java_candidates.append(JDK32s); - java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); - - QList candidates; - for(JavaVersionPtr java_candidate : java_candidates) - { - if(!candidates.contains(java_candidate->path)) - { - candidates.append(java_candidate->path); - } - } - - return candidates; -} - -#elif OSX -QList JavaUtils::FindJavaPaths() -{ - QList javas; - javas.append(this->GetDefaultJava()->path); - javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); - javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); - javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); - QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); - QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, libraryJVMJavas) { - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); - } - QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); - QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); - foreach (const QString &java, systemLibraryJVMJavas) { - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); - javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); - } - return javas; -} - -#elif LINUX -QList JavaUtils::FindJavaPaths() -{ - QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\""; - - QList javas; - javas.append(this->GetDefaultJava()->path); - javas.append("/opt/java/bin/java"); - javas.append("/usr/bin/java"); - - return javas; -} -#else -QList JavaUtils::FindJavaPaths() -{ - QLOG_INFO() << "Unknown operating system build - defaulting to \"java\""; - - QList javas; - javas.append(this->GetDefaultJava()->path); - - return javas; -} -#endif diff --git a/logic/JavaUtils.h b/logic/JavaUtils.h deleted file mode 100644 index 22a68ef3..00000000 --- a/logic/JavaUtils.h +++ /dev/null @@ -1,43 +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 -#include - -#include -#include "JavaCheckerJob.h" -#include "JavaChecker.h" -#include "lists/JavaVersionList.h" - -#if WINDOWS -#include -#endif - -class JavaUtils : public QObject -{ - Q_OBJECT -public: - JavaUtils(); - - JavaVersionPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); - QList FindJavaPaths(); - JavaVersionPtr GetDefaultJava(); - -#if WINDOWS - QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); -#endif -}; diff --git a/logic/LegacyUpdate.cpp b/logic/LegacyUpdate.cpp index aea88c95..00ee795d 100644 --- a/logic/LegacyUpdate.cpp +++ b/logic/LegacyUpdate.cpp @@ -13,20 +13,24 @@ * limitations under the License. */ -#include "LegacyUpdate.h" -#include "lists/LwjglVersionList.h" -#include "lists/MinecraftVersionList.h" -#include "BaseInstance.h" -#include "LegacyInstance.h" -#include "MultiMC.h" -#include "ModList.h" +#include + #include #include #include #include + +#include "logic/LegacyUpdate.h" +#include "logic/LwjglVersionList.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/BaseInstance.h" +#include "logic/LegacyInstance.h" +#include "MultiMC.h" +#include "logic/ModList.h" + #include "logger/QsLog.h" #include "logic/net/URLConstants.h" -#include + LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { diff --git a/logic/LwjglVersionList.cpp b/logic/LwjglVersionList.cpp new file mode 100644 index 00000000..df46d7be --- /dev/null +++ b/logic/LwjglVersionList.cpp @@ -0,0 +1,199 @@ +/* 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 "LwjglVersionList.h" +#include "MultiMC.h" + +#include +#include +#include + +#include "logger/QsLog.h" + +#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" + +LWJGLVersionList::LWJGLVersionList(QObject *parent) : QAbstractListModel(parent) +{ + setLoading(false); +} + +QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + const PtrLWJGLVersion version = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + return version->name(); + + case Qt::ToolTipRole: + return version->url(); + + default: + return QVariant(); + } +} + +QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + return "Version"; + + case Qt::ToolTipRole: + return "LWJGL version name."; + + default: + return QVariant(); + } +} + +int LWJGLVersionList::columnCount(const QModelIndex &parent) const +{ + return 1; +} + +bool LWJGLVersionList::isLoading() const +{ + return m_loading; +} + +void LWJGLVersionList::loadList() +{ + Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); + + setLoading(true); + auto worker = MMC->qnam(); + QNetworkRequest req(QUrl(RSS_URL)); + req.setRawHeader("Accept", "text/xml"); + req.setRawHeader("User-Agent", "MultiMC/5.0 (Uncached)"); + reply = worker->get(req); + connect(reply, SIGNAL(finished()), SLOT(netRequestComplete())); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +void LWJGLVersionList::netRequestComplete() +{ + if (reply->error() == QNetworkReply::NoError) + { + QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); + Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); + + QDomDocument doc; + + QString xmlErrorMsg; + int errorLine; + if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine)) + { + failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + + QString::number(errorLine)); + setLoading(false); + return; + } + + QDomNodeList items = doc.elementsByTagName("item"); + + QList tempList; + + for (int i = 0; i < items.length(); i++) + { + Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", + "XML element isn't an element... wat?"); + + QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); + if (linkElement.isNull()) + { + QLOG_INFO() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; + continue; + } + + QString link = linkElement.text(); + + // Make sure it's a download link. + if (link.endsWith("/download") && link.contains(lwjglRegex)) + { + QString name = link.mid(lwjglRegex.indexIn(link) + 6); + // Subtract 4 here to remove the .zip file extension. + name = name.left(lwjglRegex.matchedLength() - 10); + + QUrl url(link); + if (!url.isValid()) + { + QLOG_INFO() << "LWJGL version URL isn't valid:" << link << "Skipping."; + continue; + } + + tempList.append(LWJGLVersion::Create(name, link)); + } + } + + beginResetModel(); + m_vlist.swap(tempList); + endResetModel(); + + QLOG_INFO() << "Loaded LWJGL list."; + finished(); + } + else + { + failed("Failed to load LWJGL list. Network error: " + reply->errorString()); + } + + setLoading(false); + reply->deleteLater(); +} + +const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName) +{ + for (int i = 0; i < count(); i++) + { + QString name = at(i)->name(); + if (name == versionName) + return at(i); + } + return PtrLWJGLVersion(); +} + +void LWJGLVersionList::failed(QString msg) +{ + QLOG_INFO() << msg; + emit loadListFailed(msg); +} + +void LWJGLVersionList::finished() +{ + emit loadListFinished(); +} + +void LWJGLVersionList::setLoading(bool loading) +{ + m_loading = loading; + emit loadingStateUpdated(m_loading); +} diff --git a/logic/LwjglVersionList.h b/logic/LwjglVersionList.h new file mode 100644 index 00000000..fa57e8eb --- /dev/null +++ b/logic/LwjglVersionList.h @@ -0,0 +1,148 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +class LWJGLVersion; +typedef std::shared_ptr PtrLWJGLVersion; + +class LWJGLVersion : public QObject +{ + Q_OBJECT + + LWJGLVersion(const QString &name, const QString &url, QObject *parent = 0) + : QObject(parent), m_name(name), m_url(url) + { + } + +public: + + static PtrLWJGLVersion Create(const QString &name, const QString &url, QObject *parent = 0) + { + return PtrLWJGLVersion(new LWJGLVersion(name, url, parent)); + } + ; + + QString name() const + { + return m_name; + } + + QString url() const + { + return m_url; + } + +protected: + QString m_name; + QString m_url; +}; + +class LWJGLVersionList : public QAbstractListModel +{ + Q_OBJECT +public: + explicit LWJGLVersionList(QObject *parent = 0); + + bool isLoaded() + { + return m_vlist.length() > 0; + } + + const PtrLWJGLVersion getVersion(const QString &versionName); + PtrLWJGLVersion at(int index) + { + return m_vlist[index]; + } + const PtrLWJGLVersion at(int index) const + { + return m_vlist[index]; + } + + int count() const + { + return m_vlist.length(); + } + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent) const + { + return count(); + } + virtual int columnCount(const QModelIndex &parent) const; + + virtual bool isLoading() const; + virtual bool errored() const + { + return m_errored; + } + + virtual QString lastErrorMsg() const + { + return m_lastErrorMsg; + } + +public +slots: + /*! + * Loads the version list. + * This is done asynchronously. On success, the loadListFinished() signal will + * be emitted. The list model will be reset as well, resulting in the modelReset() + * signal being emitted. Note that the model will be reset before loadListFinished() is + * emitted. + * If loading the list failed, the loadListFailed(QString msg), + * signal will be emitted. + */ + virtual void loadList(); + +signals: + /*! + * Emitted when the list either starts or finishes loading. + * \param loading Whether or not the list is loading. + */ + void loadingStateUpdated(bool loading); + + void loadListFinished(); + + void loadListFailed(QString msg); + +private: + QList m_vlist; + + QNetworkReply *m_netReply; + QNetworkReply *reply; + + bool m_loading; + bool m_errored; + QString m_lastErrorMsg; + + void failed(QString msg); + + void finished(); + + void setLoading(bool loading); + +private +slots: + virtual void netRequestComplete(); +}; diff --git a/logic/MinecraftVersion.h b/logic/MinecraftVersion.h deleted file mode 100644 index 61f803b5..00000000 --- a/logic/MinecraftVersion.h +++ /dev/null @@ -1,92 +0,0 @@ -/* Copyright 2013 Andrew Okin - * - * 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 "BaseVersion.h" -#include -#include - -struct MinecraftVersion : public BaseVersion -{ - /// 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; - - /// is this the latest version? - bool is_latest = false; - - /// is this a snapshot? - bool is_snapshot = false; - - /// is this a built-in version that comes with MultiMC? - bool is_builtin = false; - - /// the human readable version name - QString m_name; - - /// the version ID. - QString m_descriptor; - - /// version traits. generally launcher business... - QSet m_traits; - - /// The main class this version uses (if any, can be empty). - QString m_mainClass; - - /// The applet class this version uses (if any, can be empty). - QString m_appletClass; - - bool usesLegacyLauncher() - { - return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); - } - - virtual QString descriptor() override - { - return m_descriptor; - } - - virtual QString name() override - { - return m_name; - } - - virtual QString typeString() const override - { - if (is_latest && is_snapshot) - { - return QObject::tr("Latest snapshot"); - } - else if(is_latest) - { - return QObject::tr("Latest release"); - } - else if(is_snapshot) - { - return QObject::tr("Snapshot"); - } - else if(is_builtin) - { - return QObject::tr("Museum piece"); - } - else - { - return QObject::tr("Regular release"); - } - } -}; diff --git a/logic/OneSixFTBInstance.cpp b/logic/OneSixFTBInstance.cpp index 131dbde3..491b15ba 100644 --- a/logic/OneSixFTBInstance.cpp +++ b/logic/OneSixFTBInstance.cpp @@ -1,12 +1,12 @@ #include "OneSixFTBInstance.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/OneSixVersionBuilder.h" #include "tasks/SequentialTask.h" #include "forge/ForgeInstaller.h" #include "forge/ForgeVersionList.h" #include "OneSixInstance_p.h" -#include "OneSixVersionBuilder.h" #include "MultiMC.h" #include "pathutils.h" diff --git a/logic/OneSixInstance.cpp b/logic/OneSixInstance.cpp index 67953f95..2c67f7a9 100644 --- a/logic/OneSixInstance.cpp +++ b/logic/OneSixInstance.cpp @@ -13,21 +13,22 @@ * limitations under the License. */ -#include "OneSixInstance.h" - #include - -#include "OneSixInstance_p.h" -#include "OneSixUpdate.h" -#include "VersionFinal.h" -#include "pathutils.h" +#include #include "logger/QsLog.h" -#include "assets/AssetsUtils.h" #include "MultiMC.h" +#include "MMCError.h" + +#include "logic/OneSixInstance.h" + +#include "logic/OneSixInstance_p.h" +#include "logic/OneSixUpdate.h" +#include "logic/minecraft/VersionFinal.h" + +#include "logic/assets/AssetsUtils.h" #include "icons/IconList.h" -#include "MinecraftProcess.h" +#include "logic/MinecraftProcess.h" #include "gui/dialogs/InstanceEditDialog.h" -#include OneSixInstance::OneSixInstance(const QString &rootDir, SettingsObject *settings, QObject *parent) diff --git a/logic/OneSixInstance.h b/logic/OneSixInstance.h index 80b197c4..00653def 100644 --- a/logic/OneSixInstance.h +++ b/logic/OneSixInstance.h @@ -17,8 +17,8 @@ #include "BaseInstance.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/ModList.h" class OneSixInstance : public BaseInstance { diff --git a/logic/OneSixInstance_p.h b/logic/OneSixInstance_p.h index e75a8da3..90802c24 100644 --- a/logic/OneSixInstance_p.h +++ b/logic/OneSixInstance_p.h @@ -15,9 +15,10 @@ #pragma once -#include "BaseInstance_p.h" -#include "VersionFinal.h" -#include "ModList.h" +#include "logic/BaseInstance_p.h" + +class ModList; +class VersionFinal; class OneSixInstancePrivate : public BaseInstancePrivate { diff --git a/logic/OneSixLibrary.cpp b/logic/OneSixLibrary.cpp deleted file mode 100644 index 45fa169e..00000000 --- a/logic/OneSixLibrary.cpp +++ /dev/null @@ -1,274 +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 - -#include "OneSixLibrary.h" -#include "OneSixRule.h" -#include "OpSys.h" -#include "logic/net/URLConstants.h" -#include -#include -#include "logger/QsLog.h" - -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 = minVersion = 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(const QString &name) -{ - m_name = name; -} -void OneSixLibrary::setBaseUrl(const QString &base_url) -{ - m_base_url = base_url; -} -void OneSixLibrary::setIsNative() -{ - m_is_native = true; -} -void OneSixLibrary::addNative(OpSys os, const QString &suffix) -{ - m_is_native = true; - m_native_suffixes[os] = suffix; -} -void OneSixLibrary::clearSuffixes() -{ - m_native_suffixes.clear(); -} -void OneSixLibrary::setRules(QList> rules) -{ - m_rules = rules; -} -bool OneSixLibrary::isActive() const -{ - return m_is_active; -} -bool OneSixLibrary::isNative() const -{ - return m_is_native; -} -QString OneSixLibrary::downloadUrl() const -{ - if (m_absolute_url.size()) - return m_absolute_url; - return m_download_url; -} -QString OneSixLibrary::storagePath() const -{ - return m_storage_path; -} - -void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url) -{ - m_absolute_url = absolute_url; -} - -QString OneSixLibrary::absoluteUrl() const -{ - return m_absolute_url; -} - -void OneSixLibrary::setHint(const QString &hint) -{ - m_hint = hint; -} - -QString OneSixLibrary::hint() const -{ - return m_hint; -} - -QStringList OneSixLibrary::files() -{ - QStringList retval; - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - retval.append(cooked_storage); - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - retval.append(cooked_storage); - } - else - retval.append(storage); - return retval; -} - -bool OneSixLibrary::filesExist(const QDir &base) -{ - auto libFiles = files(); - for(auto file: libFiles) - { - QFileInfo info(base, file); - QLOG_WARN() << info.absoluteFilePath() << "doesn't exist"; - if (!info.exists()) - return false; - } - return true; -} - -bool OneSixLibrary::extractTo(QString target_dir) -{ - QString storage = storagePath(); - if (storage.contains("${arch}")) - { - QString cooked_storage = storage; - cooked_storage.replace("${arch}", "32"); - QString origin = PathCombine("libraries", cooked_storage); - QString target_dir_cooked = PathCombine(target_dir, "32"); - if (!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - cooked_storage = storage; - cooked_storage.replace("${arch}", "64"); - origin = PathCombine("libraries", cooked_storage); - target_dir_cooked = PathCombine(target_dir, "64"); - if (!ensureFolderPathExists(target_dir_cooked)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; - return false; - } - if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) - .isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + origin; - return false; - } - } - else - { - if (!ensureFolderPathExists(target_dir)) - { - QLOG_ERROR() << "Couldn't create folder " + target_dir; - return false; - } - QString path = PathCombine("libraries", storage); - if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) - { - QLOG_ERROR() << "Couldn't extract " + path; - return false; - } - } - return true; -} - -QJsonObject OneSixLibrary::toJson() -{ - QJsonObject libRoot; - libRoot.insert("name", m_name); - if (m_absolute_url.size()) - libRoot.insert("MMC-absoluteUrl", m_absolute_url); - if (m_hint.size()) - libRoot.insert("MMC-hint", m_hint); - if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && - m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) - { - 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 deleted file mode 100644 index 1c7169ce..00000000 --- a/logic/OneSixLibrary.h +++ /dev/null @@ -1,148 +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 -#include -#include -#include -#include -#include - -#include "logic/net/URLConstants.h" -#include "OpSys.h" - -class Rule; - -class OneSixLibrary; -typedef std::shared_ptr OneSixLibraryPtr; - -class OneSixLibrary -{ -private: - // basic values used internally (so far) - QString m_name; - QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; - QList> m_rules; - - // custom values - /// absolute URL. takes precedence over m_download_path, if defined - QString m_absolute_url; - /// type hint - modifies how the library is treated - 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 m_native_suffixes; - -public: - QStringList extract_excludes; - QString minVersion; - - enum DependType - { - Soft, - Hard - }; - DependType dependType; - -public: - /// Constructor - OneSixLibrary(const QString &name, const DependType type = Soft) - { - m_name = name; - dependType = type; - } - - /// Returns the raw name field - QString rawName() const - { - return m_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(const QString &name); - /// get a decent-looking name - QString name() const - { - return m_decentname; - } - /// get a decent-looking version - QString version() const - { - return m_decentversion; - } - /// what kind of library is it? (for display) - QString type() const - { - return m_decenttype; - } - /// Set the url base for downloads - void setBaseUrl(const 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, const QString &suffix); - /// Clears all suffixes - void clearSuffixes(); - /// Set the load rules - void setRules(QList> rules); - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - /// Returns true if the library is native - bool isNative() const; - /// Get the URL to download the library from - QString downloadUrl() const; - /// Get the relative path where the library should be saved - QString storagePath() const; - - /// set an absolute URL for the library. This is an MMC extension. - void setAbsoluteUrl(const QString &absolute_url); - QString absoluteUrl() const; - - /// set a hint about how to treat the library. This is an MMC extension. - void setHint(const QString &hint); - QString hint() const; - - bool extractTo(QString target_dir); - bool filesExist(const QDir &base); - QStringList files(); -}; diff --git a/logic/OneSixRule.cpp b/logic/OneSixRule.cpp deleted file mode 100644 index d8d13b50..00000000 --- a/logic/OneSixRule.cpp +++ /dev/null @@ -1,89 +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 -#include - -#include "OneSixRule.h" - -QList> rulesFromJsonV4(const QJsonObject &objectWithRules) -{ - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; - - QJsonArray ruleList = rulesVal.toArray(); - for (auto ruleVal : ruleList) - { - std::shared_ptr 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)); - } - return rules; -} - -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; -} diff --git a/logic/OneSixRule.h b/logic/OneSixRule.h deleted file mode 100644 index 426e2886..00000000 --- a/logic/OneSixRule.h +++ /dev/null @@ -1,98 +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 - -#include "logic/OneSixLibrary.h" - -enum RuleAction -{ - Allow, - Disallow, - Defer -}; - -RuleAction RuleAction_fromString(QString); -QList> rulesFromJsonV4(const QJsonObject &objectWithRules); - -class Rule -{ -protected: - RuleAction m_result; - virtual bool applies(OneSixLibrary *parent) = 0; - -public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(OneSixLibrary *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } - ; -}; - -class OsRule : public Rule -{ -private: - // the OS - OpSys m_system; - // the OS version regexp - QString m_version_regexp; - -protected: - virtual bool applies(OneSixLibrary *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, OpSys system, - QString version_regexp) - { - return std::shared_ptr(new OsRule(result, system, version_regexp)); - } -}; - -class ImplicitRule : public Rule -{ -protected: - virtual bool applies(OneSixLibrary *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } - -public: - virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result) - { - return std::shared_ptr(new ImplicitRule(result)); - } -}; diff --git a/logic/OneSixUpdate.cpp b/logic/OneSixUpdate.cpp index 230ef9a9..66a699fe 100644 --- a/logic/OneSixUpdate.cpp +++ b/logic/OneSixUpdate.cpp @@ -22,18 +22,17 @@ #include #include #include +#include +#include -#include "BaseInstance.h" -#include "lists/MinecraftVersionList.h" -#include "VersionFinal.h" -#include "OneSixLibrary.h" -#include "OneSixInstance.h" +#include "logic/BaseInstance.h" +#include "logic/minecraft/MinecraftVersionList.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/OneSixInstance.h" #include "logic/forge/ForgeMirrors.h" -#include "net/URLConstants.h" -#include "assets/AssetsUtils.h" - -#include "pathutils.h" -#include +#include "logic/net/URLConstants.h" +#include "logic/assets/AssetsUtils.h" OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst) diff --git a/logic/OneSixVersionBuilder.cpp b/logic/OneSixVersionBuilder.cpp deleted file mode 100644 index 3fb96c5e..00000000 --- a/logic/OneSixVersionBuilder.cpp +++ /dev/null @@ -1,248 +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 "OneSixVersionBuilder.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "VersionFinal.h" -#include "OneSixInstance.h" -#include "OneSixRule.h" -#include "VersionFile.h" -#include "MMCJson.h" -#include "modutils.h" -#include "logger/QsLog.h" - -OneSixVersionBuilder::OneSixVersionBuilder() -{ -} - -void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance, const QStringList &external) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = instance; - builder.buildInternal(external); -} - -void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, - const QJsonObject &obj) -{ - OneSixVersionBuilder builder; - builder.m_version = version; - builder.m_instance = 0; - builder.readJsonAndApply(obj); -} - -void OneSixVersionBuilder::buildInternal(const QStringList &external) -{ - m_version->versionFiles.clear(); - - QDir root(m_instance->instanceRoot()); - QDir patches(root.absoluteFilePath("patches/")); - - // if we do external files, do just those. - if (!external.isEmpty()) - { - int externalOrder = -1; - for (auto fileName : external) - { - QLOG_INFO() << "Reading" << fileName; - auto file = - parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); - file->name = QFileInfo(fileName).fileName(); - file->fileId = "org.multimc.external." + file->name; - file->order = (externalOrder += 1); - file->version = QString(); - file->mcVersion = QString(); - m_version->versionFiles.append(file); - } - } - // else, if there's custom json, we just do that. - else if (QFile::exists(root.absoluteFilePath("custom.json"))) - { - QLOG_INFO() << "Reading custom.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); - file->name = "custom.json"; - file->filename = "custom.json"; - file->fileId = "org.multimc.custom.json"; - file->order = -1; - file->version = QString(); - m_version->versionFiles.append(file); - // QObject::tr("The version descriptors of this instance are not compatible with the - // current version of MultiMC")); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") - } - // version.json -> patches/*.json -> user.json - else - do - { - // version.json - QLOG_INFO() << "Reading version.json"; - auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); - file->name = "Minecraft"; - file->fileId = "org.multimc.version.json"; - file->order = -1; - file->version = m_instance->intendedVersionId(); - file->mcVersion = m_instance->intendedVersionId(); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more - // info.").arg(root.absoluteFilePath("version.json"))); - - // patches/ - // load all, put into map for ordering, apply in the right order - - QMap> files; - for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - QLOG_INFO() << "Reading" << info.fileName(); - auto file = parseJsonFile(info, true); - if (files.contains(file->order)) - { - throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( - file->fileId, files[file->order].second->fileId)); - } - files.insert(file->order, qMakePair(info.fileName(), file)); - } - for (auto order : files.keys()) - { - auto &filePair = files[order]; - m_version->versionFiles.append(filePair.second); - } - } while (0); - - // some final touches - m_version->finalize(); -} - - - -void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj) -{ - m_version->clear(); - - auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); - // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); - - file->applyTo(m_version); - m_version->versionFiles.append(file); - // QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); - // QObject::tr("The version descriptors of this instance are not compatible with the current - // version of MultiMC")); -} - -VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo, - const bool requireOrder, bool isFTB) -{ - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") - .arg(fileInfo.fileName(), file.errorString())); - } - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(error.offset)); - } - return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); - // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more - // info.").arg(file.fileName()); -} - -QMap OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) -{ - QMap out; - - // make sure the order file exists - if (!QDir(instance->instanceRoot()).exists("order.json")) - return out; - - // and it can be opened - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::ReadOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - - // and then read it and process it if all above is true. - try - { - auto obj = MMCJson::ensureObject(doc); - for (auto it = obj.begin(); it != obj.end(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - out.insert(it.key(), MMCJson::ensureInteger(it.value())); - } - } - catch (JSONValidationError &err) - { - QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - QLOG_WARN() << "Ignoring overriden order"; - return out; - } - return out; -} - -bool OneSixVersionBuilder::writeOverrideOrders(const QMap &order, - OneSixInstance *instance) -{ - QJsonObject obj; - for (auto it = order.cbegin(); it != order.cend(); ++it) - { - if (it.key().startsWith("org.multimc.")) - { - continue; - } - obj.insert(it.key(), it.value()); - } - QFile orderFile(instance->instanceRoot() + "/order.json"); - if (!orderFile.open(QFile::WriteOnly)) - { - QLOG_ERROR() << "Couldn't open" << orderFile.fileName() - << "for writing:" << orderFile.errorString(); - return false; - } - orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); - return true; -} diff --git a/logic/OneSixVersionBuilder.h b/logic/OneSixVersionBuilder.h deleted file mode 100644 index 6646584e..00000000 --- a/logic/OneSixVersionBuilder.h +++ /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. - */ - -#pragma once - -#include -#include -#include "VersionFile.h" - -class VersionFinal; -class OneSixInstance; -class QJsonObject; -class QFileInfo; - -class OneSixVersionBuilder -{ - OneSixVersionBuilder(); -public: - static void build(VersionFinal *version, OneSixInstance *instance, const QStringList &external); - static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); - - static QMap readOverrideOrders(OneSixInstance *instance); - static bool writeOverrideOrders(const QMap &order, OneSixInstance *instance); - -private: - VersionFinal *m_version; - OneSixInstance *m_instance; - - void buildInternal(const QStringList& external); - void readJsonAndApply(const QJsonObject &obj); - - VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, - bool isFTB = false); -}; diff --git a/logic/OpSys.cpp b/logic/OpSys.cpp deleted file mode 100644 index e001b7f3..00000000 --- a/logic/OpSys.cpp +++ /dev/null @@ -1,42 +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 "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 deleted file mode 100644 index 363c87d7..00000000 --- a/logic/OpSys.h +++ /dev/null @@ -1,37 +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 -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/VersionFile.cpp b/logic/VersionFile.cpp deleted file mode 100644 index 87936b6a..00000000 --- a/logic/VersionFile.cpp +++ /dev/null @@ -1,635 +0,0 @@ -#include -#include - -#include - -#include "logger/QsLog.h" -#include "logic/VersionFile.h" -#include "logic/OneSixLibrary.h" -#include "logic/VersionFinal.h" -#include "MMCJson.h" - -using namespace MMCJson; - -#define CURRENT_MINIMUM_LAUNCHER_VERSION 14 - -JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) -{ - JarmodPtr out(new Jarmod()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a jarmod that doesn't have a 'name' field"); - } - out->name = libObj.value("name").toString(); - - auto readString = [libObj, filename](const QString & key, QString & variable) - { - if (libObj.contains(key)) - { - QJsonValue val = libObj.value(key); - if (!val.isString()) - { - QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; - } - else - { - variable = val.toString(); - } - } - }; - - readString("url", out->baseurl); - readString("MMC-absoluteUrl", out->absoluteUrl); - if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) - { - out->absoluteUrl = out->baseurl + out->name; - } - return out; - -} - - -RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) -{ - RawLibraryPtr out(new RawLibrary()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a library that doesn't have a 'name' field"); - } - out->name = libObj.value("name").toString(); - - auto readString = [libObj, filename](const QString & key, QString & variable) - { - if (libObj.contains(key)) - { - QJsonValue val = libObj.value(key); - if (!val.isString()) - { - QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; - } - else - { - variable = val.toString(); - } - } - }; - - readString("url", out->url); - readString("MMC-hint", out->hint); - readString("MMC-absulute_url", out->absoluteUrl); - readString("MMC-absoluteUrl", out->absoluteUrl); - if (libObj.contains("extract")) - { - out->applyExcludes = true; - auto extractObj = ensureObject(libObj.value("extract")); - for (auto excludeVal : ensureArray(extractObj.value("exclude"))) - { - out->excludes.append(ensureString(excludeVal)); - } - } - if (libObj.contains("natives")) - { - out->applyNatives = true; - QJsonObject nativesObj = ensureObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - if (!it.value().isString()) - { - QLOG_WARN() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->natives.append(qMakePair(opSys, it.value().toString())); - } - } - } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->rules = rulesFromJsonV4(libObj); - } - return out; -} - -VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB) -{ - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError("The root of " + filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - if (requireOrder) - { - if (root.contains("order")) - { - out->order = ensureInteger(root.value("order")); - } - else - { - // FIXME: evaluate if we don't want to throw exceptions here instead - QLOG_ERROR() << filename << "doesn't contain an order field"; - } - } - - out->name = root.value("name").toString(); - out->fileId = root.value("fileId").toString(); - out->version = root.value("version").toString(); - out->mcVersion = root.value("mcVersion").toString(); - out->filename = filename; - - auto readString = [root, filename](const QString & key, QString & variable) - { - if (root.contains(key)) - { - variable = ensureString(root.value(key)); - } - }; - - // FIXME: This should be ignored when applying. - if (!isFTB) - { - readString("id", out->id); - } - - readString("mainClass", out->mainClass); - readString("appletClass", out->appletClass); - readString("processArguments", out->processArguments); - readString("minecraftArguments", out->overwriteMinecraftArguments); - readString("+minecraftArguments", out->addMinecraftArguments); - readString("-minecraftArguments", out->removeMinecraftArguments); - readString("type", out->type); - readString("releaseTime", out->versionReleaseTime); - readString("time", out->versionFileUpdateTime); - readString("assets", out->assets); - - if (root.contains("minimumLauncherVersion")) - { - out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion")); - } - - if (root.contains("tweakers")) - { - out->shouldOverwriteTweakers = true; - for (auto tweakerVal : ensureArray(root.value("tweakers"))) - { - out->overwriteTweakers.append(ensureString(tweakerVal)); - } - } - - if (root.contains("+tweakers")) - { - for (auto tweakerVal : ensureArray(root.value("+tweakers"))) - { - out->addTweakers.append(ensureString(tweakerVal)); - } - } - - if (root.contains("-tweakers")) - { - for (auto tweakerVal : ensureArray(root.value("-tweakers"))) - { - out->removeTweakers.append(ensureString(tweakerVal)); - } - } - - if (root.contains("+traits")) - { - for (auto tweakerVal : ensureArray(root.value("+traits"))) - { - out->traits.insert(ensureString(tweakerVal)); - } - } - - if (root.contains("libraries")) - { - // FIXME: This should be done when applying. - out->shouldOverwriteLibs = !isFTB; - for (auto libVal : ensureArray(root.value("libraries"))) - { - auto libObj = ensureObject(libVal); - - auto lib = RawLibrary::fromJson(libObj, filename); - // FIXME: This should be done when applying. - if (isFTB) - { - lib->hint = "local"; - lib->insertType = RawLibrary::Prepend; - out->addLibs.prepend(lib); - } - else - { - out->overwriteLibs.append(lib); - } - } - } - - if (root.contains("+jarMods")) - { - for (auto libVal : ensureArray(root.value("+jarMods"))) - { - QJsonObject libObj = ensureObject(libVal); - // parse the jarmod - auto lib = Jarmod::fromJson(libObj, filename); - // and add to jar mods - out->jarMods.append(lib); - } - } - - if (root.contains("+libraries")) - { - for (auto libVal : ensureArray(root.value("+libraries"))) - { - QJsonObject libObj = ensureObject(libVal); - QJsonValue insertVal = ensureExists(libObj.value("insert")); - - // parse the library - auto lib = RawLibrary::fromJson(libObj, filename); - - // TODO: utility functions for handling this case. templates? - QString insertString; - { - if (insertVal.isString()) - { - insertString = insertVal.toString(); - } - else if (insertVal.isObject()) - { - QJsonObject insertObj = insertVal.toObject(); - if (insertObj.isEmpty()) - { - throw JSONValidationError("One library has an empty insert object in " + - filename); - } - insertString = insertObj.keys().first(); - lib->insertData = insertObj.value(insertString).toString(); - } - } - if (insertString == "apply") - { - lib->insertType = RawLibrary::Apply; - } - else if (insertString == "prepend") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "append") - { - lib->insertType = RawLibrary::Prepend; - } - else if (insertString == "replace") - { - lib->insertType = RawLibrary::Replace; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid insert type"); - } - if (libObj.contains("MMC-depend")) - { - const QString dependString = ensureString(libObj.value("MMC-depend")); - if (dependString == "hard") - { - lib->dependType = RawLibrary::Hard; - } - else if (dependString == "soft") - { - lib->dependType = RawLibrary::Soft; - } - else - { - throw JSONValidationError("A '+' library in " + filename + - " contains an invalid depend type"); - } - } - out->addLibs.append(lib); - } - } - - if (root.contains("-libraries")) - { - for (auto libVal : ensureArray(root.value("-libraries"))) - { - auto libObj = ensureObject(libVal); - out->removeLibs.append(ensureString(libObj.value("name"))); - } - } - return out; -} - -OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) -{ - std::shared_ptr out(new OneSixLibrary(lib->name)); - if (!lib->url.isEmpty()) - { - out->setBaseUrl(lib->url); - } - out->setHint(lib->hint); - if (!lib->absoluteUrl.isEmpty()) - { - out->setAbsoluteUrl(lib->absoluteUrl); - } - out->setAbsoluteUrl(lib->absoluteUrl); - out->extract_excludes = lib->excludes; - for (auto native : lib->natives) - { - out->addNative(native.first, native.second); - } - out->setRules(lib->rules); - out->finalize(); - return out; -} - -int VersionFile::findLibrary(QList haystack, const QString &needle) -{ - int retval = -1; - for (int i = 0; i < haystack.size(); ++i) - { - QString chunk = haystack.at(i)->rawName(); - if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) - { - // only one is allowed. - if(retval != -1) - return -1; - retval = i; - } - } - return retval; -} - -bool VersionFile::isVanilla() -{ - return fileId == "org.multimc.version.json"; -} - -bool VersionFile::hasJarMods() -{ - return !jarMods.isEmpty(); -} - -void VersionFile::applyTo(VersionFinal *version) -{ - if (minimumLauncherVersion != -1) - { - if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); - } - } - - if (!version->id.isNull() && !mcVersion.isNull()) - { - if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == - -1) - { - throw MinecraftVersionMismatch(fileId, mcVersion, version->id); - } - } - - if (!id.isNull()) - { - version->id = id; - } - if (!mainClass.isNull()) - { - version->mainClass = mainClass; - } - if (!appletClass.isNull()) - { - version->appletClass = appletClass; - } - if (!processArguments.isNull()) - { - if(isVanilla()) - { - version->vanillaProcessArguments = processArguments; - } - version->processArguments = processArguments; - } - if (!type.isNull()) - { - version->type = type; - } - if (!versionReleaseTime.isNull()) - { - version->versionReleaseTime = versionReleaseTime; - } - if (!versionFileUpdateTime.isNull()) - { - version->time = versionFileUpdateTime; - } - if (!assets.isNull()) - { - version->assets = assets; - } - if (minimumLauncherVersion >= 0) - { - version->minimumLauncherVersion = minimumLauncherVersion; - } - if (!overwriteMinecraftArguments.isNull()) - { - if(isVanilla()) - { - version->vanillaMinecraftArguments = overwriteMinecraftArguments; - } - version->minecraftArguments = overwriteMinecraftArguments; - } - if (!addMinecraftArguments.isNull()) - { - version->minecraftArguments += addMinecraftArguments; - } - if (!removeMinecraftArguments.isNull()) - { - version->minecraftArguments.remove(removeMinecraftArguments); - } - if (shouldOverwriteTweakers) - { - version->tweakers = overwriteTweakers; - } - for (auto tweaker : addTweakers) - { - version->tweakers += tweaker; - } - for (auto tweaker : removeTweakers) - { - version->tweakers.removeAll(tweaker); - } - version->jarMods.append(jarMods); - version->traits.unite(traits); - if (shouldOverwriteLibs) - { - QList libs; - for (auto lib : overwriteLibs) - { - libs.append(createLibrary(lib)); - } - if(isVanilla()) - version->vanillaLibraries = libs; - version->libraries = libs; - } - for (auto lib : addLibs) - { - switch (lib->insertType) - { - case RawLibrary::Apply: - { - // QLOG_INFO() << "Applying lib " << lib->name; - int index = findLibrary(version->libraries, lib->name); - if (index >= 0) - { - auto library = version->libraries[index]; - if (!lib->url.isNull()) - { - library->setBaseUrl(lib->url); - } - if (!lib->hint.isNull()) - { - library->setHint(lib->hint); - } - if (!lib->absoluteUrl.isNull()) - { - library->setAbsoluteUrl(lib->absoluteUrl); - } - if (lib->applyExcludes) - { - library->extract_excludes = lib->excludes; - } - if (lib->applyNatives) - { - library->clearSuffixes(); - for (auto native : lib->natives) - { - library->addNative(native.first, native.second); - } - } - if (lib->applyRules) - { - library->setRules(lib->rules); - } - library->finalize(); - } - else - { - QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; - } - break; - } - case RawLibrary::Append: - case RawLibrary::Prepend: - { - // QLOG_INFO() << "Adding lib " << lib->name; - const int startOfVersion = lib->name.lastIndexOf(':') + 1; - const int index = findLibrary( - version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); - if (index < 0) - { - if (lib->insertType == RawLibrary::Append) - { - version->libraries.append(createLibrary(lib)); - } - else - { - version->libraries.prepend(createLibrary(lib)); - } - } - else - { - auto otherLib = version->libraries.at(index); - const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); - const Util::Version otherVersion = otherLib->version(); - // if the existing version is a hard dependency we can either use it or - // fail, but we can't change it - if (otherLib->dependType == OneSixLibrary::Hard) - { - // we need a higher version, or we're hard to and the versions aren't - // equal - if (ourVersion > otherVersion || - (lib->dependType == RawLibrary::Hard && ourVersion != otherVersion)) - { - throw VersionBuildError( - QObject::tr( - "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, filename)); - } - else - { - // the library is already existing, so we don't have to do anything - } - } - else if (otherLib->dependType == OneSixLibrary::Soft) - { - // if we are higher it means we should update - if (ourVersion > otherVersion) - { - auto library = createLibrary(lib); - if (Util::Version(otherLib->minVersion) < ourVersion) - { - library->minVersion = ourVersion.toString(); - } - version->libraries.replace(index, library); - } - else - { - // our version is smaller than the existing version, but we require - // it: fail - if (lib->dependType == RawLibrary::Hard) - { - throw VersionBuildError(QObject::tr( - "Error resolving library dependencies between %1 and %2 in %3.") - .arg(otherLib->rawName(), lib->name, - filename)); - } - } - } - } - break; - } - case RawLibrary::Replace: - { - QString toReplace; - if(lib->insertData.isEmpty()) - { - const int startOfVersion = lib->name.lastIndexOf(':') + 1; - toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); - } - else - toReplace = lib->insertData; - // QLOG_INFO() << "Replacing lib " << toReplace << " with " << lib->name; - int index = findLibrary(version->libraries, toReplace); - if (index >= 0) - { - version->libraries.replace(index, createLibrary(lib)); - } - else - { - QLOG_WARN() << "Couldn't find" << toReplace << "(skipping)"; - } - break; - } - } - } - for (auto lib : removeLibs) - { - int index = findLibrary(version->libraries, lib); - if (index >= 0) - { - // QLOG_INFO() << "Removing lib " << lib; - version->libraries.removeAt(index); - } - else - { - QLOG_WARN() << "Couldn't find" << lib << "(skipping)"; - } - } -} diff --git a/logic/VersionFile.h b/logic/VersionFile.h deleted file mode 100644 index ae0c58a0..00000000 --- a/logic/VersionFile.h +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include -#include -#include -#include "logic/OpSys.h" -#include "logic/OneSixRule.h" -#include "MMCError.h" - -class VersionFinal; - -class VersionBuildError : public MMCError -{ -public: - VersionBuildError(QString cause) : MMCError(cause) {}; - virtual ~VersionBuildError() noexcept {} -}; - -/** - * the base version file was meant for a newer version of the vanilla launcher than we support - */ -class LauncherVersionError : public VersionBuildError -{ -public: - LauncherVersionError(int actual, int supported) - : VersionBuildError(QObject::tr( - "The base version file of this instance was meant for a newer (%1) " - "version of the vanilla launcher than this version of MultiMC supports (%2).") - .arg(actual) - .arg(supported)) {}; - virtual ~LauncherVersionError() noexcept {} -}; - -/** - * some patch was intended for a different version of minecraft - */ -class MinecraftVersionMismatch : public VersionBuildError -{ -public: - MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) - : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " - "(%2) than that of the instance (%3).") - .arg(fileId) - .arg(mcVersion) - .arg(parentMcVersion)) {}; - virtual ~MinecraftVersionMismatch() noexcept {} -}; - -struct RawLibrary; -typedef std::shared_ptr RawLibraryPtr; -struct RawLibrary -{ - QString name; - QString url; - QString hint; - QString absoluteUrl; - bool applyExcludes = false; - QStringList excludes; - bool applyNatives = false; - QList> natives; - bool applyRules = false; - QList> rules; - - // user for '+' libraries - enum InsertType - { - Apply, - Append, - Prepend, - Replace - }; - InsertType insertType = Append; - QString insertData; - enum DependType - { - Soft, - Hard - }; - DependType dependType = Soft; - - static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); -}; - -struct Jarmod; -typedef std::shared_ptr JarmodPtr; -struct Jarmod -{ - QString name; - QString baseurl; - QString hint; - QString absoluteUrl; - - static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); -}; - -struct VersionFile; -typedef std::shared_ptr VersionFilePtr; -struct VersionFile -{ -public: /* methods */ - static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, - const bool requireOrder, const bool isFTB = false); - - static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); - int findLibrary(QList haystack, const QString &needle); - void applyTo(VersionFinal *version); - bool isVanilla(); - bool hasJarMods(); -public: /* data */ - int order = 0; - QString name; - QString fileId; - QString version; - // TODO use the mcVersion to determine if a version file should be removed on update - QString mcVersion; - QString filename; - // TODO requirements - // QMap requirements; - QString id; - QString mainClass; - QString appletClass; - QString overwriteMinecraftArguments; - QString addMinecraftArguments; - QString removeMinecraftArguments; - QString processArguments; - QString type; - QString versionReleaseTime; - QString versionFileUpdateTime; - QString assets; - int minimumLauncherVersion = -1; - - bool shouldOverwriteTweakers = false; - QStringList overwriteTweakers; - QStringList addTweakers; - QStringList removeTweakers; - - bool shouldOverwriteLibs = false; - QList overwriteLibs; - QList addLibs; - QList removeLibs; - - QSet traits; - - QList jarMods; -}; diff --git a/logic/VersionFinal.cpp b/logic/VersionFinal.cpp deleted file mode 100644 index b8690740..00000000 --- a/logic/VersionFinal.cpp +++ /dev/null @@ -1,431 +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 "VersionFinal.h" - -#include -#include -#include - -#include "OneSixVersionBuilder.h" -#include "OneSixInstance.h" -#include "VersionFilterData.h" -#include - -VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent) - : QAbstractListModel(parent), m_instance(instance) -{ - clear(); -} - -void VersionFinal::reload(const QStringList &external) -{ - beginResetModel(); - OneSixVersionBuilder::build(this, m_instance, external); - reapply(true); - endResetModel(); -} - -void VersionFinal::clear() -{ - id.clear(); - time.clear(); - versionReleaseTime.clear(); - type.clear(); - assets.clear(); - processArguments.clear(); - minecraftArguments.clear(); - minimumLauncherVersion = 0xDEADBEAF; - mainClass.clear(); - appletClass.clear(); - libraries.clear(); - tweakers.clear(); - jarMods.clear(); - traits.clear(); -} - -bool VersionFinal::canRemove(const int index) const -{ - if (index < versionFiles.size()) - { - return versionFiles.at(index)->fileId != "org.multimc.version.json"; - } - return false; -} - -bool VersionFinal::preremove(VersionFilePtr versionfile) -{ - bool ok = true; - for(auto & jarmod: versionfile->jarMods) - { - QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); - QFileInfo finfo (fullpath); - if(finfo.exists(fullpath)) - ok &= QFile::remove(fullpath); - } - return ok; -} - -bool VersionFinal::remove(const int index) -{ - if (!canRemove(index)) - return false; - if(!preremove(versionFiles[index])) - { - return false; - } - if(!QFile::remove(versionFiles.at(index)->filename)) - return false; - beginResetModel(); - versionFiles.removeAt(index); - reapply(true); - endResetModel(); - return true; -} - -bool VersionFinal::remove(const QString id) -{ - int i = 0; - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return remove(i); - } - i++; - } - return false; -} - -QString VersionFinal::versionFileId(const int index) const -{ - if (index < 0 || index >= versionFiles.size()) - { - return QString(); - } - return versionFiles.at(index)->fileId; -} - -VersionFilePtr VersionFinal::versionFile(const QString &id) -{ - for (auto file : versionFiles) - { - if (file->fileId == id) - { - return file; - } - } - return 0; -} - -bool VersionFinal::hasJarMods() -{ - return !jarMods.isEmpty(); -} - -bool VersionFinal::hasFtbPack() -{ - return versionFile("org.multimc.ftb.pack.json") != nullptr; -} - -bool VersionFinal::removeFtbPack() -{ - return remove("org.multimc.ftb.pack.json"); -} - -bool VersionFinal::isVanilla() -{ - QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); - if(versionFiles.size() > 1) - return false; - if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) - return false; - return true; -} - -bool VersionFinal::revertToVanilla() -{ - beginResetModel(); - auto it = versionFiles.begin(); - while (it != versionFiles.end()) - { - if ((*it)->fileId != "org.multimc.version.json") - { - if(!preremove(*it)) - { - endResetModel(); - return false; - } - if(!QFile::remove((*it)->filename)) - { - endResetModel(); - return false; - } - it = versionFiles.erase(it); - } - else - it++; - } - reapply(true); - endResetModel(); - return true; -} - -bool VersionFinal::usesLegacyCustomJson() -{ - return QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json")); -} - -QList > VersionFinal::getActiveNormalLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && !lib->isNative()) - { - output.append(lib); - } - } - return output; -} -QList > VersionFinal::getActiveNativeLibs() -{ - QList > output; - for (auto lib : libraries) - { - if (lib->isActive() && lib->isNative()) - { - output.append(lib); - } - } - return output; -} - -std::shared_ptr VersionFinal::fromJson(const QJsonObject &obj) -{ - std::shared_ptr version(new VersionFinal(0)); - try - { - OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj); - } - catch(MMCError & err) - { - return 0; - } - return version; -} - -QVariant VersionFinal::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= versionFiles.size()) - return QVariant(); - - if (role == Qt::DisplayRole) - { - switch (column) - { - case 0: - return versionFiles.at(row)->name; - case 1: - return versionFiles.at(row)->version; - default: - return QVariant(); - } - } - return QVariant(); -} -QVariant VersionFinal::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case 0: - return tr("Name"); - case 1: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); -} -Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const -{ - if (!index.isValid()) - return Qt::NoItemFlags; - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; -} - -int VersionFinal::rowCount(const QModelIndex &parent) const -{ - return versionFiles.size(); -} - -int VersionFinal::columnCount(const QModelIndex &parent) const -{ - return 2; -} - -QMap VersionFinal::getExistingOrder() const -{ - - QMap order; - // default - { - for (auto file : versionFiles) - { - order.insert(file->fileId, file->order); - } - } - // overriden - { - QMap overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_instance); - for (auto id : order.keys()) - { - if (overridenOrder.contains(id)) - { - order[id] = overridenOrder[id]; - } - } - } - return order; -} - -void VersionFinal::move(const int index, const MoveDirection direction) -{ - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - if (theirIndex < 0 || theirIndex >= versionFiles.size()) - { - return; - } - const QString ourId = versionFileId(index); - const QString theirId = versionFileId(theirIndex); - if (ourId.isNull() || ourId.startsWith("org.multimc.") || - theirId.isNull() || theirId.startsWith("org.multimc.")) - { - return; - } - if(direction == MoveDown) - { - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex+1); - } - else - { - beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); - } - versionFiles.swap(index, theirIndex); - endMoveRows(); - - auto order = getExistingOrder(); - order[ourId] = theirIndex; - order[theirId] = index; - - if (!OneSixVersionBuilder::writeOverrideOrders(order, m_instance)) - { - throw MMCError(tr("Couldn't save the new order")); - } - else - { - reapply(); - } -} -void VersionFinal::resetOrder() -{ - QDir(m_instance->instanceRoot()).remove("order.json"); - reapply(); -} - -void VersionFinal::reapply(const bool alreadyReseting) -{ - if (!alreadyReseting) - { - beginResetModel(); - } - - clear(); - - auto existingOrders = getExistingOrder(); - QList orders = existingOrders.values(); - std::sort(orders.begin(), orders.end()); - QList newVersionFiles; - for (auto order : orders) - { - auto file = versionFile(existingOrders.key(order)); - newVersionFiles.append(file); - file->applyTo(this); - } - versionFiles.swap(newVersionFiles); - finalize(); - if (!alreadyReseting) - { - endResetModel(); - } -} - -void VersionFinal::finalize() -{ - // HACK: deny april fools. my head hurts enough already. - QDate now = QDate::currentDate(); - bool isAprilFools = now.month() == 4 && now.day() == 1; - if (assets.endsWith("_af") && !isAprilFools) - { - assets = assets.left(assets.length() - 3); - } - if (assets.isEmpty()) - { - assets = "legacy"; - } - auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void - { - if (!minecraftArguments.isEmpty()) - return; - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - minecraftArguments = "--username ${auth_player_name} " - "--session ${auth_session} " - "--version ${profile_name}"; - } - }; - finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); - finalizeArguments(minecraftArguments, processArguments); -} - diff --git a/logic/VersionFinal.h b/logic/VersionFinal.h deleted file mode 100644 index ceb90f57..00000000 --- a/logic/VersionFinal.h +++ /dev/null @@ -1,172 +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 - -#include -#include -#include - -#include "OneSixLibrary.h" -#include "VersionFile.h" - -class OneSixInstance; - -class VersionFinal : public QAbstractListModel -{ - Q_OBJECT -public: - explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - - void reload(const QStringList &external = QStringList()); - void clear(); - - bool canRemove(const int index) const; - - QString versionFileId(const int index) const; - - // is this version unmodded vanilla minecraft? - bool isVanilla(); - // remove any customizations on top of vanilla - bool revertToVanilla(); - - // does this version have an FTB pack patch file? - bool hasFtbPack(); - // remove FTB pack - bool removeFtbPack(); - - // does this version have any jar mods? - bool hasJarMods(); - - // does this version still use a legacy custom.json file? - bool usesLegacyCustomJson(); - - - enum MoveDirection { MoveUp, MoveDown }; - void move(const int index, const MoveDirection direction); - void resetOrder(); - - // clears and reapplies all version files - void reapply(const bool alreadyReseting = false); - void finalize(); - -public -slots: - bool remove(const int index); - bool remove(const QString id); - -public: - QList> getActiveNormalLibs(); - QList> getActiveNativeLibs(); - - static std::shared_ptr fromJson(const QJsonObject &obj); - -private: - bool preremove(VersionFilePtr); - - // data members -public: - /// the ID - determines which jar to use! ACTUALLY IMPORTANT! - QString id; - /// Last updated time - as a string - QString time; - /// Release time - as a string - QString versionReleaseTime; - /// Release type - "release" or "snapshot" - QString type; - /// Assets type - "legacy" or a version ID - QString assets; - /** - * DEPRECATED: Old versions of the new vanilla launcher used this - * ex: "username_session_version" - */ - QString processArguments; - /// Same as above, but only for vanilla - QString vanillaProcessArguments; - /** - * 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; - /// Same as above, but only for vanilla - QString vanillaMinecraftArguments; - /** - * the minimum launcher version required by this version ... current is 4 (at point of - * writing) - */ - int minimumLauncherVersion = 0xDEADBEEF; - /** - * A list of all tweaker classes - */ - QStringList tweakers; - /** - * The main class to load first - */ - QString mainClass; - /** - * The applet class, for some very old minecraft releases - */ - QString appletClass; - - /// the list of libs - both active and inactive, native and java - QList> libraries; - - /// same, but only vanilla. - QList> vanillaLibraries; - - /// traits, collected from all the version files (version files can only add) - QSet traits; - - /// A list of jar mods. version files can add those. - QList jarMods; - - /* - FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. - - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx", - "version": "^10\\.5\\.\\d$" - } - } - ], - "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 rules; - - QList versionFiles; - VersionFilePtr versionFile(const QString &id); - -private: - OneSixInstance *m_instance; - QMap getExistingOrder() const; -}; diff --git a/logic/forge/ForgeInstaller.cpp b/logic/forge/ForgeInstaller.cpp index 3b13a7cd..0e7b26f7 100644 --- a/logic/forge/ForgeInstaller.cpp +++ b/logic/forge/ForgeInstaller.cpp @@ -14,8 +14,8 @@ */ #include "ForgeInstaller.h" -#include "logic/VersionFinal.h" -#include "logic/OneSixLibrary.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixLibrary.h" #include "logic/net/HttpMetaCache.h" #include "logic/tasks/Task.h" #include "logic/OneSixInstance.h" diff --git a/logic/forge/ForgeVersionList.h b/logic/forge/ForgeVersionList.h index 2c020b0f..477edb3d 100644 --- a/logic/forge/ForgeVersionList.h +++ b/logic/forge/ForgeVersionList.h @@ -20,7 +20,7 @@ #include #include -#include "logic/lists/BaseVersionList.h" +#include "logic/BaseVersionList.h" #include "logic/tasks/Task.h" #include "logic/net/NetJob.h" #include "logic/forge/ForgeVersion.h" diff --git a/logic/java/JavaChecker.cpp b/logic/java/JavaChecker.cpp new file mode 100644 index 00000000..b87ee3d5 --- /dev/null +++ b/logic/java/JavaChecker.cpp @@ -0,0 +1,124 @@ +#include "JavaChecker.h" +#include "MultiMC.h" +#include +#include +#include +#include +#include + +JavaChecker::JavaChecker(QObject *parent) : QObject(parent) +{ +} + +void JavaChecker::performCheck() +{ + QString checkerJar = PathCombine(MMC->bin(), "jars", "JavaCheck.jar"); + + QStringList args = {"-jar", checkerJar}; + + process.reset(new QProcess()); + process->setArguments(args); + process->setProgram(path); + process->setProcessChannelMode(QProcess::SeparateChannels); + QLOG_DEBUG() << "Running java checker!"; + QLOG_DEBUG() << "Java: " + path; + QLOG_DEBUG() << "Args: {" + args.join("|") + "}"; + + connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, + SLOT(finished(int, QProcess::ExitStatus))); + connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, + SLOT(error(QProcess::ProcessError))); + connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); + killTimer.setSingleShot(true); + killTimer.start(5000); + process->start(); +} + +void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) +{ + killTimer.stop(); + QProcessPtr _process; + _process.swap(process); + + JavaCheckResult result; + { + result.path = path; + result.id = id; + } + QLOG_DEBUG() << "Java checker finished with status " << status << " exit code " << exitcode; + + if (status == QProcess::CrashExit || exitcode == 1) + { + QLOG_DEBUG() << "Java checker failed!"; + emit checkFinished(result); + return; + } + + bool success = true; + QString p_stdout = _process->readAllStandardOutput(); + QLOG_DEBUG() << p_stdout; + + QMap results; + QStringList lines = p_stdout.split("\n", QString::SkipEmptyParts); + for(QString line : lines) + { + line = line.trimmed(); + + auto parts = line.split('=', QString::SkipEmptyParts); + if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) + { + success = false; + } + else + { + results.insert(parts[0], parts[1]); + } + } + + if(!results.contains("os.arch") || !results.contains("java.version") || !success) + { + QLOG_DEBUG() << "Java checker failed - couldn't extract required information."; + emit checkFinished(result); + return; + } + + auto os_arch = results["os.arch"]; + auto java_version = results["java.version"]; + bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; + + + result.valid = true; + result.is_64bit = is_64; + result.mojangPlatform = is_64 ? "64" : "32"; + result.realPlatform = os_arch; + result.javaVersion = java_version; + QLOG_DEBUG() << "Java checker succeeded."; + emit checkFinished(result); +} + +void JavaChecker::error(QProcess::ProcessError err) +{ + if(err == QProcess::FailedToStart) + { + killTimer.stop(); + QLOG_DEBUG() << "Java checker has failed to start."; + JavaCheckResult result; + { + result.path = path; + result.id = id; + } + + emit checkFinished(result); + return; + } +} + +void JavaChecker::timeout() +{ + // NO MERCY. NO ABUSE. + if(process) + { + QLOG_DEBUG() << "Java checker has been killed by timeout."; + process->kill(); + } +} diff --git a/logic/java/JavaChecker.h b/logic/java/JavaChecker.h new file mode 100644 index 00000000..e19895f7 --- /dev/null +++ b/logic/java/JavaChecker.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include + +class JavaChecker; + + +struct JavaCheckResult +{ + QString path; + QString mojangPlatform; + QString realPlatform; + QString javaVersion; + bool valid = false; + bool is_64bit = false; + int id; +}; + +typedef std::shared_ptr QProcessPtr; +typedef std::shared_ptr JavaCheckerPtr; +class JavaChecker : public QObject +{ + Q_OBJECT +public: + explicit JavaChecker(QObject *parent = 0); + void performCheck(); + + QString path; + int id; + +signals: + void checkFinished(JavaCheckResult result); +private: + QProcessPtr process; + QTimer killTimer; +public +slots: + void timeout(); + void finished(int exitcode, QProcess::ExitStatus); + void error(QProcess::ProcessError); +}; diff --git a/logic/java/JavaCheckerJob.cpp b/logic/java/JavaCheckerJob.cpp new file mode 100644 index 00000000..b0aea758 --- /dev/null +++ b/logic/java/JavaCheckerJob.cpp @@ -0,0 +1,47 @@ +/* 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 "JavaCheckerJob.h" +#include "pathutils.h" +#include "MultiMC.h" + +#include "logger/QsLog.h" + +void JavaCheckerJob::partFinished(JavaCheckResult result) +{ + num_finished++; + QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" + << javacheckers.size(); + emit progress(num_finished, javacheckers.size()); + + javaresults.replace(result.id, result); + + if (num_finished == javacheckers.size()) + { + emit finished(javaresults); + } +} + +void JavaCheckerJob::start() +{ + QLOG_INFO() << m_job_name.toLocal8Bit() << " started."; + m_running = true; + for (auto iter : javacheckers) + { + javaresults.append(JavaCheckResult()); + connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); + iter->performCheck(); + } +} diff --git a/logic/java/JavaCheckerJob.h b/logic/java/JavaCheckerJob.h new file mode 100644 index 00000000..132a92d4 --- /dev/null +++ b/logic/java/JavaCheckerJob.h @@ -0,0 +1,100 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "JavaChecker.h" +#include "logic/tasks/ProgressProvider.h" + +class JavaCheckerJob; +typedef std::shared_ptr JavaCheckerJobPtr; + +class JavaCheckerJob : public ProgressProvider +{ + Q_OBJECT +public: + explicit JavaCheckerJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {}; + + bool addJavaCheckerAction(JavaCheckerPtr base) + { + javacheckers.append(base); + total_progress++; + // if this is already running, the action needs to be started right away! + if (isRunning()) + { + emit progress(current_progress, total_progress); + connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); + + base->performCheck(); + } + return true; + } + + JavaCheckerPtr operator[](int index) + { + return javacheckers[index]; + } + ; + JavaCheckerPtr first() + { + if (javacheckers.size()) + return javacheckers[0]; + return JavaCheckerPtr(); + } + int size() const + { + return javacheckers.size(); + } + virtual void getProgress(qint64 ¤t, qint64 &total) + { + current = current_progress; + total = total_progress; + } + ; + virtual QString getStatus() const + { + return m_job_name; + } + ; + virtual bool isRunning() const + { + return m_running; + } + ; + +signals: + void started(); + void progress(int current, int total); + void finished(QList); +public +slots: + virtual void start(); + // FIXME: implement + virtual void abort() {}; +private +slots: + void partFinished(JavaCheckResult result); + +private: + QString m_job_name; + QList javacheckers; + QList javaresults; + qint64 current_progress = 0; + qint64 total_progress = 0; + int num_finished = 0; + bool m_running = false; +}; diff --git a/logic/java/JavaUtils.cpp b/logic/java/JavaUtils.cpp new file mode 100644 index 00000000..1be93a9b --- /dev/null +++ b/logic/java/JavaUtils.cpp @@ -0,0 +1,221 @@ +/* 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 +#include +#include +#include + +#include +#include + +#include "MultiMC.h" + +#include "logger/QsLog.h" +#include "logic/java/JavaUtils.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaVersionList.h" + +JavaUtils::JavaUtils() +{ +} + +JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) +{ + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = id; + javaVersion->arch = arch; + javaVersion->path = path; + + return javaVersion; +} + +JavaVersionPtr JavaUtils::GetDefaultJava() +{ + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = "java"; + javaVersion->arch = "unknown"; + javaVersion->path = "java"; + + return javaVersion; +} + +#if WINDOWS +QList JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) +{ + QList javas; + + QString archType = "unknown"; + if (keyType == KEY_WOW64_64KEY) + archType = "64"; + else if (keyType == KEY_WOW64_32KEY) + archType = "32"; + + HKEY jreKey; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, + KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) + { + // Read the current type version from the registry. + // This will be used to find any key that contains the JavaHome value. + char *value = new char[0]; + DWORD valueSz = 0; + if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == + ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); + } + + QString recommended = value; + + TCHAR subKeyName[255]; + DWORD subKeyNameSize, numSubKeys, retCode; + + // Get the number of subkeys + RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, + NULL, NULL); + + // Iterate until RegEnumKeyEx fails + if (numSubKeys > 0) + { + for (int i = 0; i < numSubKeys; i++) + { + subKeyNameSize = 255; + retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, + NULL); + if (retCode == ERROR_SUCCESS) + { + // Now open the registry key for the version that we just got. + QString newKeyName = keyName + "\\" + subKeyName; + + HKEY newKey; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, + KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) + { + // Read the JavaHome value to find where Java is installed. + value = new char[0]; + valueSz = 0; + if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, + &valueSz) == ERROR_MORE_DATA) + { + value = new char[valueSz]; + RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, + &valueSz); + + // Now, we construct the version object and add it to the list. + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = subKeyName; + javaVersion->arch = archType; + javaVersion->path = + QDir(PathCombine(value, "bin")).absoluteFilePath("java.exe"); + javas.append(javaVersion); + } + + RegCloseKey(newKey); + } + } + } + } + + RegCloseKey(jreKey); + } + + return javas; +} + +QList JavaUtils::FindJavaPaths() +{ + QList java_candidates; + + QList JRE64s = this->FindJavaFromRegistryKey( + KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList JDK64s = this->FindJavaFromRegistryKey( + KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + QList JRE32s = this->FindJavaFromRegistryKey( + KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); + QList JDK32s = this->FindJavaFromRegistryKey( + KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); + + java_candidates.append(JRE64s); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/java.exe")); + java_candidates.append(JDK64s); + java_candidates.append(JRE32s); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/java.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/java.exe")); + java_candidates.append(JDK32s); + java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); + + QList candidates; + for(JavaVersionPtr java_candidate : java_candidates) + { + if(!candidates.contains(java_candidate->path)) + { + candidates.append(java_candidate->path); + } + } + + return candidates; +} + +#elif OSX +QList JavaUtils::FindJavaPaths() +{ + QList javas; + javas.append(this->GetDefaultJava()->path); + javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); + javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); + javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); + QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); + QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &java, libraryJVMJavas) { + javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); + javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); + } + QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); + QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + foreach (const QString &java, systemLibraryJVMJavas) { + javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); + javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); + } + return javas; +} + +#elif LINUX +QList JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\""; + + QList javas; + javas.append(this->GetDefaultJava()->path); + javas.append("/opt/java/bin/java"); + javas.append("/usr/bin/java"); + + return javas; +} +#else +QList JavaUtils::FindJavaPaths() +{ + QLOG_INFO() << "Unknown operating system build - defaulting to \"java\""; + + QList javas; + javas.append(this->GetDefaultJava()->path); + + return javas; +} +#endif diff --git a/logic/java/JavaUtils.h b/logic/java/JavaUtils.h new file mode 100644 index 00000000..af92100f --- /dev/null +++ b/logic/java/JavaUtils.h @@ -0,0 +1,43 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include "JavaCheckerJob.h" +#include "JavaChecker.h" +#include "JavaVersionList.h" + +#if WINDOWS +#include +#endif + +class JavaUtils : public QObject +{ + Q_OBJECT +public: + JavaUtils(); + + JavaVersionPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); + QList FindJavaPaths(); + JavaVersionPtr GetDefaultJava(); + +#if WINDOWS + QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); +#endif +}; diff --git a/logic/java/JavaVersionList.cpp b/logic/java/JavaVersionList.cpp new file mode 100644 index 00000000..dcb6ced6 --- /dev/null +++ b/logic/java/JavaVersionList.cpp @@ -0,0 +1,241 @@ +/* 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 +#include +#include + +#include "MultiMC.h" +#include "logger/QsLog.h" + +#include "logic/java/JavaVersionList.h" +#include "logic/java/JavaCheckerJob.h" +#include "logic/java/JavaUtils.h" + +JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) +{ +} + +Task *JavaVersionList::getLoadTask() +{ + return new JavaListLoadTask(this); +} + +const BaseVersionPtr JavaVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +bool JavaVersionList::isLoaded() +{ + return m_loaded; +} + +int JavaVersionList::count() const +{ + return m_vlist.count(); +} + +int JavaVersionList::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant JavaVersionList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast(m_vlist[index.row()]); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: + return version->id; + + case 1: + return version->arch; + + case 2: + return version->path; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return version->descriptor(); + + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + + default: + return QVariant(); + } +} + +QVariant JavaVersionList::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case 0: + return "Version"; + + case 1: + return "Arch"; + + case 2: + return "Path"; + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case 0: + return "The name of the version."; + + case 1: + return "The architecture this version is for."; + + case 2: + return "Path to this Java version."; + + default: + return QVariant(); + } + + default: + return QVariant(); + } +} + +BaseVersionPtr JavaVersionList::getTopRecommended() const +{ + auto first = m_vlist.first(); + if(first != nullptr) + { + return first; + } + else + { + return BaseVersionPtr(); + } +} + +void JavaVersionList::updateListData(QList versions) +{ + beginResetModel(); + m_vlist = versions; + m_loaded = true; + endResetModel(); + // NOW SORT!! + // sort(); +} + +void JavaVersionList::sort() +{ + // NO-OP for now +} + +JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) : Task() +{ + m_list = vlist; + m_currentRecommended = NULL; +} + +JavaListLoadTask::~JavaListLoadTask() +{ +} + +void JavaListLoadTask::executeTask() +{ + setStatus(tr("Detecting Java installations...")); + + JavaUtils ju; + QList candidate_paths = ju.FindJavaPaths(); + + m_job = std::shared_ptr(new JavaCheckerJob("Java detection")); + connect(m_job.get(), SIGNAL(finished(QList)), this, SLOT(javaCheckerFinished(QList))); + connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int))); + + QLOG_DEBUG() << "Probing the following Java paths: "; + int id = 0; + for(QString candidate : candidate_paths) + { + QLOG_DEBUG() << " " << candidate; + + auto candidate_checker = new JavaChecker(); + candidate_checker->path = candidate; + candidate_checker->id = id; + m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); + + id++; + } + + m_job->start(); +} + +void JavaListLoadTask::checkerProgress(int current, int total) +{ + float progress = (current * 100.0) / total; + this->setProgress((int) progress); +} + +void JavaListLoadTask::javaCheckerFinished(QList results) +{ + QList candidates; + + QLOG_DEBUG() << "Found the following valid Java installations:"; + for(JavaCheckResult result : results) + { + if(result.valid) + { + JavaVersionPtr javaVersion(new JavaVersion()); + + javaVersion->id = result.javaVersion; + javaVersion->arch = result.mojangPlatform; + javaVersion->path = result.path; + candidates.append(javaVersion); + + QLOG_DEBUG() << " " << javaVersion->id << javaVersion->arch << javaVersion->path; + } + } + + QList javas_bvp; + for (auto java : candidates) + { + //QLOG_INFO() << java->id << java->arch << " at " << java->path; + BaseVersionPtr bp_java = std::dynamic_pointer_cast(java); + + if (bp_java) + { + javas_bvp.append(java); + } + } + + m_list->updateListData(javas_bvp); + emitSucceeded(); +} diff --git a/logic/java/JavaVersionList.h b/logic/java/JavaVersionList.h new file mode 100644 index 00000000..a46f33a2 --- /dev/null +++ b/logic/java/JavaVersionList.h @@ -0,0 +1,96 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/java/JavaCheckerJob.h" + +class JavaListLoadTask; + +struct JavaVersion : public BaseVersion +{ + virtual QString descriptor() + { + return id; + } + + virtual QString name() + { + return id; + } + + virtual QString typeString() const + { + return arch; + } + + QString id; + QString arch; + QString path; +}; + +typedef std::shared_ptr JavaVersionPtr; + +class JavaVersionList : public BaseVersionList +{ + Q_OBJECT +public: + explicit JavaVersionList(QObject *parent = 0); + + virtual Task *getLoadTask(); + virtual bool isLoaded(); + virtual const BaseVersionPtr at(int i) const; + virtual int count() const; + virtual void sort(); + + virtual BaseVersionPtr getTopRecommended() const; + + virtual QVariant data(const QModelIndex &index, int role) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex &parent) const; + +public +slots: + virtual void updateListData(QList versions); + +protected: + QList m_vlist; + + bool m_loaded = false; +}; + +class JavaListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit JavaListLoadTask(JavaVersionList *vlist); + ~JavaListLoadTask(); + + virtual void executeTask(); +public slots: + void javaCheckerFinished(QList results); + void checkerProgress(int current, int total); + +protected: + std::shared_ptr m_job; + JavaVersionList *m_list; + JavaVersion *m_currentRecommended; +}; diff --git a/logic/lists/BaseVersionList.cpp b/logic/lists/BaseVersionList.cpp deleted file mode 100644 index 6e2c5282..00000000 --- a/logic/lists/BaseVersionList.cpp +++ /dev/null @@ -1,121 +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 "logic/lists/BaseVersionList.h" -#include "logic/BaseVersion.h" - -BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent) -{ -} - -BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) -{ - for (int i = 0; i < count(); i++) - { - if (at(i)->descriptor() == descriptor) - return at(i); - } - return BaseVersionPtr(); -} - -BaseVersionPtr BaseVersionList::getLatestStable() const -{ - if (count() <= 0) - return BaseVersionPtr(); - else - return at(0); -} - -QVariant BaseVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - BaseVersionPtr version = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case NameColumn: - return version->name(); - - case TypeColumn: - return version->typeString(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return version->descriptor(); - - case VersionPointerRole: - return qVariantFromValue(version); - - default: - return QVariant(); - } -} - -QVariant BaseVersionList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case NameColumn: - return "Name"; - - case TypeColumn: - return "Type"; - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return "The name of the version."; - - case TypeColumn: - return "The version's type."; - - default: - return QVariant(); - } - - default: - return QVariant(); - } -} - -int BaseVersionList::rowCount(const QModelIndex &parent) const -{ - // Return count - return count(); -} - -int BaseVersionList::columnCount(const QModelIndex &parent) const -{ - return 2; -} diff --git a/logic/lists/BaseVersionList.h b/logic/lists/BaseVersionList.h deleted file mode 100644 index 21b44e8d..00000000 --- a/logic/lists/BaseVersionList.h +++ /dev/null @@ -1,120 +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 -#include -#include - -#include "logic/BaseVersion.h" - -class Task; - -/*! - * \brief Class that each instance type's version list derives from. - * Version lists are the lists that keep track of the available game versions - * for that instance. This list will not be loaded on startup. It will be loaded - * when the list's load function is called. Before using the version list, you - * should check to see if it has been loaded yet and if not, load the list. - * - * Note that this class also inherits from QAbstractListModel. Methods from that - * class determine how this version list shows up in a list view. Said methods - * all have a default implementation, but they can be overridden by plugins to - * change the behavior of the list. - */ -class BaseVersionList : public QAbstractListModel -{ - Q_OBJECT -public: - enum ModelRoles - { - VersionPointerRole = 0x34B1CB48 - }; - - enum VListColumns - { - // First column - Name - NameColumn = 0, - - // Second column - Type - TypeColumn, - - // Third column - Timestamp - TimeColumn - }; - - explicit BaseVersionList(QObject *parent = 0); - - /*! - * \brief Gets a task that will reload the version list. - * Simply execute the task to load the list. - * The task returned by this function should reset the model when it's done. - * \return A pointer to a task that reloads the version list. - */ - virtual Task *getLoadTask() = 0; - - //! Checks whether or not the list is loaded. If this returns false, the list should be - //loaded. - virtual bool isLoaded() = 0; - - //! Gets the version at the given index. - virtual const BaseVersionPtr at(int i) const = 0; - - //! Returns the number of versions in the list. - virtual int count() const = 0; - - //////// List Model Functions //////// - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent) const; - virtual int columnCount(const QModelIndex &parent) const; - - /*! - * \brief Finds a version by its descriptor. - * \param The descriptor of the version to find. - * \return A const pointer to the version with the given descriptor. NULL if - * one doesn't exist. - */ - virtual 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 BaseVersionPtr getLatestStable() const; - - /*! - * Sorts the version list. - */ - virtual void sort() = 0; - -protected -slots: - /*! - * Updates this list with the given list of versions. - * This is done by copying each version in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the versions are set to this - * version list. This can't be done in the load task, because the versions the load - * task creates are on the load task's thread and Qt won't allow their parents - * to be set to something created on another thread. - * To get around that problem, we invoke this method on the GUI thread, which - * then copies the versions and sets their parents correctly. - * \param versions List of versions whose parents should be set. - */ - virtual void updateListData(QList versions) = 0; -}; diff --git a/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp deleted file mode 100644 index 8808d6b5..00000000 --- a/logic/lists/InstanceList.cpp +++ /dev/null @@ -1,618 +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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "MultiMC.h" -#include "logic/lists/InstanceList.h" -#include "logic/icons/IconList.h" -#include "logic/lists/MinecraftVersionList.h" -#include "logic/BaseInstance.h" -#include "logic/InstanceFactory.h" -#include "logger/QsLog.h" -#include - -const static int GROUP_FILE_FORMAT_VERSION = 1; - -InstanceList::InstanceList(const QString &instDir, QObject *parent) - : QAbstractListModel(parent), m_instDir(instDir) -{ - connect(MMC, &MultiMC::aboutToQuit, this, &InstanceList::saveGroupList); - - if (!QDir::current().exists(m_instDir)) - { - QDir::current().mkpath(m_instDir); - } - - /* - * FIXME HACK: instances sometimes need to be created at launch. They need the versions for - * that. - * - * Remove this. it has no business of reloading the whole list. The instances which - * need it should track such events themselves and CHANGE THEIR DATA ONLY! - */ - connect(MMC->minecraftlist().get(), &MinecraftVersionList::modelReset, this, - &InstanceList::loadList); -} - -InstanceList::~InstanceList() -{ -} - -int InstanceList::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return m_instances.count(); -} - -QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const -{ - Q_UNUSED(parent); - if (row < 0 || row >= m_instances.size()) - return QModelIndex(); - return createIndex(row, column, (void *)m_instances.at(row).get()); -} - -QVariant InstanceList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - { - return QVariant(); - } - BaseInstance *pdata = static_cast(index.internalPointer()); - switch (role) - { - case InstancePointerRole: - { - QVariant v = qVariantFromValue((void *)pdata); - return v; - } - case InstanceIDRole: - { - return pdata->id(); - } - case Qt::DisplayRole: - { - return pdata->name(); - } - case Qt::ToolTipRole: - { - return pdata->instanceRoot(); - } - case Qt::DecorationRole: - { - QString key = pdata->iconKey(); - return MMC->icons()->getIcon(key); - } - // for now. - case GroupViewRoles::GroupRole: - { - return pdata->group(); - } - default: - break; - } - return QVariant(); -} - -Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags f; - if (index.isValid()) - { - f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); - } - return f; -} - -void InstanceList::groupChanged() -{ - // save the groups. save all of them. - saveGroupList(); -} - -QStringList InstanceList::getGroups() -{ - return m_groups.toList(); -} - -void InstanceList::saveGroupList() -{ - QString groupFileName = m_instDir + "/instgroups.json"; - QFile groupFile(groupFileName); - - // if you can't open the file, fail - if (!groupFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - // An error occurred. Ignore it. - QLOG_ERROR() << "Failed to save instance group file."; - return; - } - QTextStream out(&groupFile); - QMap> groupMap; - for (auto instance : m_instances) - { - QString id = instance->id(); - QString group = instance->group(); - if (group.isEmpty()) - continue; - - // keep a list/set of groups for choosing - m_groups.insert(group); - - if (!groupMap.count(group)) - { - QSet set; - set.insert(id); - groupMap[group] = set; - } - else - { - QSet &set = groupMap[group]; - set.insert(id); - } - } - QJsonObject toplevel; - toplevel.insert("formatVersion", QJsonValue(QString("1"))); - QJsonObject groupsArr; - for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++) - { - auto list = iter.value(); - auto name = iter.key(); - QJsonObject groupObj; - QJsonArray instanceArr; - groupObj.insert("hidden", QJsonValue(QString("false"))); - for (auto item : list) - { - instanceArr.append(QJsonValue(item)); - } - groupObj.insert("instances", instanceArr); - groupsArr.insert(name, groupObj); - } - toplevel.insert("groups", groupsArr); - QJsonDocument doc(toplevel); - groupFile.write(doc.toJson()); - groupFile.close(); -} - -void InstanceList::loadGroupList(QMap &groupMap) -{ - QString groupFileName = m_instDir + "/instgroups.json"; - - // if there's no group file, fail - if (!QFileInfo(groupFileName).exists()) - return; - - QFile groupFile(groupFileName); - - // if you can't open the file, fail - if (!groupFile.open(QIODevice::ReadOnly)) - { - // An error occurred. Ignore it. - QLOG_ERROR() << "Failed to read instance group file."; - return; - } - - QTextStream in(&groupFile); - QString jsonStr = in.readAll(); - groupFile.close(); - - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonStr.toUtf8(), &error); - - // if the json was bad, fail - if (error.error != QJsonParseError::NoError) - { - QLOG_ERROR() << QString("Failed to parse instance group file: %1 at offset %2") - .arg(error.errorString(), QString::number(error.offset)) - .toUtf8(); - return; - } - - // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) - { - QLOG_WARN() << "Invalid group file. Root entry should be an object."; - return; - } - - QJsonObject rootObj = jsonDoc.object(); - - // Make sure the format version matches, otherwise fail. - if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) - return; - - // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) - { - QLOG_WARN() << "Invalid group list JSON: 'groups' should be an object."; - return; - } - - // Iterate through all the groups. - QJsonObject groupMapping = rootObj.value("groups").toObject(); - for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) - { - QString groupName = iter.key(); - - // If not an object, complain and skip to the next one. - if (!iter.value().isObject()) - { - QLOG_WARN() << QString("Group '%1' in the group list should " - "be an object.") - .arg(groupName) - .toUtf8(); - continue; - } - - QJsonObject groupObj = iter.value().toObject(); - if (!groupObj.value("instances").isArray()) - { - QLOG_WARN() << QString("Group '%1' in the group list is invalid. " - "It should contain an array " - "called 'instances'.") - .arg(groupName) - .toUtf8(); - continue; - } - - // keep a list/set of groups for choosing - m_groups.insert(groupName); - - // Iterate through the list of instances in the group. - QJsonArray instancesArray = groupObj.value("instances").toArray(); - - for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); - iter2++) - { - groupMap[(*iter2).toString()] = groupName; - } - } -} - -QList InstanceList::discoverFTBInstances() -{ - QList records; - QDir dir = QDir(MMC->settings()->get("FTBLauncherDataRoot").toString()); - QDir dataDir = QDir(MMC->settings()->get("FTBRoot").toString()); - if (!dataDir.exists()) - { - QLOG_INFO() << "The FTB directory specified does not exist. Please check your settings"; - return records; - } - else if (!dir.exists()) - { - QLOG_INFO() << "The FTB launcher data directory specified does not exist. Please check your settings"; - return records; - } - dir.cd("ModPacks"); - auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name); - for (auto filename : allFiles) - { - if (!filename.endsWith(".xml")) - continue; - auto fpath = dir.absoluteFilePath(filename); - QFile f(fpath); - QLOG_INFO() << "Discovering FTB instances -- " << fpath; - if (!f.open(QFile::ReadOnly)) - continue; - - // read the FTB packs XML. - QXmlStreamReader reader(&f); - while (!reader.atEnd()) - { - switch (reader.readNext()) - { - case QXmlStreamReader::StartElement: - { - if (reader.name() == "modpack") - { - QXmlStreamAttributes attrs = reader.attributes(); - FTBRecord record; - record.dirName = attrs.value("dir").toString(); - record.instanceDir = dataDir.absoluteFilePath(record.dirName); - record.templateDir = dir.absoluteFilePath(record.dirName); - QDir test(record.instanceDir); - QLOG_DEBUG() << dataDir.absolutePath() << record.instanceDir << record.dirName; - if (!test.exists()) - continue; - record.name = attrs.value("name").toString(); - record.logo = attrs.value("logo").toString(); - record.mcVersion = attrs.value("mcVersion").toString(); - record.description = attrs.value("description").toString(); - records.append(record); - } - break; - } - case QXmlStreamReader::EndElement: - break; - case QXmlStreamReader::Characters: - break; - default: - break; - } - } - f.close(); - } - return records; -} - -void InstanceList::loadFTBInstances(QMap &groupMap, - QList &tempList) -{ - auto records = discoverFTBInstances(); - if (!records.size()) - { - QLOG_INFO() << "No FTB instances to load."; - return; - } - QLOG_INFO() << "Loading FTB instances! -- got " << records.size(); - // process the records we acquired. - for (auto record : records) - { - QLOG_INFO() << "Loading FTB instance from " << record.instanceDir; - QString iconKey = record.logo; - iconKey.remove(QRegularExpression("\\..*")); - MMC->icons()->addIcon(iconKey, iconKey, PathCombine(record.templateDir, record.logo), - MMCIcon::Transient); - - if (!QFileInfo(PathCombine(record.instanceDir, "instance.cfg")).exists()) - { - QLOG_INFO() << "Converting " << record.name << " as new."; - InstancePtr instPtr; - auto &factory = InstanceFactory::get(); - auto version = MMC->minecraftlist()->findVersion(record.mcVersion); - if (!version) - { - QLOG_ERROR() << "Can't load instance " << record.instanceDir - << " because minecraft version " << record.mcVersion - << " can't be resolved."; - continue; - } - auto error = factory.createInstance(instPtr, version, record.instanceDir, - InstanceFactory::FTBInstance); - - if (!instPtr || error != InstanceFactory::NoCreateError) - continue; - - instPtr->setGroupInitial("FTB"); - instPtr->setName(record.name); - instPtr->setIconKey(iconKey); - instPtr->setIntendedVersionId(record.mcVersion); - instPtr->setNotes(record.description); - if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) - continue; - tempList.append(InstancePtr(instPtr)); - } - else - { - QLOG_INFO() << "Loading existing " << record.name; - InstancePtr instPtr; - auto error = InstanceFactory::get().loadInstance(instPtr, record.instanceDir); - if (!instPtr || error != InstanceFactory::NoLoadError) - continue; - instPtr->setGroupInitial("FTB"); - instPtr->setName(record.name); - instPtr->setIconKey(iconKey); - if (instPtr->intendedVersionId() != record.mcVersion) - instPtr->setIntendedVersionId(record.mcVersion); - instPtr->setNotes(record.description); - if(!continueProcessInstance(instPtr, error, record.instanceDir, groupMap)) - continue; - tempList.append(InstancePtr(instPtr)); - } - } -} - -InstanceList::InstListError InstanceList::loadList() -{ - // load the instance groups - QMap groupMap; - loadGroupList(groupMap); - - QList tempList; - { - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, - QDirIterator::FollowSymlinks); - while (iter.hasNext()) - { - QString subDir = iter.next(); - if (!QFileInfo(PathCombine(subDir, "instance.cfg")).exists()) - continue; - QLOG_INFO() << "Loading MultiMC instance from " << subDir; - InstancePtr instPtr; - auto error = InstanceFactory::get().loadInstance(instPtr, subDir); - if(!continueProcessInstance(instPtr, error, subDir, groupMap)) - continue; - tempList.append(instPtr); - } - } - - if (MMC->settings()->get("TrackFTBInstances").toBool()) - { - loadFTBInstances(groupMap, tempList); - } - beginResetModel(); - m_instances.clear(); - for(auto inst: tempList) - { - inst->setParent(this); - connect(inst.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, - SLOT(propertiesChanged(BaseInstance *))); - connect(inst.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); - connect(inst.get(), SIGNAL(nuked(BaseInstance *)), this, - SLOT(instanceNuked(BaseInstance *))); - m_instances.append(inst); - } - endResetModel(); - emit dataIsInvalid(); - return NoError; -} - -/// Clear all instances. Triggers notifications. -void InstanceList::clear() -{ - beginResetModel(); - saveGroupList(); - m_instances.clear(); - endResetModel(); - emit dataIsInvalid(); -} - -void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) -{ - m_instDir = value.toString(); - loadList(); -} - -/// Add an instance. Triggers notifications, returns the new index -int InstanceList::add(InstancePtr t) -{ - beginInsertRows(QModelIndex(), m_instances.size(), m_instances.size()); - m_instances.append(t); - t->setParent(this); - connect(t.get(), SIGNAL(propertiesChanged(BaseInstance *)), this, - SLOT(propertiesChanged(BaseInstance *))); - connect(t.get(), SIGNAL(groupChanged()), this, SLOT(groupChanged())); - connect(t.get(), SIGNAL(nuked(BaseInstance *)), this, SLOT(instanceNuked(BaseInstance *))); - endInsertRows(); - return count() - 1; -} - -InstancePtr InstanceList::getInstanceById(QString instId) const -{ - if (m_instances.isEmpty()) - { - return InstancePtr(); - } - - QListIterator iter(m_instances); - InstancePtr inst; - while (iter.hasNext()) - { - inst = iter.next(); - if (inst->id() == instId) - break; - } - if (inst->id() != instId) - return InstancePtr(); - else - return iter.peekPrevious(); -} - -QModelIndex InstanceList::getInstanceIndexById(const QString &id) const -{ - return index(getInstIndex(getInstanceById(id).get())); -} - -int InstanceList::getInstIndex(BaseInstance *inst) const -{ - for (int i = 0; i < m_instances.count(); i++) - { - if (inst == m_instances[i].get()) - { - return i; - } - } - return -1; -} - -bool InstanceList::continueProcessInstance(InstancePtr instPtr, const int error, - const QDir &dir, QMap &groupMap) -{ - if (error != InstanceFactory::NoLoadError && error != InstanceFactory::NotAnInstance) - { - QString errorMsg = QString("Failed to load instance %1: ") - .arg(QFileInfo(dir.absolutePath()).baseName()) - .toUtf8(); - - switch (error) - { - default: - errorMsg += QString("Unknown instance loader error %1").arg(error); - break; - } - QLOG_ERROR() << errorMsg.toUtf8(); - return false; - } - else if (!instPtr) - { - QLOG_ERROR() << QString("Error loading instance %1. Instance loader returned null.") - .arg(QFileInfo(dir.absolutePath()).baseName()) - .toUtf8(); - return false; - } - else - { - auto iter = groupMap.find(instPtr->id()); - if (iter != groupMap.end()) - { - instPtr->setGroupInitial((*iter)); - } - QLOG_INFO() << "Loaded instance " << instPtr->name() << " from " << dir.absolutePath(); - return true; - } -} - -void InstanceList::instanceNuked(BaseInstance *inst) -{ - int i = getInstIndex(inst); - if (i != -1) - { - beginRemoveRows(QModelIndex(), i, i); - m_instances.removeAt(i); - endRemoveRows(); - } -} - -void InstanceList::propertiesChanged(BaseInstance *inst) -{ - int i = getInstIndex(inst); - if (i != -1) - { - emit dataChanged(index(i), index(i)); - } -} - -InstanceProxyModel::InstanceProxyModel(QObject *parent) : GroupedProxyModel(parent) -{ -} - -bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - BaseInstance *pdataLeft = static_cast(left.internalPointer()); - BaseInstance *pdataRight = static_cast(right.internalPointer()); - QString sortMode = MMC->settings()->get("InstSortMode").toString(); - if (sortMode == "LastLaunch") - { - return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); - } - else - { - return QString::localeAwareCompare(pdataLeft->name(), pdataRight->name()) < 0; - } -} diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h deleted file mode 100644 index f0bbb7ec..00000000 --- a/logic/lists/InstanceList.h +++ /dev/null @@ -1,152 +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 -#include -#include -#include -#include - -#include "logic/BaseInstance.h" - -class BaseInstance; - -class QDir; - -struct FTBRecord -{ - QString dirName; - QString name; - QString logo; - QString mcVersion; - QString description; - QString instanceDir; - QString templateDir; -}; - -class InstanceList : public QAbstractListModel -{ - Q_OBJECT -private: - void loadGroupList(QMap &groupList); - QList discoverFTBInstances(); - void loadFTBInstances(QMap &groupMap, QList & tempList); - -private -slots: - void saveGroupList(); - -public: - explicit InstanceList(const QString &instDir, QObject *parent = 0); - virtual ~InstanceList(); - -public: - QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - - enum AdditionalRoles - { - InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance - InstanceIDRole = 0x34B1CB49 ///< Return id if the instance - }; - /*! - * \brief Error codes returned by functions in the InstanceList class. - * NoError Indicates that no error occurred. - * UnknownError indicates that an unspecified error occurred. - */ - enum InstListError - { - NoError = 0, - UnknownError - }; - - QString instDir() const - { - return m_instDir; - } - - /*! - * \brief Get the instance at index - */ - InstancePtr at(int i) const - { - return m_instances.at(i); - } - ; - - /*! - * \brief Get the count of loaded instances - */ - int count() const - { - return m_instances.count(); - } - ; - - /// Clear all instances. Triggers notifications. - void clear(); - - /// Add an instance. Triggers notifications, returns the new index - int add(InstancePtr t); - - /// Get an instance by ID - InstancePtr getInstanceById(QString id) const; - - QModelIndex getInstanceIndexById(const QString &id) const; - - // FIXME: instead of iterating through all instances and forming a set, keep the set around - QStringList getGroups(); -signals: - void dataIsInvalid(); - -public -slots: - void on_InstFolderChanged(const Setting &setting, QVariant value); - - /*! - * \brief Loads the instance list. Triggers notifications. - */ - InstListError loadList(); - -private -slots: - void propertiesChanged(BaseInstance *inst); - void instanceNuked(BaseInstance *inst); - void groupChanged(); - -private: - int getInstIndex(BaseInstance *inst) const; - - bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir, - QMap &groupMap); - -protected: - QString m_instDir; - QList m_instances; - QSet m_groups; -}; - -class InstanceProxyModel : public GroupedProxyModel -{ -public: - explicit InstanceProxyModel(QObject *parent = 0); - -protected: - virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; -}; diff --git a/logic/lists/JavaVersionList.cpp b/logic/lists/JavaVersionList.cpp deleted file mode 100644 index 4fd0bc19..00000000 --- a/logic/lists/JavaVersionList.cpp +++ /dev/null @@ -1,241 +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 "JavaVersionList.h" -#include "MultiMC.h" - -#include -#include -#include - -#include "logger/QsLog.h" -#include "logic/JavaCheckerJob.h" -#include "logic/JavaUtils.h" - -JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent) -{ -} - -Task *JavaVersionList::getLoadTask() -{ - return new JavaListLoadTask(this); -} - -const BaseVersionPtr JavaVersionList::at(int i) const -{ - return m_vlist.at(i); -} - -bool JavaVersionList::isLoaded() -{ - return m_loaded; -} - -int JavaVersionList::count() const -{ - return m_vlist.count(); -} - -int JavaVersionList::columnCount(const QModelIndex &parent) const -{ - return 3; -} - -QVariant JavaVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - auto version = std::dynamic_pointer_cast(m_vlist[index.row()]); - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case 0: - return version->id; - - case 1: - return version->arch; - - case 2: - return version->path; - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return version->descriptor(); - - case VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); - - default: - return QVariant(); - } -} - -QVariant JavaVersionList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case 0: - return "Version"; - - case 1: - return "Arch"; - - case 2: - return "Path"; - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case 0: - return "The name of the version."; - - case 1: - return "The architecture this version is for."; - - case 2: - return "Path to this Java version."; - - default: - return QVariant(); - } - - default: - return QVariant(); - } -} - -BaseVersionPtr JavaVersionList::getTopRecommended() const -{ - auto first = m_vlist.first(); - if(first != nullptr) - { - return first; - } - else - { - return BaseVersionPtr(); - } -} - -void JavaVersionList::updateListData(QList versions) -{ - beginResetModel(); - m_vlist = versions; - m_loaded = true; - endResetModel(); - // NOW SORT!! - // sort(); -} - -void JavaVersionList::sort() -{ - // NO-OP for now -} - -JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) : Task() -{ - m_list = vlist; - m_currentRecommended = NULL; -} - -JavaListLoadTask::~JavaListLoadTask() -{ -} - -void JavaListLoadTask::executeTask() -{ - setStatus(tr("Detecting Java installations...")); - - JavaUtils ju; - QList candidate_paths = ju.FindJavaPaths(); - - m_job = std::shared_ptr(new JavaCheckerJob("Java detection")); - connect(m_job.get(), SIGNAL(finished(QList)), this, SLOT(javaCheckerFinished(QList))); - connect(m_job.get(), SIGNAL(progress(int, int)), this, SLOT(checkerProgress(int, int))); - - QLOG_DEBUG() << "Probing the following Java paths: "; - int id = 0; - for(QString candidate : candidate_paths) - { - QLOG_DEBUG() << " " << candidate; - - auto candidate_checker = new JavaChecker(); - candidate_checker->path = candidate; - candidate_checker->id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - - id++; - } - - m_job->start(); -} - -void JavaListLoadTask::checkerProgress(int current, int total) -{ - float progress = (current * 100.0) / total; - this->setProgress((int) progress); -} - -void JavaListLoadTask::javaCheckerFinished(QList results) -{ - QList candidates; - - QLOG_DEBUG() << "Found the following valid Java installations:"; - for(JavaCheckResult result : results) - { - if(result.valid) - { - JavaVersionPtr javaVersion(new JavaVersion()); - - javaVersion->id = result.javaVersion; - javaVersion->arch = result.mojangPlatform; - javaVersion->path = result.path; - candidates.append(javaVersion); - - QLOG_DEBUG() << " " << javaVersion->id << javaVersion->arch << javaVersion->path; - } - } - - QList javas_bvp; - for (auto java : candidates) - { - //QLOG_INFO() << java->id << java->arch << " at " << java->path; - BaseVersionPtr bp_java = std::dynamic_pointer_cast(java); - - if (bp_java) - { - javas_bvp.append(java); - } - } - - m_list->updateListData(javas_bvp); - emitSucceeded(); -} diff --git a/logic/lists/JavaVersionList.h b/logic/lists/JavaVersionList.h deleted file mode 100644 index e6cc8e5f..00000000 --- a/logic/lists/JavaVersionList.h +++ /dev/null @@ -1,96 +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 -#include - -#include "BaseVersionList.h" -#include "logic/tasks/Task.h" -#include "logic/JavaCheckerJob.h" - -class JavaListLoadTask; - -struct JavaVersion : public BaseVersion -{ - virtual QString descriptor() - { - return id; - } - - virtual QString name() - { - return id; - } - - virtual QString typeString() const - { - return arch; - } - - QString id; - QString arch; - QString path; -}; - -typedef std::shared_ptr JavaVersionPtr; - -class JavaVersionList : public BaseVersionList -{ - Q_OBJECT -public: - explicit JavaVersionList(QObject *parent = 0); - - virtual Task *getLoadTask(); - virtual bool isLoaded(); - virtual const BaseVersionPtr at(int i) const; - virtual int count() const; - virtual void sort(); - - virtual BaseVersionPtr getTopRecommended() const; - - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int columnCount(const QModelIndex &parent) const; - -public -slots: - virtual void updateListData(QList versions); - -protected: - QList m_vlist; - - bool m_loaded = false; -}; - -class JavaListLoadTask : public Task -{ - Q_OBJECT - -public: - explicit JavaListLoadTask(JavaVersionList *vlist); - ~JavaListLoadTask(); - - virtual void executeTask(); -public slots: - void javaCheckerFinished(QList results); - void checkerProgress(int current, int total); - -protected: - std::shared_ptr m_job; - JavaVersionList *m_list; - JavaVersion *m_currentRecommended; -}; diff --git a/logic/lists/LwjglVersionList.cpp b/logic/lists/LwjglVersionList.cpp deleted file mode 100644 index df46d7be..00000000 --- a/logic/lists/LwjglVersionList.cpp +++ /dev/null @@ -1,199 +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 "LwjglVersionList.h" -#include "MultiMC.h" - -#include -#include -#include - -#include "logger/QsLog.h" - -#define RSS_URL "http://sourceforge.net/api/file/index/project-id/58488/mtime/desc/rss" - -LWJGLVersionList::LWJGLVersionList(QObject *parent) : QAbstractListModel(parent) -{ - setLoading(false); -} - -QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - const PtrLWJGLVersion version = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - return version->name(); - - case Qt::ToolTipRole: - return version->url(); - - default: - return QVariant(); - } -} - -QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - return "Version"; - - case Qt::ToolTipRole: - return "LWJGL version name."; - - default: - return QVariant(); - } -} - -int LWJGLVersionList::columnCount(const QModelIndex &parent) const -{ - return 1; -} - -bool LWJGLVersionList::isLoading() const -{ - return m_loading; -} - -void LWJGLVersionList::loadList() -{ - Q_ASSERT_X(!m_loading, "loadList", "list is already loading (m_loading is true)"); - - setLoading(true); - auto worker = MMC->qnam(); - QNetworkRequest req(QUrl(RSS_URL)); - req.setRawHeader("Accept", "text/xml"); - req.setRawHeader("User-Agent", "MultiMC/5.0 (Uncached)"); - reply = worker->get(req); - connect(reply, SIGNAL(finished()), SLOT(netRequestComplete())); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -void LWJGLVersionList::netRequestComplete() -{ - if (reply->error() == QNetworkReply::NoError) - { - QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip"); - Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid"); - - QDomDocument doc; - - QString xmlErrorMsg; - int errorLine; - if (!doc.setContent(reply->readAll(), false, &xmlErrorMsg, &errorLine)) - { - failed("Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + - QString::number(errorLine)); - setLoading(false); - return; - } - - QDomNodeList items = doc.elementsByTagName("item"); - - QList tempList; - - for (int i = 0; i < items.length(); i++) - { - Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", - "XML element isn't an element... wat?"); - - QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link"); - if (linkElement.isNull()) - { - QLOG_INFO() << "Link element" << i << "in RSS feed doesn't exist! Skipping."; - continue; - } - - QString link = linkElement.text(); - - // Make sure it's a download link. - if (link.endsWith("/download") && link.contains(lwjglRegex)) - { - QString name = link.mid(lwjglRegex.indexIn(link) + 6); - // Subtract 4 here to remove the .zip file extension. - name = name.left(lwjglRegex.matchedLength() - 10); - - QUrl url(link); - if (!url.isValid()) - { - QLOG_INFO() << "LWJGL version URL isn't valid:" << link << "Skipping."; - continue; - } - - tempList.append(LWJGLVersion::Create(name, link)); - } - } - - beginResetModel(); - m_vlist.swap(tempList); - endResetModel(); - - QLOG_INFO() << "Loaded LWJGL list."; - finished(); - } - else - { - failed("Failed to load LWJGL list. Network error: " + reply->errorString()); - } - - setLoading(false); - reply->deleteLater(); -} - -const PtrLWJGLVersion LWJGLVersionList::getVersion(const QString &versionName) -{ - for (int i = 0; i < count(); i++) - { - QString name = at(i)->name(); - if (name == versionName) - return at(i); - } - return PtrLWJGLVersion(); -} - -void LWJGLVersionList::failed(QString msg) -{ - QLOG_INFO() << msg; - emit loadListFailed(msg); -} - -void LWJGLVersionList::finished() -{ - emit loadListFinished(); -} - -void LWJGLVersionList::setLoading(bool loading) -{ - m_loading = loading; - emit loadingStateUpdated(m_loading); -} diff --git a/logic/lists/LwjglVersionList.h b/logic/lists/LwjglVersionList.h deleted file mode 100644 index fa57e8eb..00000000 --- a/logic/lists/LwjglVersionList.h +++ /dev/null @@ -1,148 +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 -#include -#include -#include - -#include - -class LWJGLVersion; -typedef std::shared_ptr PtrLWJGLVersion; - -class LWJGLVersion : public QObject -{ - Q_OBJECT - - LWJGLVersion(const QString &name, const QString &url, QObject *parent = 0) - : QObject(parent), m_name(name), m_url(url) - { - } - -public: - - static PtrLWJGLVersion Create(const QString &name, const QString &url, QObject *parent = 0) - { - return PtrLWJGLVersion(new LWJGLVersion(name, url, parent)); - } - ; - - QString name() const - { - return m_name; - } - - QString url() const - { - return m_url; - } - -protected: - QString m_name; - QString m_url; -}; - -class LWJGLVersionList : public QAbstractListModel -{ - Q_OBJECT -public: - explicit LWJGLVersionList(QObject *parent = 0); - - bool isLoaded() - { - return m_vlist.length() > 0; - } - - const PtrLWJGLVersion getVersion(const QString &versionName); - PtrLWJGLVersion at(int index) - { - return m_vlist[index]; - } - const PtrLWJGLVersion at(int index) const - { - return m_vlist[index]; - } - - int count() const - { - return m_vlist.length(); - } - - virtual QVariant data(const QModelIndex &index, int role) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual int rowCount(const QModelIndex &parent) const - { - return count(); - } - virtual int columnCount(const QModelIndex &parent) const; - - virtual bool isLoading() const; - virtual bool errored() const - { - return m_errored; - } - - virtual QString lastErrorMsg() const - { - return m_lastErrorMsg; - } - -public -slots: - /*! - * Loads the version list. - * This is done asynchronously. On success, the loadListFinished() signal will - * be emitted. The list model will be reset as well, resulting in the modelReset() - * signal being emitted. Note that the model will be reset before loadListFinished() is - * emitted. - * If loading the list failed, the loadListFailed(QString msg), - * signal will be emitted. - */ - virtual void loadList(); - -signals: - /*! - * Emitted when the list either starts or finishes loading. - * \param loading Whether or not the list is loading. - */ - void loadingStateUpdated(bool loading); - - void loadListFinished(); - - void loadListFailed(QString msg); - -private: - QList m_vlist; - - QNetworkReply *m_netReply; - QNetworkReply *reply; - - bool m_loading; - bool m_errored; - QString m_lastErrorMsg; - - void failed(QString msg); - - void finished(); - - void setLoading(bool loading); - -private -slots: - virtual void netRequestComplete(); -}; diff --git a/logic/lists/MinecraftVersionList.cpp b/logic/lists/MinecraftVersionList.cpp deleted file mode 100644 index cdf5fa77..00000000 --- a/logic/lists/MinecraftVersionList.cpp +++ /dev/null @@ -1,330 +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 "MinecraftVersionList.h" -#include "MultiMC.h" -#include "logic/net/URLConstants.h" -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include - -inline QDateTime timeFromS3Time(QString str) -{ - return QDateTime::fromString(str, Qt::ISODate); -} - -MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) -{ - loadBuiltinList(); -} - -Task *MinecraftVersionList::getLoadTask() -{ - return new MCVListLoadTask(this); -} - -bool MinecraftVersionList::isLoaded() -{ - return m_loaded; -} - -const BaseVersionPtr MinecraftVersionList::at(int i) const -{ - return m_vlist.at(i); -} - -int MinecraftVersionList::count() const -{ - return m_vlist.count(); -} - -static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) -{ - auto left = std::dynamic_pointer_cast(first); - auto right = std::dynamic_pointer_cast(second); - return left->timestamp > right->timestamp; -} - -void MinecraftVersionList::sortInternal() -{ - qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); -} - -void MinecraftVersionList::loadBuiltinList() -{ - // grab the version list data from internal resources. - QResource versionList(":/versions/minecraft.json"); - QFile filez(versionList.absoluteFilePath()); - filez.open(QIODevice::ReadOnly); - auto data = filez.readAll(); - - // parse the data as json - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - QJsonObject root = jsonDoc.object(); - - // parse all the versions - for (const auto version : MMCJson::ensureArray(root.value("versions"))) - { - QJsonObject versionObj = version.toObject(); - QString versionID = versionObj.value("id").toString(""); - QString versionTimeStr = versionObj.value("releaseTime").toString(""); - QString versionTypeStr = versionObj.value("type").toString(""); - QSet traits; - if (versionObj.contains("+traits")) - { - for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) - { - traits.insert(MMCJson::ensureString(traitVal)); - } - } - if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) - { - // FIXME: log this somewhere - continue; - } - // Parse the timestamp. - QDateTime versionTime = timeFromS3Time(versionTimeStr); - if (!versionTime.isValid()) - { - // FIXME: log this somewhere - continue; - } - // Get the download URL. - QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - - // main class and applet class - QString mainClass = versionObj.value("type").toString(""); - QString appletClass = versionObj.value("type").toString(""); - - // Now, we construct the version object and add it to the list. - std::shared_ptr mcVersion(new MinecraftVersion()); - mcVersion->m_name = mcVersion->m_descriptor = versionID; - mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); - mcVersion->download_url = dlUrl; - mcVersion->is_builtin = true; - mcVersion->m_appletClass = appletClass; - mcVersion->m_mainClass = mainClass; - mcVersion->m_traits = traits; - m_vlist.append(mcVersion); - } -} - -void MinecraftVersionList::sort() -{ - beginResetModel(); - sortInternal(); - endResetModel(); -} - -BaseVersionPtr MinecraftVersionList::getLatestStable() const -{ - for (int i = 0; i < m_vlist.length(); i++) - { - auto ver = std::dynamic_pointer_cast(m_vlist.at(i)); - if (ver->is_latest && !ver->is_snapshot) - { - return m_vlist.at(i); - } - } - return BaseVersionPtr(); -} - -void MinecraftVersionList::updateListData(QList versions) -{ - beginResetModel(); - for (auto version : versions) - { - auto descr = version->descriptor(); - for (auto builtin_v : m_vlist) - { - if (descr == builtin_v->descriptor()) - { - goto SKIP_THIS_ONE; - } - } - m_vlist.append(version); - SKIP_THIS_ONE: - { - } - } - m_loaded = true; - sortInternal(); - endResetModel(); -} - -inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) -{ - QDomNodeList elementList = parent.elementsByTagName(tagname); - if (elementList.count()) - return elementList.at(0).toElement(); - else - return QDomElement(); -} - -MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) -{ - m_list = vlist; - m_currentStable = NULL; - vlistReply = nullptr; -} - -MCVListLoadTask::~MCVListLoadTask() -{ -} - -void MCVListLoadTask::executeTask() -{ - setStatus(tr("Loading instance version list...")); - auto worker = MMC->qnam(); - vlistReply = worker->get(QNetworkRequest( - QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); - connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); -} - -void MCVListLoadTask::list_downloaded() -{ - if (vlistReply->error() != QNetworkReply::NoError) - { - vlistReply->deleteLater(); - emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); - return; - } - - auto foo = vlistReply->readAll(); - QJsonParseError jsonError; - QLOG_INFO() << foo; - QJsonDocument jsonDoc = QJsonDocument::fromJson(foo, &jsonError); - vlistReply->deleteLater(); - - 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(); - - QString latestReleaseID = "INVALID"; - QString latestSnapshotID = "INVALID"; - try - { - QJsonObject latest = MMCJson::ensureObject(root.value("latest")); - latestReleaseID = MMCJson::ensureString(latest.value("release")); - latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); - } - catch (MMCError &err) - { - QLOG_ERROR() - << tr("Error parsing version list JSON: couldn't determine latest versions"); - } - - // Now, get the array of versions. - if (!root.value("versions").isArray()) - { - emitFailed( - "Error parsing version list JSON: version list object is missing 'versions' array"); - return; - } - QJsonArray versions = root.value("versions").toArray(); - - QList tempList; - for (auto version : versions) - { - bool is_snapshot = false; - bool is_latest = false; - - // Load the version info. - if (!version.isObject()) - { - // FIXME: log this somewhere - continue; - } - QJsonObject versionObj = version.toObject(); - QString versionID = versionObj.value("id").toString(""); - QString versionTimeStr = versionObj.value("releaseTime").toString(""); - QString versionTypeStr = versionObj.value("type").toString(""); - if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) - { - // FIXME: log this somewhere - continue; - } - - // Parse the timestamp. - QDateTime versionTime = timeFromS3Time(versionTimeStr); - if (!versionTime.isValid()) - { - // FIXME: log this somewhere - continue; - } - // OneSix or Legacy. use filter to determine type - if (versionTypeStr == "release") - { - is_latest = (versionID == latestReleaseID); - is_snapshot = false; - } - else if (versionTypeStr == "snapshot") // It's a snapshot... yay - { - is_latest = (versionID == latestSnapshotID); - is_snapshot = true; - } - else if (versionTypeStr == "old_alpha") - { - is_latest = false; - is_snapshot = false; - } - else if (versionTypeStr == "old_beta") - { - is_latest = false; - is_snapshot = false; - } - else - { - // FIXME: log this somewhere - continue; - } - // Get the download URL. - QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; - - // Now, we construct the version object and add it to the list. - std::shared_ptr mcVersion(new MinecraftVersion()); - mcVersion->m_name = mcVersion->m_descriptor = versionID; - mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); - mcVersion->download_url = dlUrl; - mcVersion->is_latest = is_latest; - mcVersion->is_snapshot = is_snapshot; - tempList.append(mcVersion); - } - m_list->updateListData(tempList); - - emitSucceeded(); - return; -} diff --git a/logic/lists/MinecraftVersionList.h b/logic/lists/MinecraftVersionList.h deleted file mode 100644 index 4698fd8f..00000000 --- a/logic/lists/MinecraftVersionList.h +++ /dev/null @@ -1,76 +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 -#include -#include - -#include "BaseVersionList.h" -#include "logic/tasks/Task.h" -#include "logic/MinecraftVersion.h" - -class MCVListLoadTask; -class QNetworkReply; - -class MinecraftVersionList : public BaseVersionList -{ - Q_OBJECT -private: - void sortInternal(); - void loadBuiltinList(); -public: - friend class MCVListLoadTask; - - explicit MinecraftVersionList(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; - -protected: - QList m_vlist; - - bool m_loaded = false; - -protected -slots: - virtual void updateListData(QList versions); -}; - -class MCVListLoadTask : public Task -{ - Q_OBJECT - -public: - explicit MCVListLoadTask(MinecraftVersionList *vlist); - ~MCVListLoadTask(); - - virtual void executeTask(); - -protected -slots: - void list_downloaded(); - -protected: - QNetworkReply *vlistReply; - MinecraftVersionList *m_list; - MinecraftVersion *m_currentStable; -}; diff --git a/logic/liteloader/LiteLoaderInstaller.cpp b/logic/liteloader/LiteLoaderInstaller.cpp index 1a94e644..0a16ac6d 100644 --- a/logic/liteloader/LiteLoaderInstaller.cpp +++ b/logic/liteloader/LiteLoaderInstaller.cpp @@ -20,8 +20,8 @@ #include "logger/QsLog.h" -#include "logic/VersionFinal.h" -#include "logic/OneSixLibrary.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixLibrary.h" #include "logic/OneSixInstance.h" #include "MultiMC.h" #include "logic/liteloader/LiteLoaderVersionList.h" diff --git a/logic/liteloader/LiteLoaderVersionList.h b/logic/liteloader/LiteLoaderVersionList.h index aa35f1ca..0aecc3e1 100644 --- a/logic/liteloader/LiteLoaderVersionList.h +++ b/logic/liteloader/LiteLoaderVersionList.h @@ -19,9 +19,9 @@ #include #include -#include "logic/lists/BaseVersionList.h" -#include "logic/tasks/Task.h" #include "logic/BaseVersion.h" +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" #include "logic/net/NetJob.h" class LLListLoadTask; diff --git a/logic/minecraft/MinecraftVersion.h b/logic/minecraft/MinecraftVersion.h new file mode 100644 index 00000000..3be25912 --- /dev/null +++ b/logic/minecraft/MinecraftVersion.h @@ -0,0 +1,92 @@ +/* Copyright 2013 Andrew Okin + * + * 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 "logic/BaseVersion.h" +#include +#include + +struct MinecraftVersion : public BaseVersion +{ + /// 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; + + /// is this the latest version? + bool is_latest = false; + + /// is this a snapshot? + bool is_snapshot = false; + + /// is this a built-in version that comes with MultiMC? + bool is_builtin = false; + + /// the human readable version name + QString m_name; + + /// the version ID. + QString m_descriptor; + + /// version traits. generally launcher business... + QSet m_traits; + + /// The main class this version uses (if any, can be empty). + QString m_mainClass; + + /// The applet class this version uses (if any, can be empty). + QString m_appletClass; + + bool usesLegacyLauncher() + { + return m_traits.contains("legacyLaunch") || m_traits.contains("aplhaLaunch"); + } + + virtual QString descriptor() override + { + return m_descriptor; + } + + virtual QString name() override + { + return m_name; + } + + virtual QString typeString() const override + { + if (is_latest && is_snapshot) + { + return QObject::tr("Latest snapshot"); + } + else if(is_latest) + { + return QObject::tr("Latest release"); + } + else if(is_snapshot) + { + return QObject::tr("Snapshot"); + } + else if(is_builtin) + { + return QObject::tr("Museum piece"); + } + else + { + return QObject::tr("Regular release"); + } + } +}; diff --git a/logic/minecraft/MinecraftVersionList.cpp b/logic/minecraft/MinecraftVersionList.cpp new file mode 100644 index 00000000..cdf5fa77 --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.cpp @@ -0,0 +1,330 @@ +/* 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 "MinecraftVersionList.h" +#include "MultiMC.h" +#include "logic/net/URLConstants.h" +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +inline QDateTime timeFromS3Time(QString str) +{ + return QDateTime::fromString(str, Qt::ISODate); +} + +MinecraftVersionList::MinecraftVersionList(QObject *parent) : BaseVersionList(parent) +{ + loadBuiltinList(); +} + +Task *MinecraftVersionList::getLoadTask() +{ + return new MCVListLoadTask(this); +} + +bool MinecraftVersionList::isLoaded() +{ + return m_loaded; +} + +const BaseVersionPtr MinecraftVersionList::at(int i) const +{ + return m_vlist.at(i); +} + +int MinecraftVersionList::count() const +{ + return m_vlist.count(); +} + +static bool cmpVersions(BaseVersionPtr first, BaseVersionPtr second) +{ + auto left = std::dynamic_pointer_cast(first); + auto right = std::dynamic_pointer_cast(second); + return left->timestamp > right->timestamp; +} + +void MinecraftVersionList::sortInternal() +{ + qSort(m_vlist.begin(), m_vlist.end(), cmpVersions); +} + +void MinecraftVersionList::loadBuiltinList() +{ + // grab the version list data from internal resources. + QResource versionList(":/versions/minecraft.json"); + QFile filez(versionList.absoluteFilePath()); + filez.open(QIODevice::ReadOnly); + auto data = filez.readAll(); + + // parse the data as json + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + QJsonObject root = jsonDoc.object(); + + // parse all the versions + for (const auto version : MMCJson::ensureArray(root.value("versions"))) + { + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTimeStr = versionObj.value("releaseTime").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + QSet traits; + if (versionObj.contains("+traits")) + { + for (auto traitVal : MMCJson::ensureArray(versionObj.value("+traits"))) + { + traits.insert(MMCJson::ensureString(traitVal)); + } + } + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + // Parse the timestamp. + QDateTime versionTime = timeFromS3Time(versionTimeStr); + if (!versionTime.isValid()) + { + // FIXME: log this somewhere + continue; + } + // Get the download URL. + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + // main class and applet class + QString mainClass = versionObj.value("type").toString(""); + QString appletClass = versionObj.value("type").toString(""); + + // Now, we construct the version object and add it to the list. + std::shared_ptr mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); + mcVersion->download_url = dlUrl; + mcVersion->is_builtin = true; + mcVersion->m_appletClass = appletClass; + mcVersion->m_mainClass = mainClass; + mcVersion->m_traits = traits; + m_vlist.append(mcVersion); + } +} + +void MinecraftVersionList::sort() +{ + beginResetModel(); + sortInternal(); + endResetModel(); +} + +BaseVersionPtr MinecraftVersionList::getLatestStable() const +{ + for (int i = 0; i < m_vlist.length(); i++) + { + auto ver = std::dynamic_pointer_cast(m_vlist.at(i)); + if (ver->is_latest && !ver->is_snapshot) + { + return m_vlist.at(i); + } + } + return BaseVersionPtr(); +} + +void MinecraftVersionList::updateListData(QList versions) +{ + beginResetModel(); + for (auto version : versions) + { + auto descr = version->descriptor(); + for (auto builtin_v : m_vlist) + { + if (descr == builtin_v->descriptor()) + { + goto SKIP_THIS_ONE; + } + } + m_vlist.append(version); + SKIP_THIS_ONE: + { + } + } + m_loaded = true; + sortInternal(); + endResetModel(); +} + +inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname) +{ + QDomNodeList elementList = parent.elementsByTagName(tagname); + if (elementList.count()) + return elementList.at(0).toElement(); + else + return QDomElement(); +} + +MCVListLoadTask::MCVListLoadTask(MinecraftVersionList *vlist) +{ + m_list = vlist; + m_currentStable = NULL; + vlistReply = nullptr; +} + +MCVListLoadTask::~MCVListLoadTask() +{ +} + +void MCVListLoadTask::executeTask() +{ + setStatus(tr("Loading instance version list...")); + auto worker = MMC->qnam(); + vlistReply = worker->get(QNetworkRequest( + QUrl("http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + "versions.json"))); + connect(vlistReply, SIGNAL(finished()), this, SLOT(list_downloaded())); +} + +void MCVListLoadTask::list_downloaded() +{ + if (vlistReply->error() != QNetworkReply::NoError) + { + vlistReply->deleteLater(); + emitFailed("Failed to load Minecraft main version list" + vlistReply->errorString()); + return; + } + + auto foo = vlistReply->readAll(); + QJsonParseError jsonError; + QLOG_INFO() << foo; + QJsonDocument jsonDoc = QJsonDocument::fromJson(foo, &jsonError); + vlistReply->deleteLater(); + + 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(); + + QString latestReleaseID = "INVALID"; + QString latestSnapshotID = "INVALID"; + try + { + QJsonObject latest = MMCJson::ensureObject(root.value("latest")); + latestReleaseID = MMCJson::ensureString(latest.value("release")); + latestSnapshotID = MMCJson::ensureString(latest.value("snapshot")); + } + catch (MMCError &err) + { + QLOG_ERROR() + << tr("Error parsing version list JSON: couldn't determine latest versions"); + } + + // Now, get the array of versions. + if (!root.value("versions").isArray()) + { + emitFailed( + "Error parsing version list JSON: version list object is missing 'versions' array"); + return; + } + QJsonArray versions = root.value("versions").toArray(); + + QList tempList; + for (auto version : versions) + { + bool is_snapshot = false; + bool is_latest = false; + + // Load the version info. + if (!version.isObject()) + { + // FIXME: log this somewhere + continue; + } + QJsonObject versionObj = version.toObject(); + QString versionID = versionObj.value("id").toString(""); + QString versionTimeStr = versionObj.value("releaseTime").toString(""); + QString versionTypeStr = versionObj.value("type").toString(""); + if (versionID.isEmpty() || versionTimeStr.isEmpty() || versionTypeStr.isEmpty()) + { + // FIXME: log this somewhere + continue; + } + + // Parse the timestamp. + QDateTime versionTime = timeFromS3Time(versionTimeStr); + if (!versionTime.isValid()) + { + // FIXME: log this somewhere + continue; + } + // OneSix or Legacy. use filter to determine type + if (versionTypeStr == "release") + { + is_latest = (versionID == latestReleaseID); + is_snapshot = false; + } + else if (versionTypeStr == "snapshot") // It's a snapshot... yay + { + is_latest = (versionID == latestSnapshotID); + is_snapshot = true; + } + else if (versionTypeStr == "old_alpha") + { + is_latest = false; + is_snapshot = false; + } + else if (versionTypeStr == "old_beta") + { + is_latest = false; + is_snapshot = false; + } + else + { + // FIXME: log this somewhere + continue; + } + // Get the download URL. + QString dlUrl = "http://" + URLConstants::AWS_DOWNLOAD_VERSIONS + versionID + "/"; + + // Now, we construct the version object and add it to the list. + std::shared_ptr mcVersion(new MinecraftVersion()); + mcVersion->m_name = mcVersion->m_descriptor = versionID; + mcVersion->timestamp = versionTime.toMSecsSinceEpoch(); + mcVersion->download_url = dlUrl; + mcVersion->is_latest = is_latest; + mcVersion->is_snapshot = is_snapshot; + tempList.append(mcVersion); + } + m_list->updateListData(tempList); + + emitSucceeded(); + return; +} diff --git a/logic/minecraft/MinecraftVersionList.h b/logic/minecraft/MinecraftVersionList.h new file mode 100644 index 00000000..18b9b21e --- /dev/null +++ b/logic/minecraft/MinecraftVersionList.h @@ -0,0 +1,76 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include "logic/BaseVersionList.h" +#include "logic/tasks/Task.h" +#include "logic/minecraft/MinecraftVersion.h" + +class MCVListLoadTask; +class QNetworkReply; + +class MinecraftVersionList : public BaseVersionList +{ + Q_OBJECT +private: + void sortInternal(); + void loadBuiltinList(); +public: + friend class MCVListLoadTask; + + explicit MinecraftVersionList(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; + +protected: + QList m_vlist; + + bool m_loaded = false; + +protected +slots: + virtual void updateListData(QList versions); +}; + +class MCVListLoadTask : public Task +{ + Q_OBJECT + +public: + explicit MCVListLoadTask(MinecraftVersionList *vlist); + ~MCVListLoadTask(); + + virtual void executeTask(); + +protected +slots: + void list_downloaded(); + +protected: + QNetworkReply *vlistReply; + MinecraftVersionList *m_list; + MinecraftVersion *m_currentStable; +}; diff --git a/logic/minecraft/OneSixLibrary.cpp b/logic/minecraft/OneSixLibrary.cpp new file mode 100644 index 00000000..45fa169e --- /dev/null +++ b/logic/minecraft/OneSixLibrary.cpp @@ -0,0 +1,274 @@ +/* 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 + +#include "OneSixLibrary.h" +#include "OneSixRule.h" +#include "OpSys.h" +#include "logic/net/URLConstants.h" +#include +#include +#include "logger/QsLog.h" + +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 = minVersion = 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(const QString &name) +{ + m_name = name; +} +void OneSixLibrary::setBaseUrl(const QString &base_url) +{ + m_base_url = base_url; +} +void OneSixLibrary::setIsNative() +{ + m_is_native = true; +} +void OneSixLibrary::addNative(OpSys os, const QString &suffix) +{ + m_is_native = true; + m_native_suffixes[os] = suffix; +} +void OneSixLibrary::clearSuffixes() +{ + m_native_suffixes.clear(); +} +void OneSixLibrary::setRules(QList> rules) +{ + m_rules = rules; +} +bool OneSixLibrary::isActive() const +{ + return m_is_active; +} +bool OneSixLibrary::isNative() const +{ + return m_is_native; +} +QString OneSixLibrary::downloadUrl() const +{ + if (m_absolute_url.size()) + return m_absolute_url; + return m_download_url; +} +QString OneSixLibrary::storagePath() const +{ + return m_storage_path; +} + +void OneSixLibrary::setAbsoluteUrl(const QString &absolute_url) +{ + m_absolute_url = absolute_url; +} + +QString OneSixLibrary::absoluteUrl() const +{ + return m_absolute_url; +} + +void OneSixLibrary::setHint(const QString &hint) +{ + m_hint = hint; +} + +QString OneSixLibrary::hint() const +{ + return m_hint; +} + +QStringList OneSixLibrary::files() +{ + QStringList retval; + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + retval.append(cooked_storage); + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + retval.append(cooked_storage); + } + else + retval.append(storage); + return retval; +} + +bool OneSixLibrary::filesExist(const QDir &base) +{ + auto libFiles = files(); + for(auto file: libFiles) + { + QFileInfo info(base, file); + QLOG_WARN() << info.absoluteFilePath() << "doesn't exist"; + if (!info.exists()) + return false; + } + return true; +} + +bool OneSixLibrary::extractTo(QString target_dir) +{ + QString storage = storagePath(); + if (storage.contains("${arch}")) + { + QString cooked_storage = storage; + cooked_storage.replace("${arch}", "32"); + QString origin = PathCombine("libraries", cooked_storage); + QString target_dir_cooked = PathCombine(target_dir, "32"); + if (!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + cooked_storage = storage; + cooked_storage.replace("${arch}", "64"); + origin = PathCombine("libraries", cooked_storage); + target_dir_cooked = PathCombine(target_dir, "64"); + if (!ensureFolderPathExists(target_dir_cooked)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir_cooked; + return false; + } + if (JlCompress::extractWithExceptions(origin, target_dir_cooked, extract_excludes) + .isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + origin; + return false; + } + } + else + { + if (!ensureFolderPathExists(target_dir)) + { + QLOG_ERROR() << "Couldn't create folder " + target_dir; + return false; + } + QString path = PathCombine("libraries", storage); + if (JlCompress::extractWithExceptions(path, target_dir, extract_excludes).isEmpty()) + { + QLOG_ERROR() << "Couldn't extract " + path; + return false; + } + } + return true; +} + +QJsonObject OneSixLibrary::toJson() +{ + QJsonObject libRoot; + libRoot.insert("name", m_name); + if (m_absolute_url.size()) + libRoot.insert("MMC-absoluteUrl", m_absolute_url); + if (m_hint.size()) + libRoot.insert("MMC-hint", m_hint); + if (m_base_url != "http://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::AWS_DOWNLOAD_LIBRARIES && + m_base_url != "https://" + URLConstants::LIBRARY_BASE && !m_base_url.isEmpty()) + { + 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/minecraft/OneSixLibrary.h b/logic/minecraft/OneSixLibrary.h new file mode 100644 index 00000000..13df0606 --- /dev/null +++ b/logic/minecraft/OneSixLibrary.h @@ -0,0 +1,148 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "logic/net/URLConstants.h" +#include "logic/minecraft/OpSys.h" + +class Rule; + +class OneSixLibrary; +typedef std::shared_ptr OneSixLibraryPtr; + +class OneSixLibrary +{ +private: + // basic values used internally (so far) + QString m_name; + QString m_base_url = "https://" + URLConstants::LIBRARY_BASE; + QList> m_rules; + + // custom values + /// absolute URL. takes precedence over m_download_path, if defined + QString m_absolute_url; + /// type hint - modifies how the library is treated + 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 m_native_suffixes; + +public: + QStringList extract_excludes; + QString minVersion; + + enum DependType + { + Soft, + Hard + }; + DependType dependType; + +public: + /// Constructor + OneSixLibrary(const QString &name, const DependType type = Soft) + { + m_name = name; + dependType = type; + } + + /// Returns the raw name field + QString rawName() const + { + return m_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(const QString &name); + /// get a decent-looking name + QString name() const + { + return m_decentname; + } + /// get a decent-looking version + QString version() const + { + return m_decentversion; + } + /// what kind of library is it? (for display) + QString type() const + { + return m_decenttype; + } + /// Set the url base for downloads + void setBaseUrl(const 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, const QString &suffix); + /// Clears all suffixes + void clearSuffixes(); + /// Set the load rules + void setRules(QList> rules); + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + /// Returns true if the library is native + bool isNative() const; + /// Get the URL to download the library from + QString downloadUrl() const; + /// Get the relative path where the library should be saved + QString storagePath() const; + + /// set an absolute URL for the library. This is an MMC extension. + void setAbsoluteUrl(const QString &absolute_url); + QString absoluteUrl() const; + + /// set a hint about how to treat the library. This is an MMC extension. + void setHint(const QString &hint); + QString hint() const; + + bool extractTo(QString target_dir); + bool filesExist(const QDir &base); + QStringList files(); +}; diff --git a/logic/minecraft/OneSixRule.cpp b/logic/minecraft/OneSixRule.cpp new file mode 100644 index 00000000..d8d13b50 --- /dev/null +++ b/logic/minecraft/OneSixRule.cpp @@ -0,0 +1,89 @@ +/* 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 +#include + +#include "OneSixRule.h" + +QList> rulesFromJsonV4(const QJsonObject &objectWithRules) +{ + QList> rules; + auto rulesVal = objectWithRules.value("rules"); + if (!rulesVal.isArray()) + return rules; + + QJsonArray ruleList = rulesVal.toArray(); + for (auto ruleVal : ruleList) + { + std::shared_ptr 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)); + } + return rules; +} + +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; +} diff --git a/logic/minecraft/OneSixRule.h b/logic/minecraft/OneSixRule.h new file mode 100644 index 00000000..2c569b9f --- /dev/null +++ b/logic/minecraft/OneSixRule.h @@ -0,0 +1,98 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "logic/minecraft/OneSixLibrary.h" + +enum RuleAction +{ + Allow, + Disallow, + Defer +}; + +RuleAction RuleAction_fromString(QString); +QList> rulesFromJsonV4(const QJsonObject &objectWithRules); + +class Rule +{ +protected: + RuleAction m_result; + virtual bool applies(OneSixLibrary *parent) = 0; + +public: + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(OneSixLibrary *parent) + { + if (applies(parent)) + return m_result; + else + return Defer; + } + ; +}; + +class OsRule : public Rule +{ +private: + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; + +protected: + virtual bool applies(OneSixLibrary *) + { + return (m_system == currentSystem); + } + OsRule(RuleAction result, OpSys system, QString version_regexp) + : Rule(result), m_system(system), m_version_regexp(version_regexp) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result, OpSys system, + QString version_regexp) + { + return std::shared_ptr(new OsRule(result, system, version_regexp)); + } +}; + +class ImplicitRule : public Rule +{ +protected: + virtual bool applies(OneSixLibrary *) + { + return true; + } + ImplicitRule(RuleAction result) : Rule(result) + { + } + +public: + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } +}; diff --git a/logic/minecraft/OneSixVersionBuilder.cpp b/logic/minecraft/OneSixVersionBuilder.cpp new file mode 100644 index 00000000..da8f956c --- /dev/null +++ b/logic/minecraft/OneSixVersionBuilder.cpp @@ -0,0 +1,249 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logic/minecraft/OneSixVersionBuilder.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixRule.h" +#include "logic/minecraft/VersionFile.h" + +#include "logic/OneSixInstance.h" +#include "logic/MMCJson.h" + +#include "logger/QsLog.h" + +OneSixVersionBuilder::OneSixVersionBuilder() +{ +} + +void OneSixVersionBuilder::build(VersionFinal *version, OneSixInstance *instance, const QStringList &external) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = instance; + builder.buildInternal(external); +} + +void OneSixVersionBuilder::readJsonAndApplyToVersion(VersionFinal *version, + const QJsonObject &obj) +{ + OneSixVersionBuilder builder; + builder.m_version = version; + builder.m_instance = 0; + builder.readJsonAndApply(obj); +} + +void OneSixVersionBuilder::buildInternal(const QStringList &external) +{ + m_version->versionFiles.clear(); + + QDir root(m_instance->instanceRoot()); + QDir patches(root.absoluteFilePath("patches/")); + + // if we do external files, do just those. + if (!external.isEmpty()) + { + int externalOrder = -1; + for (auto fileName : external) + { + QLOG_INFO() << "Reading" << fileName; + auto file = + parseJsonFile(QFileInfo(fileName), false, fileName.endsWith("pack.json")); + file->name = QFileInfo(fileName).fileName(); + file->fileId = "org.multimc.external." + file->name; + file->order = (externalOrder += 1); + file->version = QString(); + file->mcVersion = QString(); + m_version->versionFiles.append(file); + } + } + // else, if there's custom json, we just do that. + else if (QFile::exists(root.absoluteFilePath("custom.json"))) + { + QLOG_INFO() << "Reading custom.json"; + auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("custom.json")), false); + file->name = "custom.json"; + file->filename = "custom.json"; + file->fileId = "org.multimc.custom.json"; + file->order = -1; + file->version = QString(); + m_version->versionFiles.append(file); + // QObject::tr("The version descriptors of this instance are not compatible with the + // current version of MultiMC")); + // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more info.") + } + // version.json -> patches/*.json -> user.json + else + do + { + // version.json + QLOG_INFO() << "Reading version.json"; + auto file = parseJsonFile(QFileInfo(root.absoluteFilePath("version.json")), false); + file->name = "Minecraft"; + file->fileId = "org.multimc.version.json"; + file->order = -1; + file->version = m_instance->intendedVersionId(); + file->mcVersion = m_instance->intendedVersionId(); + m_version->versionFiles.append(file); + // QObject::tr("Error while applying %1. Please check MultiMC-0.log for more + // info.").arg(root.absoluteFilePath("version.json"))); + + // patches/ + // load all, put into map for ordering, apply in the right order + + QMap> files; + for (auto info : patches.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + QLOG_INFO() << "Reading" << info.fileName(); + auto file = parseJsonFile(info, true); + if (files.contains(file->order)) + { + throw VersionBuildError(QObject::tr("%1 has the same order as %2").arg( + file->fileId, files[file->order].second->fileId)); + } + files.insert(file->order, qMakePair(info.fileName(), file)); + } + for (auto order : files.keys()) + { + auto &filePair = files[order]; + m_version->versionFiles.append(filePair.second); + } + } while (0); + + // some final touches + m_version->finalize(); +} + + + +void OneSixVersionBuilder::readJsonAndApply(const QJsonObject &obj) +{ + m_version->clear(); + + auto file = VersionFile::fromJson(QJsonDocument(obj), QString(), false); + // QObject::tr("Error while reading. Please check MultiMC-0.log for more info.")); + + file->applyTo(m_version); + m_version->versionFiles.append(file); + // QObject::tr("Error while applying. Please check MultiMC-0.log for more info.")); + // QObject::tr("The version descriptors of this instance are not compatible with the current + // version of MultiMC")); +} + +VersionFilePtr OneSixVersionBuilder::parseJsonFile(const QFileInfo &fileInfo, + const bool requireOrder, bool isFTB) +{ + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + throw JSONValidationError(QObject::tr("Unable to open the version file %1: %2.") + .arg(fileInfo.fileName(), file.errorString())); + } + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + throw JSONValidationError(QObject::tr("Unable to process the version file %1: %2 at %3.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(error.offset)); + } + return VersionFile::fromJson(doc, file.fileName(), requireOrder, isFTB); + // QObject::tr("Error while reading %1. Please check MultiMC-0.log for more + // info.").arg(file.fileName()); +} + +QMap OneSixVersionBuilder::readOverrideOrders(OneSixInstance *instance) +{ + QMap out; + + // make sure the order file exists + if (!QDir(instance->instanceRoot()).exists("order.json")) + return out; + + // and it can be opened + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::ReadOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + + // and then read it and process it if all above is true. + try + { + auto obj = MMCJson::ensureObject(doc); + for (auto it = obj.begin(); it != obj.end(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + out.insert(it.key(), MMCJson::ensureInteger(it.value())); + } + } + catch (JSONValidationError &err) + { + QLOG_ERROR() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + QLOG_WARN() << "Ignoring overriden order"; + return out; + } + return out; +} + +bool OneSixVersionBuilder::writeOverrideOrders(const QMap &order, + OneSixInstance *instance) +{ + QJsonObject obj; + for (auto it = order.cbegin(); it != order.cend(); ++it) + { + if (it.key().startsWith("org.multimc.")) + { + continue; + } + obj.insert(it.key(), it.value()); + } + QFile orderFile(instance->instanceRoot() + "/order.json"); + if (!orderFile.open(QFile::WriteOnly)) + { + QLOG_ERROR() << "Couldn't open" << orderFile.fileName() + << "for writing:" << orderFile.errorString(); + return false; + } + orderFile.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); + return true; +} diff --git a/logic/minecraft/OneSixVersionBuilder.h b/logic/minecraft/OneSixVersionBuilder.h new file mode 100644 index 00000000..6646584e --- /dev/null +++ b/logic/minecraft/OneSixVersionBuilder.h @@ -0,0 +1,46 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include "VersionFile.h" + +class VersionFinal; +class OneSixInstance; +class QJsonObject; +class QFileInfo; + +class OneSixVersionBuilder +{ + OneSixVersionBuilder(); +public: + static void build(VersionFinal *version, OneSixInstance *instance, const QStringList &external); + static void readJsonAndApplyToVersion(VersionFinal *version, const QJsonObject &obj); + + static QMap readOverrideOrders(OneSixInstance *instance); + static bool writeOverrideOrders(const QMap &order, OneSixInstance *instance); + +private: + VersionFinal *m_version; + OneSixInstance *m_instance; + + void buildInternal(const QStringList& external); + void readJsonAndApply(const QJsonObject &obj); + + VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder, + bool isFTB = false); +}; diff --git a/logic/minecraft/OpSys.cpp b/logic/minecraft/OpSys.cpp new file mode 100644 index 00000000..e001b7f3 --- /dev/null +++ b/logic/minecraft/OpSys.cpp @@ -0,0 +1,42 @@ +/* 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 "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/minecraft/OpSys.h b/logic/minecraft/OpSys.h new file mode 100644 index 00000000..363c87d7 --- /dev/null +++ b/logic/minecraft/OpSys.h @@ -0,0 +1,37 @@ +/* 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 +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/minecraft/VersionFile.cpp b/logic/minecraft/VersionFile.cpp new file mode 100644 index 00000000..a011ce1a --- /dev/null +++ b/logic/minecraft/VersionFile.cpp @@ -0,0 +1,635 @@ +#include +#include +#include + +#include "logger/QsLog.h" + +#include "logic/minecraft/VersionFile.h" +#include "logic/minecraft/OneSixLibrary.h" +#include "logic/minecraft/VersionFinal.h" +#include "logic/MMCJson.h" + +using namespace MMCJson; + +#define CURRENT_MINIMUM_LAUNCHER_VERSION 14 + +JarmodPtr Jarmod::fromJson(const QJsonObject &libObj, const QString &filename) +{ + JarmodPtr out(new Jarmod()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->baseurl); + readString("MMC-absoluteUrl", out->absoluteUrl); + if(!out->baseurl.isEmpty() && out->absoluteUrl.isEmpty()) + { + out->absoluteUrl = out->baseurl + out->name; + } + return out; + +} + + +RawLibraryPtr RawLibrary::fromJson(const QJsonObject &libObj, const QString &filename) +{ + RawLibraryPtr out(new RawLibrary()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a library that doesn't have a 'name' field"); + } + out->name = libObj.value("name").toString(); + + auto readString = [libObj, filename](const QString & key, QString & variable) + { + if (libObj.contains(key)) + { + QJsonValue val = libObj.value(key); + if (!val.isString()) + { + QLOG_WARN() << key << "is not a string in" << filename << "(skipping)"; + } + else + { + variable = val.toString(); + } + } + }; + + readString("url", out->url); + readString("MMC-hint", out->hint); + readString("MMC-absulute_url", out->absoluteUrl); + readString("MMC-absoluteUrl", out->absoluteUrl); + if (libObj.contains("extract")) + { + out->applyExcludes = true; + auto extractObj = ensureObject(libObj.value("extract")); + for (auto excludeVal : ensureArray(extractObj.value("exclude"))) + { + out->excludes.append(ensureString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + out->applyNatives = true; + QJsonObject nativesObj = ensureObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + QLOG_WARN() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->natives.append(qMakePair(opSys, it.value().toString())); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->rules = rulesFromJsonV4(libObj); + } + return out; +} + +VersionFilePtr VersionFile::fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB) +{ + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError("The root of " + filename + " is not an object"); + } + + QJsonObject root = doc.object(); + + if (requireOrder) + { + if (root.contains("order")) + { + out->order = ensureInteger(root.value("order")); + } + else + { + // FIXME: evaluate if we don't want to throw exceptions here instead + QLOG_ERROR() << filename << "doesn't contain an order field"; + } + } + + out->name = root.value("name").toString(); + out->fileId = root.value("fileId").toString(); + out->version = root.value("version").toString(); + out->mcVersion = root.value("mcVersion").toString(); + out->filename = filename; + + auto readString = [root, filename](const QString & key, QString & variable) + { + if (root.contains(key)) + { + variable = ensureString(root.value(key)); + } + }; + + // FIXME: This should be ignored when applying. + if (!isFTB) + { + readString("id", out->id); + } + + readString("mainClass", out->mainClass); + readString("appletClass", out->appletClass); + readString("processArguments", out->processArguments); + readString("minecraftArguments", out->overwriteMinecraftArguments); + readString("+minecraftArguments", out->addMinecraftArguments); + readString("-minecraftArguments", out->removeMinecraftArguments); + readString("type", out->type); + readString("releaseTime", out->versionReleaseTime); + readString("time", out->versionFileUpdateTime); + readString("assets", out->assets); + + if (root.contains("minimumLauncherVersion")) + { + out->minimumLauncherVersion = ensureInteger(root.value("minimumLauncherVersion")); + } + + if (root.contains("tweakers")) + { + out->shouldOverwriteTweakers = true; + for (auto tweakerVal : ensureArray(root.value("tweakers"))) + { + out->overwriteTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("+tweakers")) + { + for (auto tweakerVal : ensureArray(root.value("+tweakers"))) + { + out->addTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("-tweakers")) + { + for (auto tweakerVal : ensureArray(root.value("-tweakers"))) + { + out->removeTweakers.append(ensureString(tweakerVal)); + } + } + + if (root.contains("+traits")) + { + for (auto tweakerVal : ensureArray(root.value("+traits"))) + { + out->traits.insert(ensureString(tweakerVal)); + } + } + + if (root.contains("libraries")) + { + // FIXME: This should be done when applying. + out->shouldOverwriteLibs = !isFTB; + for (auto libVal : ensureArray(root.value("libraries"))) + { + auto libObj = ensureObject(libVal); + + auto lib = RawLibrary::fromJson(libObj, filename); + // FIXME: This should be done when applying. + if (isFTB) + { + lib->hint = "local"; + lib->insertType = RawLibrary::Prepend; + out->addLibs.prepend(lib); + } + else + { + out->overwriteLibs.append(lib); + } + } + } + + if (root.contains("+jarMods")) + { + for (auto libVal : ensureArray(root.value("+jarMods"))) + { + QJsonObject libObj = ensureObject(libVal); + // parse the jarmod + auto lib = Jarmod::fromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + + if (root.contains("+libraries")) + { + for (auto libVal : ensureArray(root.value("+libraries"))) + { + QJsonObject libObj = ensureObject(libVal); + QJsonValue insertVal = ensureExists(libObj.value("insert")); + + // parse the library + auto lib = RawLibrary::fromJson(libObj, filename); + + // TODO: utility functions for handling this case. templates? + QString insertString; + { + if (insertVal.isString()) + { + insertString = insertVal.toString(); + } + else if (insertVal.isObject()) + { + QJsonObject insertObj = insertVal.toObject(); + if (insertObj.isEmpty()) + { + throw JSONValidationError("One library has an empty insert object in " + + filename); + } + insertString = insertObj.keys().first(); + lib->insertData = insertObj.value(insertString).toString(); + } + } + if (insertString == "apply") + { + lib->insertType = RawLibrary::Apply; + } + else if (insertString == "prepend") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "append") + { + lib->insertType = RawLibrary::Prepend; + } + else if (insertString == "replace") + { + lib->insertType = RawLibrary::Replace; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid insert type"); + } + if (libObj.contains("MMC-depend")) + { + const QString dependString = ensureString(libObj.value("MMC-depend")); + if (dependString == "hard") + { + lib->dependType = RawLibrary::Hard; + } + else if (dependString == "soft") + { + lib->dependType = RawLibrary::Soft; + } + else + { + throw JSONValidationError("A '+' library in " + filename + + " contains an invalid depend type"); + } + } + out->addLibs.append(lib); + } + } + + if (root.contains("-libraries")) + { + for (auto libVal : ensureArray(root.value("-libraries"))) + { + auto libObj = ensureObject(libVal); + out->removeLibs.append(ensureString(libObj.value("name"))); + } + } + return out; +} + +OneSixLibraryPtr VersionFile::createLibrary(RawLibraryPtr lib) +{ + std::shared_ptr out(new OneSixLibrary(lib->name)); + if (!lib->url.isEmpty()) + { + out->setBaseUrl(lib->url); + } + out->setHint(lib->hint); + if (!lib->absoluteUrl.isEmpty()) + { + out->setAbsoluteUrl(lib->absoluteUrl); + } + out->setAbsoluteUrl(lib->absoluteUrl); + out->extract_excludes = lib->excludes; + for (auto native : lib->natives) + { + out->addNative(native.first, native.second); + } + out->setRules(lib->rules); + out->finalize(); + return out; +} + +int VersionFile::findLibrary(QList haystack, const QString &needle) +{ + int retval = -1; + for (int i = 0; i < haystack.size(); ++i) + { + QString chunk = haystack.at(i)->rawName(); + if (QRegExp(needle, Qt::CaseSensitive, QRegExp::WildcardUnix).indexIn(chunk) != -1) + { + // only one is allowed. + if(retval != -1) + return -1; + retval = i; + } + } + return retval; +} + +bool VersionFile::isVanilla() +{ + return fileId == "org.multimc.version.json"; +} + +bool VersionFile::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +void VersionFile::applyTo(VersionFinal *version) +{ + if (minimumLauncherVersion != -1) + { + if (minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) + { + throw LauncherVersionError(minimumLauncherVersion, CURRENT_MINIMUM_LAUNCHER_VERSION); + } + } + + if (!version->id.isNull() && !mcVersion.isNull()) + { + if (QRegExp(mcVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(version->id) == + -1) + { + throw MinecraftVersionMismatch(fileId, mcVersion, version->id); + } + } + + if (!id.isNull()) + { + version->id = id; + } + if (!mainClass.isNull()) + { + version->mainClass = mainClass; + } + if (!appletClass.isNull()) + { + version->appletClass = appletClass; + } + if (!processArguments.isNull()) + { + if(isVanilla()) + { + version->vanillaProcessArguments = processArguments; + } + version->processArguments = processArguments; + } + if (!type.isNull()) + { + version->type = type; + } + if (!versionReleaseTime.isNull()) + { + version->versionReleaseTime = versionReleaseTime; + } + if (!versionFileUpdateTime.isNull()) + { + version->time = versionFileUpdateTime; + } + if (!assets.isNull()) + { + version->assets = assets; + } + if (minimumLauncherVersion >= 0) + { + version->minimumLauncherVersion = minimumLauncherVersion; + } + if (!overwriteMinecraftArguments.isNull()) + { + if(isVanilla()) + { + version->vanillaMinecraftArguments = overwriteMinecraftArguments; + } + version->minecraftArguments = overwriteMinecraftArguments; + } + if (!addMinecraftArguments.isNull()) + { + version->minecraftArguments += addMinecraftArguments; + } + if (!removeMinecraftArguments.isNull()) + { + version->minecraftArguments.remove(removeMinecraftArguments); + } + if (shouldOverwriteTweakers) + { + version->tweakers = overwriteTweakers; + } + for (auto tweaker : addTweakers) + { + version->tweakers += tweaker; + } + for (auto tweaker : removeTweakers) + { + version->tweakers.removeAll(tweaker); + } + version->jarMods.append(jarMods); + version->traits.unite(traits); + if (shouldOverwriteLibs) + { + QList libs; + for (auto lib : overwriteLibs) + { + libs.append(createLibrary(lib)); + } + if(isVanilla()) + version->vanillaLibraries = libs; + version->libraries = libs; + } + for (auto lib : addLibs) + { + switch (lib->insertType) + { + case RawLibrary::Apply: + { + // QLOG_INFO() << "Applying lib " << lib->name; + int index = findLibrary(version->libraries, lib->name); + if (index >= 0) + { + auto library = version->libraries[index]; + if (!lib->url.isNull()) + { + library->setBaseUrl(lib->url); + } + if (!lib->hint.isNull()) + { + library->setHint(lib->hint); + } + if (!lib->absoluteUrl.isNull()) + { + library->setAbsoluteUrl(lib->absoluteUrl); + } + if (lib->applyExcludes) + { + library->extract_excludes = lib->excludes; + } + if (lib->applyNatives) + { + library->clearSuffixes(); + for (auto native : lib->natives) + { + library->addNative(native.first, native.second); + } + } + if (lib->applyRules) + { + library->setRules(lib->rules); + } + library->finalize(); + } + else + { + QLOG_WARN() << "Couldn't find" << lib->name << "(skipping)"; + } + break; + } + case RawLibrary::Append: + case RawLibrary::Prepend: + { + // QLOG_INFO() << "Adding lib " << lib->name; + const int startOfVersion = lib->name.lastIndexOf(':') + 1; + const int index = findLibrary( + version->libraries, QString(lib->name).replace(startOfVersion, INT_MAX, '*')); + if (index < 0) + { + if (lib->insertType == RawLibrary::Append) + { + version->libraries.append(createLibrary(lib)); + } + else + { + version->libraries.prepend(createLibrary(lib)); + } + } + else + { + auto otherLib = version->libraries.at(index); + const Util::Version ourVersion = lib->name.mid(startOfVersion, INT_MAX); + const Util::Version otherVersion = otherLib->version(); + // if the existing version is a hard dependency we can either use it or + // fail, but we can't change it + if (otherLib->dependType == OneSixLibrary::Hard) + { + // we need a higher version, or we're hard to and the versions aren't + // equal + if (ourVersion > otherVersion || + (lib->dependType == RawLibrary::Hard && ourVersion != otherVersion)) + { + throw VersionBuildError( + QObject::tr( + "Error resolving library dependencies between %1 and %2 in %3.") + .arg(otherLib->rawName(), lib->name, filename)); + } + else + { + // the library is already existing, so we don't have to do anything + } + } + else if (otherLib->dependType == OneSixLibrary::Soft) + { + // if we are higher it means we should update + if (ourVersion > otherVersion) + { + auto library = createLibrary(lib); + if (Util::Version(otherLib->minVersion) < ourVersion) + { + library->minVersion = ourVersion.toString(); + } + version->libraries.replace(index, library); + } + else + { + // our version is smaller than the existing version, but we require + // it: fail + if (lib->dependType == RawLibrary::Hard) + { + throw VersionBuildError(QObject::tr( + "Error resolving library dependencies between %1 and %2 in %3.") + .arg(otherLib->rawName(), lib->name, + filename)); + } + } + } + } + break; + } + case RawLibrary::Replace: + { + QString toReplace; + if(lib->insertData.isEmpty()) + { + const int startOfVersion = lib->name.lastIndexOf(':') + 1; + toReplace = QString(lib->name).replace(startOfVersion, INT_MAX, '*'); + } + else + toReplace = lib->insertData; + // QLOG_INFO() << "Replacing lib " << toReplace << " with " << lib->name; + int index = findLibrary(version->libraries, toReplace); + if (index >= 0) + { + version->libraries.replace(index, createLibrary(lib)); + } + else + { + QLOG_WARN() << "Couldn't find" << toReplace << "(skipping)"; + } + break; + } + } + } + for (auto lib : removeLibs) + { + int index = findLibrary(version->libraries, lib); + if (index >= 0) + { + // QLOG_INFO() << "Removing lib " << lib; + version->libraries.removeAt(index); + } + else + { + QLOG_WARN() << "Couldn't find" << lib << "(skipping)"; + } + } +} diff --git a/logic/minecraft/VersionFile.h b/logic/minecraft/VersionFile.h new file mode 100644 index 00000000..8234445b --- /dev/null +++ b/logic/minecraft/VersionFile.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include "logic/minecraft/OpSys.h" +#include "logic/minecraft/OneSixRule.h" +#include "MMCError.h" + +class VersionFinal; + +class VersionBuildError : public MMCError +{ +public: + VersionBuildError(QString cause) : MMCError(cause) {}; + virtual ~VersionBuildError() noexcept {} +}; + +/** + * the base version file was meant for a newer version of the vanilla launcher than we support + */ +class LauncherVersionError : public VersionBuildError +{ +public: + LauncherVersionError(int actual, int supported) + : VersionBuildError(QObject::tr( + "The base version file of this instance was meant for a newer (%1) " + "version of the vanilla launcher than this version of MultiMC supports (%2).") + .arg(actual) + .arg(supported)) {}; + virtual ~LauncherVersionError() noexcept {} +}; + +/** + * some patch was intended for a different version of minecraft + */ +class MinecraftVersionMismatch : public VersionBuildError +{ +public: + MinecraftVersionMismatch(QString fileId, QString mcVersion, QString parentMcVersion) + : VersionBuildError(QObject::tr("The patch %1 is for a different version of Minecraft " + "(%2) than that of the instance (%3).") + .arg(fileId) + .arg(mcVersion) + .arg(parentMcVersion)) {}; + virtual ~MinecraftVersionMismatch() noexcept {} +}; + +struct RawLibrary; +typedef std::shared_ptr RawLibraryPtr; +struct RawLibrary +{ + QString name; + QString url; + QString hint; + QString absoluteUrl; + bool applyExcludes = false; + QStringList excludes; + bool applyNatives = false; + QList> natives; + bool applyRules = false; + QList> rules; + + // user for '+' libraries + enum InsertType + { + Apply, + Append, + Prepend, + Replace + }; + InsertType insertType = Append; + QString insertData; + enum DependType + { + Soft, + Hard + }; + DependType dependType = Soft; + + static RawLibraryPtr fromJson(const QJsonObject &libObj, const QString &filename); +}; + +struct Jarmod; +typedef std::shared_ptr JarmodPtr; +struct Jarmod +{ + QString name; + QString baseurl; + QString hint; + QString absoluteUrl; + + static JarmodPtr fromJson(const QJsonObject &libObj, const QString &filename); +}; + +struct VersionFile; +typedef std::shared_ptr VersionFilePtr; +struct VersionFile +{ +public: /* methods */ + static VersionFilePtr fromJson(const QJsonDocument &doc, const QString &filename, + const bool requireOrder, const bool isFTB = false); + + static OneSixLibraryPtr createLibrary(RawLibraryPtr lib); + int findLibrary(QList haystack, const QString &needle); + void applyTo(VersionFinal *version); + bool isVanilla(); + bool hasJarMods(); +public: /* data */ + int order = 0; + QString name; + QString fileId; + QString version; + // TODO use the mcVersion to determine if a version file should be removed on update + QString mcVersion; + QString filename; + // TODO requirements + // QMap requirements; + QString id; + QString mainClass; + QString appletClass; + QString overwriteMinecraftArguments; + QString addMinecraftArguments; + QString removeMinecraftArguments; + QString processArguments; + QString type; + QString versionReleaseTime; + QString versionFileUpdateTime; + QString assets; + int minimumLauncherVersion = -1; + + bool shouldOverwriteTweakers = false; + QStringList overwriteTweakers; + QStringList addTweakers; + QStringList removeTweakers; + + bool shouldOverwriteLibs = false; + QList overwriteLibs; + QList addLibs; + QList removeLibs; + + QSet traits; + + QList jarMods; +}; diff --git a/logic/minecraft/VersionFinal.cpp b/logic/minecraft/VersionFinal.cpp new file mode 100644 index 00000000..fbf6a160 --- /dev/null +++ b/logic/minecraft/VersionFinal.cpp @@ -0,0 +1,429 @@ +/* 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 +#include +#include +#include + +#include "logic/minecraft/VersionFinal.h" +#include "logic/minecraft/OneSixVersionBuilder.h" +#include "logic/OneSixInstance.h" + +VersionFinal::VersionFinal(OneSixInstance *instance, QObject *parent) + : QAbstractListModel(parent), m_instance(instance) +{ + clear(); +} + +void VersionFinal::reload(const QStringList &external) +{ + beginResetModel(); + OneSixVersionBuilder::build(this, m_instance, external); + reapply(true); + endResetModel(); +} + +void VersionFinal::clear() +{ + id.clear(); + time.clear(); + versionReleaseTime.clear(); + type.clear(); + assets.clear(); + processArguments.clear(); + minecraftArguments.clear(); + minimumLauncherVersion = 0xDEADBEAF; + mainClass.clear(); + appletClass.clear(); + libraries.clear(); + tweakers.clear(); + jarMods.clear(); + traits.clear(); +} + +bool VersionFinal::canRemove(const int index) const +{ + if (index < versionFiles.size()) + { + return versionFiles.at(index)->fileId != "org.multimc.version.json"; + } + return false; +} + +bool VersionFinal::preremove(VersionFilePtr versionfile) +{ + bool ok = true; + for(auto & jarmod: versionfile->jarMods) + { + QString fullpath =PathCombine(m_instance->jarModsDir(), jarmod->name); + QFileInfo finfo (fullpath); + if(finfo.exists(fullpath)) + ok &= QFile::remove(fullpath); + } + return ok; +} + +bool VersionFinal::remove(const int index) +{ + if (!canRemove(index)) + return false; + if(!preremove(versionFiles[index])) + { + return false; + } + if(!QFile::remove(versionFiles.at(index)->filename)) + return false; + beginResetModel(); + versionFiles.removeAt(index); + reapply(true); + endResetModel(); + return true; +} + +bool VersionFinal::remove(const QString id) +{ + int i = 0; + for (auto file : versionFiles) + { + if (file->fileId == id) + { + return remove(i); + } + i++; + } + return false; +} + +QString VersionFinal::versionFileId(const int index) const +{ + if (index < 0 || index >= versionFiles.size()) + { + return QString(); + } + return versionFiles.at(index)->fileId; +} + +VersionFilePtr VersionFinal::versionFile(const QString &id) +{ + for (auto file : versionFiles) + { + if (file->fileId == id) + { + return file; + } + } + return 0; +} + +bool VersionFinal::hasJarMods() +{ + return !jarMods.isEmpty(); +} + +bool VersionFinal::hasFtbPack() +{ + return versionFile("org.multimc.ftb.pack.json") != nullptr; +} + +bool VersionFinal::removeFtbPack() +{ + return remove("org.multimc.ftb.pack.json"); +} + +bool VersionFinal::isVanilla() +{ + QDir patches(PathCombine(m_instance->instanceRoot(), "patches/")); + if(versionFiles.size() > 1) + return false; + if(QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json"))) + return false; + return true; +} + +bool VersionFinal::revertToVanilla() +{ + beginResetModel(); + auto it = versionFiles.begin(); + while (it != versionFiles.end()) + { + if ((*it)->fileId != "org.multimc.version.json") + { + if(!preremove(*it)) + { + endResetModel(); + return false; + } + if(!QFile::remove((*it)->filename)) + { + endResetModel(); + return false; + } + it = versionFiles.erase(it); + } + else + it++; + } + reapply(true); + endResetModel(); + return true; +} + +bool VersionFinal::usesLegacyCustomJson() +{ + return QFile::exists(PathCombine(m_instance->instanceRoot(), "custom.json")); +} + +QList > VersionFinal::getActiveNormalLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && !lib->isNative()) + { + output.append(lib); + } + } + return output; +} +QList > VersionFinal::getActiveNativeLibs() +{ + QList > output; + for (auto lib : libraries) + { + if (lib->isActive() && lib->isNative()) + { + output.append(lib); + } + } + return output; +} + +std::shared_ptr VersionFinal::fromJson(const QJsonObject &obj) +{ + std::shared_ptr version(new VersionFinal(0)); + try + { + OneSixVersionBuilder::readJsonAndApplyToVersion(version.get(), obj); + } + catch(MMCError & err) + { + return 0; + } + return version; +} + +QVariant VersionFinal::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= versionFiles.size()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (column) + { + case 0: + return versionFiles.at(row)->name; + case 1: + return versionFiles.at(row)->version; + default: + return QVariant(); + } + } + return QVariant(); +} +QVariant VersionFinal::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case 0: + return tr("Name"); + case 1: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); +} +Qt::ItemFlags VersionFinal::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +int VersionFinal::rowCount(const QModelIndex &parent) const +{ + return versionFiles.size(); +} + +int VersionFinal::columnCount(const QModelIndex &parent) const +{ + return 2; +} + +QMap VersionFinal::getExistingOrder() const +{ + + QMap order; + // default + { + for (auto file : versionFiles) + { + order.insert(file->fileId, file->order); + } + } + // overriden + { + QMap overridenOrder = OneSixVersionBuilder::readOverrideOrders(m_instance); + for (auto id : order.keys()) + { + if (overridenOrder.contains(id)) + { + order[id] = overridenOrder[id]; + } + } + } + return order; +} + +void VersionFinal::move(const int index, const MoveDirection direction) +{ + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + if (theirIndex < 0 || theirIndex >= versionFiles.size()) + { + return; + } + const QString ourId = versionFileId(index); + const QString theirId = versionFileId(theirIndex); + if (ourId.isNull() || ourId.startsWith("org.multimc.") || + theirId.isNull() || theirId.startsWith("org.multimc.")) + { + return; + } + if(direction == MoveDown) + { + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex+1); + } + else + { + beginMoveRows(QModelIndex(), index, index, QModelIndex(), theirIndex); + } + versionFiles.swap(index, theirIndex); + endMoveRows(); + + auto order = getExistingOrder(); + order[ourId] = theirIndex; + order[theirId] = index; + + if (!OneSixVersionBuilder::writeOverrideOrders(order, m_instance)) + { + throw MMCError(tr("Couldn't save the new order")); + } + else + { + reapply(); + } +} +void VersionFinal::resetOrder() +{ + QDir(m_instance->instanceRoot()).remove("order.json"); + reapply(); +} + +void VersionFinal::reapply(const bool alreadyReseting) +{ + if (!alreadyReseting) + { + beginResetModel(); + } + + clear(); + + auto existingOrders = getExistingOrder(); + QList orders = existingOrders.values(); + std::sort(orders.begin(), orders.end()); + QList newVersionFiles; + for (auto order : orders) + { + auto file = versionFile(existingOrders.key(order)); + newVersionFiles.append(file); + file->applyTo(this); + } + versionFiles.swap(newVersionFiles); + finalize(); + if (!alreadyReseting) + { + endResetModel(); + } +} + +void VersionFinal::finalize() +{ + // HACK: deny april fools. my head hurts enough already. + QDate now = QDate::currentDate(); + bool isAprilFools = now.month() == 4 && now.day() == 1; + if (assets.endsWith("_af") && !isAprilFools) + { + assets = assets.left(assets.length() - 3); + } + if (assets.isEmpty()) + { + assets = "legacy"; + } + auto finalizeArguments = [&]( QString & minecraftArguments, const QString & processArguments ) -> void + { + if (!minecraftArguments.isEmpty()) + return; + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + minecraftArguments = "--username ${auth_player_name} " + "--session ${auth_session} " + "--version ${profile_name}"; + } + }; + finalizeArguments(vanillaMinecraftArguments, vanillaProcessArguments); + finalizeArguments(minecraftArguments, processArguments); +} + diff --git a/logic/minecraft/VersionFinal.h b/logic/minecraft/VersionFinal.h new file mode 100644 index 00000000..ceb90f57 --- /dev/null +++ b/logic/minecraft/VersionFinal.h @@ -0,0 +1,172 @@ +/* Copyright 2013 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "OneSixLibrary.h" +#include "VersionFile.h" + +class OneSixInstance; + +class VersionFinal : public QAbstractListModel +{ + Q_OBJECT +public: + explicit VersionFinal(OneSixInstance *instance, QObject *parent = 0); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + + void reload(const QStringList &external = QStringList()); + void clear(); + + bool canRemove(const int index) const; + + QString versionFileId(const int index) const; + + // is this version unmodded vanilla minecraft? + bool isVanilla(); + // remove any customizations on top of vanilla + bool revertToVanilla(); + + // does this version have an FTB pack patch file? + bool hasFtbPack(); + // remove FTB pack + bool removeFtbPack(); + + // does this version have any jar mods? + bool hasJarMods(); + + // does this version still use a legacy custom.json file? + bool usesLegacyCustomJson(); + + + enum MoveDirection { MoveUp, MoveDown }; + void move(const int index, const MoveDirection direction); + void resetOrder(); + + // clears and reapplies all version files + void reapply(const bool alreadyReseting = false); + void finalize(); + +public +slots: + bool remove(const int index); + bool remove(const QString id); + +public: + QList> getActiveNormalLibs(); + QList> getActiveNativeLibs(); + + static std::shared_ptr fromJson(const QJsonObject &obj); + +private: + bool preremove(VersionFilePtr); + + // data members +public: + /// the ID - determines which jar to use! ACTUALLY IMPORTANT! + QString id; + /// Last updated time - as a string + QString time; + /// Release time - as a string + QString versionReleaseTime; + /// Release type - "release" or "snapshot" + QString type; + /// Assets type - "legacy" or a version ID + QString assets; + /** + * DEPRECATED: Old versions of the new vanilla launcher used this + * ex: "username_session_version" + */ + QString processArguments; + /// Same as above, but only for vanilla + QString vanillaProcessArguments; + /** + * 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; + /// Same as above, but only for vanilla + QString vanillaMinecraftArguments; + /** + * the minimum launcher version required by this version ... current is 4 (at point of + * writing) + */ + int minimumLauncherVersion = 0xDEADBEEF; + /** + * A list of all tweaker classes + */ + QStringList tweakers; + /** + * The main class to load first + */ + QString mainClass; + /** + * The applet class, for some very old minecraft releases + */ + QString appletClass; + + /// the list of libs - both active and inactive, native and java + QList> libraries; + + /// same, but only vanilla. + QList> vanillaLibraries; + + /// traits, collected from all the version files (version files can only add) + QSet traits; + + /// A list of jar mods. version files can add those. + QList jarMods; + + /* + FIXME: add support for those rules here? Looks like a pile of quick hacks to me though. + + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx", + "version": "^10\\.5\\.\\d$" + } + } + ], + "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 rules; + + QList versionFiles; + VersionFilePtr versionFile(const QString &id); + +private: + OneSixInstance *m_instance; + QMap getExistingOrder() const; +}; -- cgit v1.2.3