From 32b3ed0a1362a4b0798ad71fac3450fb77cb7e41 Mon Sep 17 00:00:00 2001 From: Thomas Groman Date: Thu, 19 Sep 2019 00:41:48 -0700 Subject: merged from 0.6.7 codebase --- api/logic/BaseInstaller.cpp | 38 +- api/logic/BaseInstaller.h | 20 +- api/logic/BaseInstance.cpp | 261 ++- api/logic/BaseInstance.h | 395 ++-- api/logic/BaseInstanceProvider.h | 57 - api/logic/BaseVersion.h | 56 +- api/logic/BaseVersionList.cpp | 88 +- api/logic/BaseVersionList.h | 136 +- api/logic/CMakeLists.txt | 771 ++++---- api/logic/Commandline.cpp | 732 ++++---- api/logic/Commandline.h | 326 ++-- api/logic/DefaultVariable.h | 54 +- api/logic/Env.cpp | 236 +-- api/logic/Env.h | 44 +- api/logic/Exception.h | 36 +- api/logic/ExponentialSeries.h | 43 + api/logic/FileSystem.cpp | 641 +++---- api/logic/FileSystem.h | 50 +- api/logic/FileSystem_test.cpp | 274 +-- api/logic/Filter.cpp | 16 +- api/logic/Filter.h | 30 +- api/logic/FolderInstanceProvider.cpp | 462 ----- api/logic/FolderInstanceProvider.h | 75 - api/logic/GZip.cpp | 208 +-- api/logic/GZip.h | 4 +- api/logic/GZip_test.cpp | 74 +- api/logic/InstanceCopyTask.cpp | 65 +- api/logic/InstanceCopyTask.h | 22 +- api/logic/InstanceCreationTask.cpp | 34 +- api/logic/InstanceCreationTask.h | 10 +- api/logic/InstanceImportTask.cpp | 678 +++---- api/logic/InstanceImportTask.h | 53 +- api/logic/InstanceList.cpp | 982 +++++++--- api/logic/InstanceList.h | 189 +- api/logic/InstanceTask.h | 84 +- api/logic/Json.cpp | 268 +-- api/logic/Json.h | 160 +- api/logic/LoggedProcess.cpp | 214 +-- api/logic/LoggedProcess.h | 72 +- api/logic/MMCStrings.cpp | 126 +- api/logic/MMCStrings.h | 2 +- api/logic/MMCZip.cpp | 400 ++-- api/logic/MMCZip.h | 72 +- api/logic/MessageLevel.cpp | 54 +- api/logic/MessageLevel.h | 20 +- api/logic/NullInstance.h | 139 +- api/logic/ProblemProvider.h | 52 +- api/logic/QObjectPtr.h | 104 +- api/logic/RWStorage.h | 108 +- api/logic/RecursiveFileSystemWatcher.cpp | 142 +- api/logic/RecursiveFileSystemWatcher.h | 80 +- api/logic/SeparatorPrefixTree.h | 544 +++--- api/logic/Usable.h | 58 +- api/logic/Version.cpp | 94 +- api/logic/Version.h | 179 +- api/logic/Version_test.cpp | 98 +- api/logic/WatchLock.h | 20 + api/logic/icons/IIconList.h | 24 +- api/logic/icons/IconUtils.cpp | 62 + api/logic/icons/IconUtils.h | 14 + api/logic/java/JavaChecker.cpp | 250 +-- api/logic/java/JavaChecker.h | 72 +- api/logic/java/JavaCheckerJob.cpp | 34 +- api/logic/java/JavaCheckerJob.h | 55 +- api/logic/java/JavaInstall.cpp | 28 +- api/logic/java/JavaInstall.h | 48 +- api/logic/java/JavaInstallList.cpp | 224 +-- api/logic/java/JavaInstallList.h | 62 +- api/logic/java/JavaUtils.cpp | 518 +++--- api/logic/java/JavaUtils.h | 14 +- api/logic/java/JavaVersion.cpp | 164 +- api/logic/java/JavaVersion.h | 68 +- api/logic/java/JavaVersion_test.cpp | 194 +- api/logic/java/launch/CheckJava.cpp | 196 +- api/logic/java/launch/CheckJava.h | 30 +- api/logic/launch/LaunchStep.cpp | 14 +- api/logic/launch/LaunchStep.h | 36 +- api/logic/launch/LaunchTask.cpp | 332 ++-- api/logic/launch/LaunchTask.h | 136 +- api/logic/launch/LogModel.cpp | 218 +-- api/logic/launch/LogModel.h | 70 +- api/logic/launch/steps/PostLaunchCommand.cpp | 96 +- api/logic/launch/steps/PostLaunchCommand.h | 28 +- api/logic/launch/steps/PreLaunchCommand.cpp | 98 +- api/logic/launch/steps/PreLaunchCommand.h | 28 +- api/logic/launch/steps/TextPrint.cpp | 18 +- api/logic/launch/steps/TextPrint.h | 20 +- api/logic/launch/steps/Update.cpp | 86 +- api/logic/launch/steps/Update.h | 24 +- api/logic/meta/BaseEntity.cpp | 202 +-- api/logic/meta/BaseEntity.h | 54 +- api/logic/meta/Index.cpp | 164 +- api/logic/meta/Index.h | 50 +- api/logic/meta/Index_test.cpp | 52 +- api/logic/meta/JsonFormat.cpp | 260 +-- api/logic/meta/JsonFormat.h | 46 +- api/logic/meta/Version.cpp | 106 +- api/logic/meta/Version.h | 138 +- api/logic/meta/VersionList.cpp | 278 +-- api/logic/meta/VersionList.h | 118 +- api/logic/minecraft/AssetsUtils.cpp | 442 +++-- api/logic/minecraft/AssetsUtils.h | 31 +- api/logic/minecraft/Component.cpp | 608 +++---- api/logic/minecraft/Component.h | 138 +- api/logic/minecraft/ComponentList.cpp | 1919 ++++++++++---------- api/logic/minecraft/ComponentList.h | 144 +- api/logic/minecraft/ComponentList_p.h | 45 +- api/logic/minecraft/ComponentUpdateTask.cpp | 1129 ++++++------ api/logic/minecraft/ComponentUpdateTask.h | 30 +- api/logic/minecraft/ComponentUpdateTask_p.h | 32 +- api/logic/minecraft/GradleSpecifier.h | 266 +-- api/logic/minecraft/GradleSpecifier_test.cpp | 126 +- api/logic/minecraft/LaunchProfile.cpp | 324 ++-- api/logic/minecraft/LaunchProfile.h | 140 +- api/logic/minecraft/Library.cpp | 537 +++--- api/logic/minecraft/Library.h | 336 ++-- api/logic/minecraft/Library_test.cpp | 498 ++--- api/logic/minecraft/MinecraftInstance.cpp | 1458 +++++++-------- api/logic/minecraft/MinecraftInstance.h | 210 +-- api/logic/minecraft/MinecraftLoadAndCheck.cpp | 50 +- api/logic/minecraft/MinecraftLoadAndCheck.h | 21 +- api/logic/minecraft/MinecraftUpdate.cpp | 250 +-- api/logic/minecraft/MinecraftUpdate.h | 36 +- api/logic/minecraft/Mod.cpp | 378 ---- api/logic/minecraft/Mod.h | 142 -- api/logic/minecraft/ModList.cpp | 367 ---- api/logic/minecraft/ModList.h | 121 -- api/logic/minecraft/ModList_test.cpp | 53 - api/logic/minecraft/MojangDownloadInfo.h | 110 +- api/logic/minecraft/MojangVersionFormat.cpp | 578 +++--- api/logic/minecraft/MojangVersionFormat.h | 20 +- api/logic/minecraft/MojangVersionFormat_test.cpp | 76 +- api/logic/minecraft/OneSixVersionFormat.cpp | 650 +++---- api/logic/minecraft/OneSixVersionFormat.h | 28 +- api/logic/minecraft/OpSys.cpp | 38 +- api/logic/minecraft/OpSys.h | 10 +- api/logic/minecraft/ParseUtils.cpp | 36 +- api/logic/minecraft/ParseUtils_test.cpp | 58 +- api/logic/minecraft/ProfileUtils.cpp | 264 +-- api/logic/minecraft/Rule.cpp | 108 +- api/logic/minecraft/Rule.h | 96 +- api/logic/minecraft/SkinUpload.cpp | 80 +- api/logic/minecraft/SkinUpload.h | 33 +- api/logic/minecraft/VersionFile.cpp | 68 +- api/logic/minecraft/VersionFile.h | 118 +- api/logic/minecraft/VersionFilterData.cpp | 104 +- api/logic/minecraft/VersionFilterData.h | 24 +- api/logic/minecraft/World.cpp | 618 ++++--- api/logic/minecraft/World.h | 117 +- api/logic/minecraft/WorldList.cpp | 519 +++--- api/logic/minecraft/WorldList.h | 178 +- api/logic/minecraft/auth/AuthSession.cpp | 32 +- api/logic/minecraft/auth/AuthSession.h | 70 +- api/logic/minecraft/auth/MojangAccount.cpp | 444 ++--- api/logic/minecraft/auth/MojangAccount.h | 172 +- api/logic/minecraft/auth/MojangAccountList.cpp | 684 +++---- api/logic/minecraft/auth/MojangAccountList.h | 306 ++-- api/logic/minecraft/auth/YggdrasilTask.cpp | 358 ++-- api/logic/minecraft/auth/YggdrasilTask.h | 213 +-- .../minecraft/auth/flows/AuthenticateTask.cpp | 314 ++-- api/logic/minecraft/auth/flows/AuthenticateTask.h | 16 +- api/logic/minecraft/auth/flows/RefreshTask.cpp | 190 +- api/logic/minecraft/auth/flows/RefreshTask.h | 14 +- api/logic/minecraft/auth/flows/ValidateTask.cpp | 34 +- api/logic/minecraft/auth/flows/ValidateTask.h | 14 +- api/logic/minecraft/forge/ForgeXzDownload.cpp | 644 +++---- api/logic/minecraft/forge/ForgeXzDownload.h | 48 +- api/logic/minecraft/gameoptions/GameOptions.cpp | 144 ++ api/logic/minecraft/gameoptions/GameOptions.h | 34 + api/logic/minecraft/launch/ClaimAccount.cpp | 20 +- api/logic/minecraft/launch/ClaimAccount.h | 24 +- .../launch/CreateServerResourcePacksFolder.cpp | 14 +- .../launch/CreateServerResourcePacksFolder.h | 18 +- api/logic/minecraft/launch/DirectJavaLaunch.cpp | 198 +- api/logic/minecraft/launch/DirectJavaLaunch.h | 40 +- api/logic/minecraft/launch/ExtractNatives.cpp | 122 +- api/logic/minecraft/launch/ExtractNatives.h | 20 +- api/logic/minecraft/launch/LauncherPartLaunch.cpp | 294 +-- api/logic/minecraft/launch/LauncherPartLaunch.h | 44 +- api/logic/minecraft/launch/ModMinecraftJar.cpp | 92 +- api/logic/minecraft/launch/ModMinecraftJar.h | 22 +- api/logic/minecraft/launch/PrintInstanceInfo.cpp | 97 +- api/logic/minecraft/launch/PrintInstanceInfo.h | 20 +- api/logic/minecraft/launch/ReconstructAssets.cpp | 36 + api/logic/minecraft/launch/ReconstructAssets.h | 33 + api/logic/minecraft/launch/ScanModFolders.cpp | 54 + api/logic/minecraft/launch/ScanModFolders.h | 42 + api/logic/minecraft/legacy/LegacyInstance.cpp | 314 ++-- api/logic/minecraft/legacy/LegacyInstance.h | 220 ++- api/logic/minecraft/legacy/LegacyModList.cpp | 231 +-- api/logic/minecraft/legacy/LegacyModList.h | 41 +- api/logic/minecraft/legacy/LegacyUpgradeTask.cpp | 201 +- api/logic/minecraft/legacy/LegacyUpgradeTask.h | 20 +- api/logic/minecraft/mod/LocalModParseTask.cpp | 298 +++ api/logic/minecraft/mod/LocalModParseTask.h | 37 + api/logic/minecraft/mod/Mod.cpp | 151 ++ api/logic/minecraft/mod/Mod.h | 117 ++ api/logic/minecraft/mod/ModDetails.h | 17 + api/logic/minecraft/mod/ModFolderLoadTask.cpp | 18 + api/logic/minecraft/mod/ModFolderLoadTask.h | 29 + api/logic/minecraft/mod/ModFolderModel.cpp | 554 ++++++ api/logic/minecraft/mod/ModFolderModel.h | 145 ++ api/logic/minecraft/mod/ModFolderModel_test.cpp | 53 + api/logic/minecraft/testdata/lib-native-arch.json | 88 +- api/logic/minecraft/testdata/lib-native.json | 100 +- api/logic/minecraft/testdata/lib-simple.json | 18 +- api/logic/minecraft/update/AssetUpdateTask.cpp | 129 +- api/logic/minecraft/update/AssetUpdateTask.h | 22 +- api/logic/minecraft/update/FMLLibrariesTask.cpp | 189 +- api/logic/minecraft/update/FMLLibrariesTask.h | 21 +- api/logic/minecraft/update/FoldersTask.cpp | 20 +- api/logic/minecraft/update/FoldersTask.h | 10 +- api/logic/minecraft/update/LibrariesTask.cpp | 123 +- api/logic/minecraft/update/LibrariesTask.h | 17 +- api/logic/modplatform/flame/FileResolvingTask.cpp | 150 +- api/logic/modplatform/flame/FileResolvingTask.h | 24 +- api/logic/modplatform/flame/PackManifest.cpp | 148 +- api/logic/modplatform/flame/PackManifest.h | 69 +- api/logic/modplatform/flame/UrlResolvingTask.cpp | 175 ++ api/logic/modplatform/flame/UrlResolvingTask.h | 43 + api/logic/modplatform/ftb/FtbPackFetchTask.cpp | 243 ++- api/logic/modplatform/ftb/FtbPackFetchTask.h | 31 +- api/logic/modplatform/ftb/FtbPackInstallTask.cpp | 297 +-- api/logic/modplatform/ftb/FtbPackInstallTask.h | 51 +- .../modplatform/ftb/FtbPrivatePackManager.cpp | 37 + api/logic/modplatform/ftb/FtbPrivatePackManager.h | 40 + api/logic/modplatform/ftb/PackHelpers.h | 47 +- api/logic/net/ByteArraySink.h | 88 +- api/logic/net/ChecksumValidator.h | 78 +- api/logic/net/Download.cpp | 418 ++--- api/logic/net/Download.h | 62 +- api/logic/net/FileSink.cpp | 142 +- api/logic/net/FileSink.h | 24 +- api/logic/net/HttpMetaCache.cpp | 368 ++-- api/logic/net/HttpMetaCache.h | 154 +- api/logic/net/MetaCacheSink.cpp | 78 +- api/logic/net/MetaCacheSink.h | 14 +- api/logic/net/Mode.h | 4 +- api/logic/net/NetAction.h | 134 +- api/logic/net/NetJob.cpp | 314 ++-- api/logic/net/NetJob.h | 98 +- api/logic/net/PasteUpload.cpp | 128 +- api/logic/net/PasteUpload.h | 63 +- api/logic/net/Sink.h | 102 +- api/logic/net/URLConstants.cpp | 4 +- api/logic/net/URLConstants.h | 26 +- api/logic/net/Validator.h | 12 +- api/logic/news/NewsChecker.cpp | 146 +- api/logic/news/NewsChecker.h | 118 +- api/logic/news/NewsEntry.cpp | 74 +- api/logic/news/NewsEntry.h | 54 +- api/logic/notifications/NotificationChecker.cpp | 158 +- api/logic/notifications/NotificationChecker.h | 74 +- api/logic/pathmatcher/FSTreeMatcher.h | 18 +- api/logic/pathmatcher/IPathMatcher.h | 6 +- api/logic/pathmatcher/MultiMatcher.h | 42 +- api/logic/pathmatcher/RegexpMatcher.h | 66 +- api/logic/screenshots/ImgurAlbumCreation.cpp | 112 +- api/logic/screenshots/ImgurAlbumCreation.h | 46 +- api/logic/screenshots/ImgurUpload.cpp | 160 +- api/logic/screenshots/ImgurUpload.h | 28 +- api/logic/screenshots/Screenshot.h | 16 +- api/logic/settings/INIFile.cpp | 204 ++- api/logic/settings/INIFile.h | 18 +- api/logic/settings/INIFile_test.cpp | 97 +- api/logic/settings/INISettingsObject.cpp | 110 +- api/logic/settings/INISettingsObject.h | 50 +- api/logic/settings/OverrideSetting.cpp | 30 +- api/logic/settings/OverrideSetting.h | 20 +- api/logic/settings/PassthroughSetting.cpp | 60 +- api/logic/settings/PassthroughSetting.h | 20 +- api/logic/settings/Setting.cpp | 34 +- api/logic/settings/Setting.h | 148 +- api/logic/settings/SettingsObject.cpp | 142 +- api/logic/settings/SettingsObject.h | 314 ++-- api/logic/status/StatusChecker.cpp | 168 +- api/logic/status/StatusChecker.h | 38 +- api/logic/tasks/SequentialTask.cpp | 56 +- api/logic/tasks/SequentialTask.h | 21 +- api/logic/tasks/Task.cpp | 168 +- api/logic/tasks/Task.h | 108 +- api/logic/tools/BaseExternalTool.cpp | 10 +- api/logic/tools/BaseExternalTool.h | 34 +- api/logic/tools/BaseProfiler.cpp | 27 +- api/logic/tools/BaseProfiler.h | 21 +- api/logic/tools/JProfiler.cpp | 116 +- api/logic/tools/JProfiler.h | 10 +- api/logic/tools/JVisualVM.cpp | 100 +- api/logic/tools/JVisualVM.h | 10 +- api/logic/tools/MCEditTool.cpp | 84 +- api/logic/tools/MCEditTool.h | 12 +- api/logic/translations/POTranslator.cpp | 373 ++++ api/logic/translations/POTranslator.h | 16 + api/logic/translations/TranslationsModel.cpp | 804 +++++--- api/logic/translations/TranslationsModel.h | 54 +- api/logic/updater/DownloadTask.cpp | 191 +- api/logic/updater/DownloadTask.h | 111 +- api/logic/updater/DownloadTask_test.cpp | 336 ++-- api/logic/updater/GoUpdate.cpp | 382 ++-- api/logic/updater/GoUpdate.h | 140 +- api/logic/updater/UpdateChecker.cpp | 400 ++-- api/logic/updater/UpdateChecker.h | 170 +- api/logic/updater/UpdateChecker_test.cpp | 243 +-- api/logic/updater/testdata/1.json | 82 +- api/logic/updater/testdata/2.json | 58 +- api/logic/updater/testdata/channels.json | 42 +- api/logic/updater/testdata/errorChannels.json | 42 +- api/logic/updater/testdata/garbageChannels.json | 40 +- api/logic/updater/testdata/index.json | 14 +- api/logic/updater/testdata/noChannels.json | 6 +- api/logic/updater/testdata/oneChannel.json | 18 +- 311 files changed, 24740 insertions(+), 22641 deletions(-) delete mode 100644 api/logic/BaseInstanceProvider.h create mode 100644 api/logic/ExponentialSeries.h delete mode 100644 api/logic/FolderInstanceProvider.cpp delete mode 100644 api/logic/FolderInstanceProvider.h create mode 100644 api/logic/WatchLock.h create mode 100644 api/logic/icons/IconUtils.cpp create mode 100644 api/logic/icons/IconUtils.h delete mode 100644 api/logic/minecraft/Mod.cpp delete mode 100644 api/logic/minecraft/Mod.h delete mode 100644 api/logic/minecraft/ModList.cpp delete mode 100644 api/logic/minecraft/ModList.h delete mode 100644 api/logic/minecraft/ModList_test.cpp create mode 100644 api/logic/minecraft/gameoptions/GameOptions.cpp create mode 100644 api/logic/minecraft/gameoptions/GameOptions.h create mode 100644 api/logic/minecraft/launch/ReconstructAssets.cpp create mode 100644 api/logic/minecraft/launch/ReconstructAssets.h create mode 100644 api/logic/minecraft/launch/ScanModFolders.cpp create mode 100644 api/logic/minecraft/launch/ScanModFolders.h create mode 100644 api/logic/minecraft/mod/LocalModParseTask.cpp create mode 100644 api/logic/minecraft/mod/LocalModParseTask.h create mode 100644 api/logic/minecraft/mod/Mod.cpp create mode 100644 api/logic/minecraft/mod/Mod.h create mode 100644 api/logic/minecraft/mod/ModDetails.h create mode 100644 api/logic/minecraft/mod/ModFolderLoadTask.cpp create mode 100644 api/logic/minecraft/mod/ModFolderLoadTask.h create mode 100644 api/logic/minecraft/mod/ModFolderModel.cpp create mode 100644 api/logic/minecraft/mod/ModFolderModel.h create mode 100644 api/logic/minecraft/mod/ModFolderModel_test.cpp create mode 100644 api/logic/modplatform/flame/UrlResolvingTask.cpp create mode 100644 api/logic/modplatform/flame/UrlResolvingTask.h create mode 100644 api/logic/modplatform/ftb/FtbPrivatePackManager.cpp create mode 100644 api/logic/modplatform/ftb/FtbPrivatePackManager.h create mode 100644 api/logic/translations/POTranslator.cpp create mode 100644 api/logic/translations/POTranslator.h (limited to 'api/logic') diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp index 51f66293..3819a4e4 100644 --- a/api/logic/BaseInstaller.cpp +++ b/api/logic/BaseInstaller.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,37 +25,37 @@ BaseInstaller::BaseInstaller() bool BaseInstaller::isApplied(MinecraftInstance *on) { - return QFile::exists(filename(on->instanceRoot())); + return QFile::exists(filename(on->instanceRoot())); } bool BaseInstaller::add(MinecraftInstance *to) { - if (!patchesDir(to->instanceRoot()).exists()) - { - QDir(to->instanceRoot()).mkdir("patches"); - } - - if (isApplied(to)) - { - if (!remove(to)) - { - return false; - } - } - - return true; + if (!patchesDir(to->instanceRoot()).exists()) + { + QDir(to->instanceRoot()).mkdir("patches"); + } + + if (isApplied(to)) + { + if (!remove(to)) + { + return false; + } + } + + return true; } bool BaseInstaller::remove(MinecraftInstance *from) { - return QFile::remove(filename(from->instanceRoot())); + return QFile::remove(filename(from->instanceRoot())); } QString BaseInstaller::filename(const QString &root) const { - return patchesDir(root).absoluteFilePath(id() + ".json"); + return patchesDir(root).absoluteFilePath(id() + ".json"); } QDir BaseInstaller::patchesDir(const QString &root) const { - return QDir(root + "/patches/"); + return QDir(root + "/patches/"); } diff --git a/api/logic/BaseInstaller.h b/api/logic/BaseInstaller.h index afe11d55..59e00e06 100644 --- a/api/logic/BaseInstaller.h +++ b/api/logic/BaseInstaller.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,17 +30,17 @@ typedef std::shared_ptr BaseVersionPtr; class MULTIMC_LOGIC_EXPORT BaseInstaller { public: - BaseInstaller(); - virtual ~BaseInstaller(){}; - bool isApplied(MinecraftInstance *on); + BaseInstaller(); + virtual ~BaseInstaller(){}; + bool isApplied(MinecraftInstance *on); - virtual bool add(MinecraftInstance *to); - virtual bool remove(MinecraftInstance *from); + virtual bool add(MinecraftInstance *to); + virtual bool remove(MinecraftInstance *from); - virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; + virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0; protected: - virtual QString id() const = 0; - QString filename(const QString &root) const; - QDir patchesDir(const QString &root) const; + virtual QString id() const = 0; + QString filename(const QString &root) const; + QDir patchesDir(const QString &root) const; }; diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp index 7e652e0d..7a95e255 100644 --- a/api/logic/BaseInstance.cpp +++ b/api/logic/BaseInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,281 +27,228 @@ #include "Commandline.h" BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : QObject() + : QObject() { - m_settings = settings; - m_rootDir = rootDir; - - m_settings->registerSetting("name", "Unnamed Instance"); - m_settings->registerSetting("iconKey", "default"); - m_settings->registerSetting("notes", ""); - m_settings->registerSetting("lastLaunchTime", 0); - m_settings->registerSetting("totalTimePlayed", 0); - - // Custom Commands - auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); - m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); - m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); - - // Console - auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); - m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); - m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); - - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); - m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); + m_settings = settings; + m_rootDir = rootDir; + + m_settings->registerSetting("name", "Unnamed Instance"); + m_settings->registerSetting("iconKey", "default"); + m_settings->registerSetting("notes", ""); + m_settings->registerSetting("lastLaunchTime", 0); + m_settings->registerSetting("totalTimePlayed", 0); + + // Custom Commands + auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); + m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); + m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); + m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); + + // Console + auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); + m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); + m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); + + m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); + m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); } QString BaseInstance::getPreLaunchCommand() { - return settings()->get("PreLaunchCommand").toString(); + return settings()->get("PreLaunchCommand").toString(); } QString BaseInstance::getWrapperCommand() { - return settings()->get("WrapperCommand").toString(); + return settings()->get("WrapperCommand").toString(); } QString BaseInstance::getPostExitCommand() { - return settings()->get("PostExitCommand").toString(); + return settings()->get("PostExitCommand").toString(); } int BaseInstance::getConsoleMaxLines() const { - auto lineSetting = settings()->getSetting("ConsoleMaxLines"); - bool conversionOk = false; - int maxLines = lineSetting->get().toInt(&conversionOk); - if(!conversionOk) - { - maxLines = lineSetting->defValue().toInt(); - qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; - } - return maxLines; + auto lineSetting = settings()->getSetting("ConsoleMaxLines"); + bool conversionOk = false; + int maxLines = lineSetting->get().toInt(&conversionOk); + if(!conversionOk) + { + maxLines = lineSetting->defValue().toInt(); + qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; + } + return maxLines; } bool BaseInstance::shouldStopOnConsoleOverflow() const { - return settings()->get("ConsoleOverflowStop").toBool(); + return settings()->get("ConsoleOverflowStop").toBool(); } void BaseInstance::iconUpdated(QString key) { - if(iconKey() == key) - { - emit propertiesChanged(this); - } + if(iconKey() == key) + { + emit propertiesChanged(this); + } } void BaseInstance::invalidate() { - changeStatus(Status::Gone); - qDebug() << "Instance" << id() << "has been invalidated."; -} - -void BaseInstance::nuke() -{ - changeStatus(Status::Gone); - qDebug() << "Instance" << id() << "has been deleted by MultiMC."; - FS::deletePath(instanceRoot()); + changeStatus(Status::Gone); + qDebug() << "Instance" << id() << "has been invalidated."; } void BaseInstance::changeStatus(BaseInstance::Status newStatus) { - Status status = currentStatus(); - if(status != newStatus) - { - m_status = newStatus; - emit statusChanged(status, newStatus); - } + Status status = currentStatus(); + if(status != newStatus) + { + m_status = newStatus; + emit statusChanged(status, newStatus); + } } BaseInstance::Status BaseInstance::currentStatus() const { - return m_status; + return m_status; } QString BaseInstance::id() const { - return QFileInfo(instanceRoot()).fileName(); + return QFileInfo(instanceRoot()).fileName(); } bool BaseInstance::isRunning() const { - return m_isRunning; + return m_isRunning; } void BaseInstance::setRunning(bool running) { - if(running == m_isRunning) - return; - - m_isRunning = running; - - if(running) - { - m_timeStarted = QDateTime::currentDateTime(); - } - else - { - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - QDateTime timeEnded = QDateTime::currentDateTime(); - settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); - emit propertiesChanged(this); - } - - emit runningStatusChanged(running); + if(running == m_isRunning) + return; + + m_isRunning = running; + + if(running) + { + m_timeStarted = QDateTime::currentDateTime(); + } + else + { + qint64 current = settings()->get("totalTimePlayed").toLongLong(); + QDateTime timeEnded = QDateTime::currentDateTime(); + settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); + emit propertiesChanged(this); + } + + emit runningStatusChanged(running); } int64_t BaseInstance::totalTimePlayed() const { - qint64 current = settings()->get("totalTimePlayed").toLongLong(); - if(m_isRunning) - { - QDateTime timeNow = QDateTime::currentDateTime(); - return current + m_timeStarted.secsTo(timeNow); - } - return current; + qint64 current = settings()->get("totalTimePlayed").toLongLong(); + if(m_isRunning) + { + QDateTime timeNow = QDateTime::currentDateTime(); + return current + m_timeStarted.secsTo(timeNow); + } + return current; } void BaseInstance::resetTimePlayed() { - settings()->reset("totalTimePlayed"); + settings()->reset("totalTimePlayed"); } QString BaseInstance::instanceType() const { - return m_settings->get("InstanceType").toString(); + return m_settings->get("InstanceType").toString(); } QString BaseInstance::instanceRoot() const { - return m_rootDir; -} - -InstancePtr BaseInstance::getSharedPtr() -{ - return shared_from_this(); + return m_rootDir; } SettingsObjectPtr BaseInstance::settings() const { - return m_settings; + return m_settings; } bool BaseInstance::canLaunch() const { - return (!hasVersionBroken() && !isRunning()); + return (!hasVersionBroken() && !isRunning()); } bool BaseInstance::reloadSettings() { - return m_settings->reload(); + return m_settings->reload(); } qint64 BaseInstance::lastLaunch() const { - return m_settings->get("lastLaunchTime").value(); + return m_settings->get("lastLaunchTime").value(); } void BaseInstance::setLastLaunch(qint64 val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("lastLaunchTime", val); - emit propertiesChanged(this); -} - -void BaseInstance::setGroupInitial(QString val) -{ - if(m_group == val) - { - return; - } - m_group = val; - emit propertiesChanged(this); -} - -void BaseInstance::setGroupPost(QString val) -{ - if(m_group == val) - { - return; - } - setGroupInitial(val); - emit groupChanged(); -} - -QString BaseInstance::group() const -{ - return m_group; + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("lastLaunchTime", val); + emit propertiesChanged(this); } void BaseInstance::setNotes(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("notes", val); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("notes", val); } QString BaseInstance::notes() const { - return m_settings->get("notes").toString(); + return m_settings->get("notes").toString(); } void BaseInstance::setIconKey(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("iconKey", val); - emit propertiesChanged(this); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("iconKey", val); + emit propertiesChanged(this); } QString BaseInstance::iconKey() const { - return m_settings->get("iconKey").toString(); + return m_settings->get("iconKey").toString(); } void BaseInstance::setName(QString val) { - //FIXME: if no change, do not set. setting involves saving a file. - m_settings->set("name", val); - emit propertiesChanged(this); + //FIXME: if no change, do not set. setting involves saving a file. + m_settings->set("name", val); + emit propertiesChanged(this); } QString BaseInstance::name() const { - return m_settings->get("name").toString(); + return m_settings->get("name").toString(); } QString BaseInstance::windowTitle() const { - return "MultiMC: " + name(); + return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " "); } // FIXME: why is this here? move it to MinecraftInstance!!! QStringList BaseInstance::extraArguments() const { - return Commandline::splitArgs(settings()->get("JvmArgs").toString()); -} - -std::shared_ptr BaseInstance::getLaunchTask() -{ - return m_launchProcess; -} - -void BaseInstance::setProvider(BaseInstanceProvider* provider) -{ - // only once. - assert(!m_provider); - if(m_provider) - { - qWarning() << "Provider set more than once for instance" << id(); - } - m_provider = provider; + return Commandline::splitArgs(settings()->get("JvmArgs").toString()); } -BaseInstanceProvider* BaseInstance::provider() const +shared_qobject_ptr BaseInstance::getLaunchTask() { - return m_provider; + return m_launchProcess; } diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h index 282bfb70..3c342cb3 100644 --- a/api/logic/BaseInstance.h +++ b/api/logic/BaseInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,6 @@ class QDir; class Task; class LaunchTask; class BaseInstance; -class BaseInstanceProvider; // pointer for lazy people typedef std::shared_ptr InstancePtr; @@ -53,231 +52,217 @@ typedef std::shared_ptr InstancePtr; */ class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this { - Q_OBJECT + Q_OBJECT protected: - /// no-touchy! - BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + /// no-touchy! + BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); public: /* types */ - enum class Status - { - Present, - Gone // either nuked or invalidated - }; + enum class Status + { + Present, + Gone // either nuked or invalidated + }; public: - /// virtual destructor to make sure the destruction is COMPLETE - virtual ~BaseInstance() {}; + /// virtual destructor to make sure the destruction is COMPLETE + virtual ~BaseInstance() {}; - virtual void init() = 0; - virtual void saveNow() = 0; + virtual void saveNow() = 0; - /// nuke thoroughly - deletes the instance contents, notifies the list/model which is - /// responsible of cleaning up the husk - void nuke(); + /*** + * the instance has been invalidated - it is no longer tracked by MultiMC for some reason, + * but it has not necessarily been deleted. + * + * Happens when the instance folder changes to some other location, or the instance is removed by external means. + */ + void invalidate(); - /*** - * the instance has been invalidated - it is no longer tracked by MultiMC for some reason, - * but it has not necessarily been deleted. - * - * Happens when the instance folder changes to some other location, or the instance is removed by external means. - */ - void invalidate(); - - /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to - /// be unique. - virtual QString id() const; - - void setRunning(bool running); - bool isRunning() const; - int64_t totalTimePlayed() const; - void resetTimePlayed(); - - void setProvider(BaseInstanceProvider * provider); - BaseInstanceProvider * provider() const; - - /// get the type of this instance - QString instanceType() const; - - /// Path to the instance's root directory. - QString instanceRoot() const; - - QString name() const; - void setName(QString val); - - /// Value used for instance window titles - QString windowTitle() const; - - QString iconKey() const; - void setIconKey(QString val); - - QString notes() const; - void setNotes(QString val); - - QString group() const; - void setGroupInitial(QString val); - void setGroupPost(QString val); - - QString getPreLaunchCommand(); - QString getPostExitCommand(); - QString getWrapperCommand(); - - /// guess log level from a line of game log - virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) - { - return level; - }; - - virtual QStringList extraArguments() const; - - /// Traits. Normally inside the version, depends on instance implementation. - virtual QSet traits() const = 0; - - /** - * Gets the time that the instance was last launched. - * Stored in milliseconds since epoch. - */ - qint64 lastLaunch() const; - /// Sets the last launched time to 'val' milliseconds since epoch - void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); - - InstancePtr getSharedPtr(); - - /*! - * \brief Gets this instance's settings object. - * This settings object stores instance-specific settings. - * \return A pointer to this instance's settings object. - */ - virtual SettingsObjectPtr settings() const; - - /// returns a valid update task - virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) = 0; - - /// returns a valid launcher (task container) - virtual std::shared_ptr createLaunchTask(AuthSessionPtr account) = 0; - - /// returns the current launch task (if any) - std::shared_ptr getLaunchTask(); - - /*! - * Create envrironment variables for running the instance - */ - virtual QProcessEnvironment createEnvironment() = 0; - - /*! - * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' - */ - virtual IPathMatcher::Ptr getLogFileMatcher() = 0; - - /*! - * Returns the root folder to use for looking up log files - */ - virtual QString getLogFileRoot() = 0; - - virtual QString getStatusbarDescription() = 0; - - /// FIXME: this really should be elsewhere... - virtual QString instanceConfigFolder() const = 0; - - /// get variables this instance exports - virtual QMap getVariables() const = 0; - - virtual QString typeName() const = 0; - - bool hasVersionBroken() const - { - return m_hasBrokenVersion; - } - void setVersionBroken(bool value) - { - if(m_hasBrokenVersion != value) - { - m_hasBrokenVersion = value; - emit propertiesChanged(this); - } - } - - bool hasUpdateAvailable() const - { - return m_hasUpdate; - } - void setUpdateAvailable(bool value) - { - if(m_hasUpdate != value) - { - m_hasUpdate = value; - emit propertiesChanged(this); - } - } - - bool hasCrashed() const - { - return m_crashed; - } - void setCrashed(bool value) - { - if(m_crashed != value) - { - m_crashed = value; - emit propertiesChanged(this); - } - } - - virtual bool canLaunch() const; - virtual bool canEdit() const = 0; - virtual bool canExport() const = 0; - - bool reloadSettings(); - - /** - * 'print' a verbose desription of the instance into a QStringList - */ - virtual QStringList verboseDescription(AuthSessionPtr session) = 0; - - Status currentStatus() const; - - int getConsoleMaxLines() const; - bool shouldStopOnConsoleOverflow() const; + /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to + /// be unique. + virtual QString id() const; + + void setRunning(bool running); + bool isRunning() const; + int64_t totalTimePlayed() const; + void resetTimePlayed(); + + /// get the type of this instance + QString instanceType() const; + + /// Path to the instance's root directory. + QString instanceRoot() const; + + /// Path to the instance's game root directory. + virtual QString gameRoot() const + { + return instanceRoot(); + } + + QString name() const; + void setName(QString val); + + /// Value used for instance window titles + QString windowTitle() const; + + QString iconKey() const; + void setIconKey(QString val); + + QString notes() const; + void setNotes(QString val); + + QString getPreLaunchCommand(); + QString getPostExitCommand(); + QString getWrapperCommand(); + + /// guess log level from a line of game log + virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) + { + return level; + }; + + virtual QStringList extraArguments() const; + + /// Traits. Normally inside the version, depends on instance implementation. + virtual QSet traits() const = 0; + + /** + * Gets the time that the instance was last launched. + * Stored in milliseconds since epoch. + */ + qint64 lastLaunch() const; + /// Sets the last launched time to 'val' milliseconds since epoch + void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch()); + + /*! + * \brief Gets this instance's settings object. + * This settings object stores instance-specific settings. + * \return A pointer to this instance's settings object. + */ + virtual SettingsObjectPtr settings() const; + + /// returns a valid update task + virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) = 0; + + /// returns a valid launcher (task container) + virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account) = 0; + + /// returns the current launch task (if any) + shared_qobject_ptr getLaunchTask(); + + /*! + * Create envrironment variables for running the instance + */ + virtual QProcessEnvironment createEnvironment() = 0; + + /*! + * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' + */ + virtual IPathMatcher::Ptr getLogFileMatcher() = 0; + + /*! + * Returns the root folder to use for looking up log files + */ + virtual QString getLogFileRoot() = 0; + + virtual QString getStatusbarDescription() = 0; + + /// FIXME: this really should be elsewhere... + virtual QString instanceConfigFolder() const = 0; + + /// get variables this instance exports + virtual QMap getVariables() const = 0; + + virtual QString typeName() const = 0; + + bool hasVersionBroken() const + { + return m_hasBrokenVersion; + } + void setVersionBroken(bool value) + { + if(m_hasBrokenVersion != value) + { + m_hasBrokenVersion = value; + emit propertiesChanged(this); + } + } + + bool hasUpdateAvailable() const + { + return m_hasUpdate; + } + void setUpdateAvailable(bool value) + { + if(m_hasUpdate != value) + { + m_hasUpdate = value; + emit propertiesChanged(this); + } + } + + bool hasCrashed() const + { + return m_crashed; + } + void setCrashed(bool value) + { + if(m_crashed != value) + { + m_crashed = value; + emit propertiesChanged(this); + } + } + + virtual bool canLaunch() const; + virtual bool canEdit() const = 0; + virtual bool canExport() const = 0; + + bool reloadSettings(); + + /** + * 'print' a verbose desription of the instance into a QStringList + */ + virtual QStringList verboseDescription(AuthSessionPtr session) = 0; + + Status currentStatus() const; + + int getConsoleMaxLines() const; + bool shouldStopOnConsoleOverflow() const; protected: - void changeStatus(Status newStatus); + void changeStatus(Status newStatus); signals: - /*! - * \brief Signal emitted when properties relevant to the instance view change - */ - void propertiesChanged(BaseInstance *inst); - /*! - * \brief Signal emitted when groups are affected in any way - */ - void groupChanged(); + /*! + * \brief Signal emitted when properties relevant to the instance view change + */ + void propertiesChanged(BaseInstance *inst); - void launchTaskChanged(std::shared_ptr); + void launchTaskChanged(shared_qobject_ptr); - void runningStatusChanged(bool running); + void runningStatusChanged(bool running); - void statusChanged(Status from, Status to); + void statusChanged(Status from, Status to); protected slots: - void iconUpdated(QString key); + void iconUpdated(QString key); protected: /* data */ - QString m_rootDir; - QString m_group; - SettingsObjectPtr m_settings; - // InstanceFlags m_flags; - bool m_isRunning = false; - std::shared_ptr m_launchProcess; - QDateTime m_timeStarted; - BaseInstanceProvider * m_provider = nullptr; + QString m_rootDir; + SettingsObjectPtr m_settings; + // InstanceFlags m_flags; + bool m_isRunning = false; + shared_qobject_ptr m_launchProcess; + QDateTime m_timeStarted; private: /* data */ - Status m_status = Status::Present; - bool m_crashed = false; - bool m_hasUpdate = false; - bool m_hasBrokenVersion = false; + Status m_status = Status::Present; + bool m_crashed = false; + bool m_hasUpdate = false; + bool m_hasBrokenVersion = false; }; -Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(shared_qobject_ptr) //Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) //Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) diff --git a/api/logic/BaseInstanceProvider.h b/api/logic/BaseInstanceProvider.h deleted file mode 100644 index 34489c5d..00000000 --- a/api/logic/BaseInstanceProvider.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include -#include "BaseInstance.h" -#include "settings/SettingsObject.h" - -#include "multimc_logic_export.h" - -using InstanceId = QString; -using InstanceLocator = std::pair; - -enum class InstCreateError -{ - NoCreateError = 0, - NoSuchVersion, - UnknownCreateError, - InstExists, - CantCreateDir -}; - -class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject -{ - Q_OBJECT -public: - BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings) - { - // nil - } -public: - virtual QList discoverInstances() = 0; - virtual InstancePtr loadInstance(const InstanceId &id) = 0; - virtual void loadGroupList() = 0; - virtual void saveGroupList() = 0; - - virtual QString getStagedInstancePath() - { - return QString(); - } - virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName) - { - return false; - } - virtual bool destroyStagingPath(const QString & path) - { - return true; - } - -signals: - // Emit this when the list of provided instances changed - void instancesChanged(); - // Emit when the set of groups your provider supplies changes. - void groupsChanged(QSet groups); - -protected: - SettingsObjectPtr m_globalSettings; -}; diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h index e49d6277..15e64bc0 100644 --- a/api/logic/BaseVersion.h +++ b/api/logic/BaseVersion.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,33 +25,33 @@ class BaseVersion { public: - virtual ~BaseVersion() {} - /*! - * A string used to identify this version in config files. - * This should be unique within the version list or shenanigans will occur. - */ - virtual QString descriptor() = 0; - - /*! - * The name of this version as it is displayed to the user. - * For example: "1.5.1" - */ - virtual QString name() = 0; - - /*! - * This should return a string that describes - * the kind of version this is (Stable, Beta, Snapshot, whatever) - */ - virtual QString typeString() const = 0; - - virtual bool operator<(BaseVersion &a) - { - return name() < a.name(); - }; - virtual bool operator>(BaseVersion &a) - { - return name() > a.name(); - }; + virtual ~BaseVersion() {} + /*! + * A string used to identify this version in config files. + * This should be unique within the version list or shenanigans will occur. + */ + virtual QString descriptor() = 0; + + /*! + * The name of this version as it is displayed to the user. + * For example: "1.5.1" + */ + virtual QString name() = 0; + + /*! + * This should return a string that describes + * the kind of version this is (Stable, Beta, Snapshot, whatever) + */ + virtual QString typeString() const = 0; + + virtual bool operator<(BaseVersion &a) + { + return name() < a.name(); + }; + virtual bool operator>(BaseVersion &a) + { + return name() > a.name(); + }; }; typedef std::shared_ptr BaseVersionPtr; diff --git a/api/logic/BaseVersionList.cpp b/api/logic/BaseVersionList.cpp index 31a635d7..b3f44463 100644 --- a/api/logic/BaseVersionList.cpp +++ b/api/logic/BaseVersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,78 +22,78 @@ 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(); + for (int i = 0; i < count(); i++) + { + if (at(i)->descriptor() == descriptor) + return at(i); + } + return BaseVersionPtr(); } BaseVersionPtr BaseVersionList::getRecommended() const { - if (count() <= 0) - return BaseVersionPtr(); - else - return at(0); + 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.isValid()) + return QVariant(); - if (index.row() > count()) - return QVariant(); + if (index.row() > count()) + return QVariant(); - BaseVersionPtr version = at(index.row()); + BaseVersionPtr version = at(index.row()); - switch (role) - { - case VersionPointerRole: - return qVariantFromValue(version); + switch (role) + { + case VersionPointerRole: + return qVariantFromValue(version); - case VersionRole: - return version->name(); + case VersionRole: + return version->name(); - case VersionIdRole: - return version->descriptor(); + case VersionIdRole: + return version->descriptor(); - case TypeRole: - return version->typeString(); + case TypeRole: + return version->typeString(); - default: - return QVariant(); - } + default: + return QVariant(); + } } BaseVersionList::RoleList BaseVersionList::providesRoles() const { - return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; + return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; } int BaseVersionList::rowCount(const QModelIndex &parent) const { - // Return count - return count(); + // Return count + return count(); } int BaseVersionList::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QHash BaseVersionList::roleNames() const { - QHash roles = QAbstractListModel::roleNames(); - roles.insert(VersionRole, "version"); - roles.insert(VersionIdRole, "versionId"); - roles.insert(ParentVersionRole, "parentGameVersion"); - roles.insert(RecommendedRole, "recommended"); - roles.insert(LatestRole, "latest"); - roles.insert(TypeRole, "type"); - roles.insert(BranchRole, "branch"); - roles.insert(PathRole, "path"); - roles.insert(ArchitectureRole, "architecture"); - return roles; + QHash roles = QAbstractListModel::roleNames(); + roles.insert(VersionRole, "version"); + roles.insert(VersionIdRole, "versionId"); + roles.insert(ParentVersionRole, "parentGameVersion"); + roles.insert(RecommendedRole, "recommended"); + roles.insert(LatestRole, "latest"); + roles.insert(TypeRole, "type"); + roles.insert(BranchRole, "branch"); + roles.insert(PathRole, "path"); + roles.insert(ArchitectureRole, "architecture"); + return roles; } diff --git a/api/logic/BaseVersionList.h b/api/logic/BaseVersionList.h index b609e039..64a33d3e 100644 --- a/api/logic/BaseVersionList.h +++ b/api/logic/BaseVersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,85 +38,85 @@ */ class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum ModelRoles - { - VersionPointerRole = Qt::UserRole, - VersionRole, - VersionIdRole, - ParentVersionRole, - RecommendedRole, - LatestRole, - TypeRole, - BranchRole, - PathRole, - ArchitectureRole, - SortRole - }; - typedef QList RoleList; + enum ModelRoles + { + VersionPointerRole = Qt::UserRole, + VersionRole, + VersionIdRole, + ParentVersionRole, + RecommendedRole, + LatestRole, + TypeRole, + BranchRole, + PathRole, + ArchitectureRole, + SortRole + }; + typedef QList RoleList; - explicit BaseVersionList(QObject *parent = 0); + 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 shared_qobject_ptr getLoadTask() = 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 shared_qobject_ptr getLoadTask() = 0; - //! Checks whether or not the list is loaded. If this returns false, the list should be - //loaded. - virtual bool isLoaded() = 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; + //! 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; + //! Returns the number of versions in the list. + virtual int count() const = 0; - //////// List Model Functions //////// - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QHash roleNames() const override; + //////// List Model Functions //////// + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QHash roleNames() const override; - //! which roles are provided by this version list? - virtual RoleList providesRoles() const; + //! which roles are provided by this version list? + virtual RoleList providesRoles() const; - /*! - * \brief Finds a version by its descriptor. - * \param descriptor 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 Finds a version by its descriptor. + * \param descriptor 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 recommended version from this list - * If the list doesn't support recommended versions, this works exactly as getLatestStable - */ - virtual BaseVersionPtr getRecommended() const; + /*! + * \brief Gets the recommended version from this list + * If the list doesn't support recommended versions, this works exactly as getLatestStable + */ + virtual BaseVersionPtr getRecommended() const; - /*! - * Sorts the version list. - */ - virtual void sortVersions() = 0; + /*! + * Sorts the version list. + */ + virtual void sortVersions() = 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; + /*! + * 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/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt index d6b61d23..db1a7dec 100644 --- a/api/logic/CMakeLists.txt +++ b/api/logic/CMakeLists.txt @@ -3,444 +3,467 @@ project(MultiMC_logic) include (UnitTest) set(CORE_SOURCES - # LOGIC - Base classes and infrastructure - BaseInstaller.h - BaseInstaller.cpp - BaseVersionList.h - BaseVersionList.cpp - InstanceList.h - InstanceList.cpp - InstanceTask.h - InstanceTask.cpp - LoggedProcess.h - LoggedProcess.cpp - MessageLevel.cpp - MessageLevel.h - BaseInstanceProvider.h - FolderInstanceProvider.h - FolderInstanceProvider.cpp - BaseVersion.h - BaseInstance.h - BaseInstance.cpp - NullInstance.h - MMCZip.h - MMCZip.cpp - MMCStrings.h - MMCStrings.cpp - - # Basic instance manipulation tasks (derived from InstanceTask) - InstanceCreationTask.h - InstanceCreationTask.cpp - InstanceCopyTask.h - InstanceCopyTask.cpp - InstanceImportTask.h - InstanceImportTask.cpp - - # Use tracking separate from memory management - Usable.h - - # Prefix tree where node names are strings between separators - SeparatorPrefixTree.h - - # WARNING: globals live here - Env.h - Env.cpp - - # String filters - Filter.h - Filter.cpp - - # JSON parsing helpers - Json.h - Json.cpp - - FileSystem.h - FileSystem.cpp - - Exception.h - - # RW lock protected map - RWStorage.h - - # A variable that has an implicit default value and keeps track of changes - DefaultVariable.h - - # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms - QObjectPtr.h - - # Compression support - GZip.h - GZip.cpp - - # Command line parameter parsing - Commandline.h - Commandline.cpp - - # Version number string support - Version.h - Version.cpp - - # A Recursive file system watcher - RecursiveFileSystemWatcher.h - RecursiveFileSystemWatcher.cpp + # LOGIC - Base classes and infrastructure + BaseInstaller.h + BaseInstaller.cpp + BaseVersionList.h + BaseVersionList.cpp + InstanceList.h + InstanceList.cpp + InstanceTask.h + InstanceTask.cpp + LoggedProcess.h + LoggedProcess.cpp + MessageLevel.cpp + MessageLevel.h + BaseVersion.h + BaseInstance.h + BaseInstance.cpp + NullInstance.h + MMCZip.h + MMCZip.cpp + MMCStrings.h + MMCStrings.cpp + + # Basic instance manipulation tasks (derived from InstanceTask) + InstanceCreationTask.h + InstanceCreationTask.cpp + InstanceCopyTask.h + InstanceCopyTask.cpp + InstanceImportTask.h + InstanceImportTask.cpp + + # Use tracking separate from memory management + Usable.h + + # Prefix tree where node names are strings between separators + SeparatorPrefixTree.h + + # WARNING: globals live here + Env.h + Env.cpp + + # String filters + Filter.h + Filter.cpp + + # JSON parsing helpers + Json.h + Json.cpp + + FileSystem.h + FileSystem.cpp + + Exception.h + + # RW lock protected map + RWStorage.h + + # A variable that has an implicit default value and keeps track of changes + DefaultVariable.h + + # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms + QObjectPtr.h + + # Compression support + GZip.h + GZip.cpp + + # Command line parameter parsing + Commandline.h + Commandline.cpp + + # Version number string support + Version.h + Version.cpp + + # A Recursive file system watcher + RecursiveFileSystemWatcher.h + RecursiveFileSystemWatcher.cpp ) add_unit_test(FileSystem - SOURCES FileSystem_test.cpp - LIBS MultiMC_logic - DATA testdata - ) + SOURCES FileSystem_test.cpp + LIBS MultiMC_logic + DATA testdata + ) add_unit_test(GZip - SOURCES GZip_test.cpp - LIBS MultiMC_logic - ) + SOURCES GZip_test.cpp + LIBS MultiMC_logic + ) set(PATHMATCHER_SOURCES - # Path matchers - pathmatcher/FSTreeMatcher.h - pathmatcher/IPathMatcher.h - pathmatcher/MultiMatcher.h - pathmatcher/RegexpMatcher.h + # Path matchers + pathmatcher/FSTreeMatcher.h + pathmatcher/IPathMatcher.h + pathmatcher/MultiMatcher.h + pathmatcher/RegexpMatcher.h ) set(NET_SOURCES - # network stuffs - net/ByteArraySink.h - net/ChecksumValidator.h - net/Download.cpp - net/Download.h - net/FileSink.cpp - net/FileSink.h - net/HttpMetaCache.cpp - net/HttpMetaCache.h - net/MetaCacheSink.cpp - net/MetaCacheSink.h - net/NetAction.h - net/NetJob.cpp - net/NetJob.h - net/PasteUpload.cpp - net/PasteUpload.h - net/Sink.h - net/URLConstants.cpp - net/URLConstants.h - net/Validator.h + # network stuffs + net/ByteArraySink.h + net/ChecksumValidator.h + net/Download.cpp + net/Download.h + net/FileSink.cpp + net/FileSink.h + net/HttpMetaCache.cpp + net/HttpMetaCache.h + net/MetaCacheSink.cpp + net/MetaCacheSink.h + net/NetAction.h + net/NetJob.cpp + net/NetJob.h + net/PasteUpload.cpp + net/PasteUpload.h + net/Sink.h + net/URLConstants.cpp + net/URLConstants.h + net/Validator.h ) # Game launch logic set(LAUNCH_SOURCES - launch/steps/PostLaunchCommand.cpp - launch/steps/PostLaunchCommand.h - launch/steps/PreLaunchCommand.cpp - launch/steps/PreLaunchCommand.h - launch/steps/TextPrint.cpp - launch/steps/TextPrint.h - launch/steps/Update.cpp - launch/steps/Update.h - launch/LaunchStep.cpp - launch/LaunchStep.h - launch/LaunchTask.cpp - launch/LaunchTask.h - launch/LogModel.cpp - launch/LogModel.h + launch/steps/PostLaunchCommand.cpp + launch/steps/PostLaunchCommand.h + launch/steps/PreLaunchCommand.cpp + launch/steps/PreLaunchCommand.h + launch/steps/TextPrint.cpp + launch/steps/TextPrint.h + launch/steps/Update.cpp + launch/steps/Update.h + launch/LaunchStep.cpp + launch/LaunchStep.h + launch/LaunchTask.cpp + launch/LaunchTask.h + launch/LogModel.cpp + launch/LogModel.h ) # Old update system set(UPDATE_SOURCES - updater/GoUpdate.h - updater/GoUpdate.cpp - updater/UpdateChecker.h - updater/UpdateChecker.cpp - updater/DownloadTask.h - updater/DownloadTask.cpp + updater/GoUpdate.h + updater/GoUpdate.cpp + updater/UpdateChecker.h + updater/UpdateChecker.cpp + updater/DownloadTask.h + updater/DownloadTask.cpp ) add_unit_test(UpdateChecker - SOURCES updater/UpdateChecker_test.cpp - LIBS MultiMC_logic - DATA updater/testdata - ) + SOURCES updater/UpdateChecker_test.cpp + LIBS MultiMC_logic + DATA updater/testdata + ) add_unit_test(DownloadTask - SOURCES updater/DownloadTask_test.cpp - LIBS MultiMC_logic - DATA updater/testdata - ) + SOURCES updater/DownloadTask_test.cpp + LIBS MultiMC_logic + DATA updater/testdata + ) # Rarely used notifications set(NOTIFICATIONS_SOURCES - # Notifications - short warning messages - notifications/NotificationChecker.h - notifications/NotificationChecker.cpp + # Notifications - short warning messages + notifications/NotificationChecker.h + notifications/NotificationChecker.cpp ) # Backend for the news bar... there's usually no news. set(NEWS_SOURCES - # News System - news/NewsChecker.h - news/NewsChecker.cpp - news/NewsEntry.h - news/NewsEntry.cpp + # News System + news/NewsChecker.h + news/NewsChecker.cpp + news/NewsEntry.h + news/NewsEntry.cpp ) # Icon interface set(ICONS_SOURCES - # News System - icons/IIconList.h - icons/IIconList.cpp + # Icons System and related code + icons/IIconList.h + icons/IIconList.cpp + icons/IconUtils.h + icons/IconUtils.cpp ) # Minecraft services status checker set(STATUS_SOURCES - # Status system - status/StatusChecker.h - status/StatusChecker.cpp + # Status system + status/StatusChecker.h + status/StatusChecker.cpp ) # Support for Minecraft instances and launch set(MINECRAFT_SOURCES - # Minecraft support - minecraft/auth/AuthSession.h - minecraft/auth/AuthSession.cpp - minecraft/auth/MojangAccountList.h - minecraft/auth/MojangAccountList.cpp - minecraft/auth/MojangAccount.h - minecraft/auth/MojangAccount.cpp - minecraft/auth/YggdrasilTask.h - minecraft/auth/YggdrasilTask.cpp - minecraft/auth/flows/AuthenticateTask.h - minecraft/auth/flows/AuthenticateTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/RefreshTask.cpp - minecraft/auth/flows/ValidateTask.h - minecraft/auth/flows/ValidateTask.cpp - minecraft/update/AssetUpdateTask.h - minecraft/update/AssetUpdateTask.cpp - minecraft/update/FMLLibrariesTask.cpp - minecraft/update/FMLLibrariesTask.h - minecraft/update/FoldersTask.cpp - minecraft/update/FoldersTask.h - minecraft/update/LibrariesTask.cpp - minecraft/update/LibrariesTask.h - minecraft/launch/ClaimAccount.cpp - minecraft/launch/ClaimAccount.h - minecraft/launch/CreateServerResourcePacksFolder.cpp - minecraft/launch/CreateServerResourcePacksFolder.h - minecraft/launch/ModMinecraftJar.cpp - minecraft/launch/ModMinecraftJar.h - minecraft/launch/DirectJavaLaunch.cpp - minecraft/launch/DirectJavaLaunch.h - minecraft/launch/ExtractNatives.cpp - minecraft/launch/ExtractNatives.h - minecraft/launch/LauncherPartLaunch.cpp - minecraft/launch/LauncherPartLaunch.h - minecraft/launch/PrintInstanceInfo.cpp - minecraft/launch/PrintInstanceInfo.h - minecraft/legacy/LegacyModList.h - minecraft/legacy/LegacyModList.cpp - minecraft/legacy/LegacyInstance.h - minecraft/legacy/LegacyInstance.cpp - minecraft/legacy/LegacyUpgradeTask.h - minecraft/legacy/LegacyUpgradeTask.cpp - minecraft/GradleSpecifier.h - minecraft/MinecraftInstance.cpp - minecraft/MinecraftInstance.h - minecraft/LaunchProfile.cpp - minecraft/LaunchProfile.h - minecraft/Component.cpp - minecraft/Component.h - minecraft/ComponentList.cpp - minecraft/ComponentList.h - minecraft/ComponentUpdateTask.cpp - minecraft/ComponentUpdateTask.h - minecraft/MinecraftLoadAndCheck.h - minecraft/MinecraftLoadAndCheck.cpp - minecraft/MinecraftUpdate.h - minecraft/MinecraftUpdate.cpp - minecraft/MojangVersionFormat.cpp - minecraft/MojangVersionFormat.h - minecraft/Rule.cpp - minecraft/Rule.h - minecraft/OneSixVersionFormat.cpp - minecraft/OneSixVersionFormat.h - minecraft/OpSys.cpp - minecraft/OpSys.h - minecraft/ParseUtils.cpp - minecraft/ParseUtils.h - minecraft/ProfileUtils.cpp - minecraft/ProfileUtils.h - minecraft/Library.cpp - minecraft/Library.h - minecraft/MojangDownloadInfo.h - minecraft/VersionFile.cpp - minecraft/VersionFile.h - minecraft/VersionFilterData.h - minecraft/VersionFilterData.cpp - minecraft/Mod.h - minecraft/Mod.cpp - minecraft/ModList.h - minecraft/ModList.cpp - minecraft/World.h - minecraft/World.cpp - minecraft/WorldList.h - minecraft/WorldList.cpp - - # Assets - minecraft/AssetsUtils.h - minecraft/AssetsUtils.cpp - - # Forge and all things forge related - minecraft/forge/ForgeXzDownload.h - minecraft/forge/ForgeXzDownload.cpp - - # Skin upload utilities - minecraft/SkinUpload.cpp - minecraft/SkinUpload.h - ) + # Minecraft support + minecraft/auth/AuthSession.h + minecraft/auth/AuthSession.cpp + minecraft/auth/MojangAccountList.h + minecraft/auth/MojangAccountList.cpp + minecraft/auth/MojangAccount.h + minecraft/auth/MojangAccount.cpp + minecraft/auth/YggdrasilTask.h + minecraft/auth/YggdrasilTask.cpp + minecraft/auth/flows/AuthenticateTask.h + minecraft/auth/flows/AuthenticateTask.cpp + minecraft/auth/flows/RefreshTask.cpp + minecraft/auth/flows/RefreshTask.cpp + minecraft/auth/flows/ValidateTask.h + minecraft/auth/flows/ValidateTask.cpp + + minecraft/gameoptions/GameOptions.h + minecraft/gameoptions/GameOptions.cpp + + minecraft/update/AssetUpdateTask.h + minecraft/update/AssetUpdateTask.cpp + minecraft/update/FMLLibrariesTask.cpp + minecraft/update/FMLLibrariesTask.h + minecraft/update/FoldersTask.cpp + minecraft/update/FoldersTask.h + minecraft/update/LibrariesTask.cpp + minecraft/update/LibrariesTask.h + + minecraft/launch/ClaimAccount.cpp + minecraft/launch/ClaimAccount.h + minecraft/launch/CreateServerResourcePacksFolder.cpp + minecraft/launch/CreateServerResourcePacksFolder.h + minecraft/launch/ModMinecraftJar.cpp + minecraft/launch/ModMinecraftJar.h + minecraft/launch/DirectJavaLaunch.cpp + minecraft/launch/DirectJavaLaunch.h + minecraft/launch/ExtractNatives.cpp + minecraft/launch/ExtractNatives.h + minecraft/launch/LauncherPartLaunch.cpp + minecraft/launch/LauncherPartLaunch.h + minecraft/launch/PrintInstanceInfo.cpp + minecraft/launch/PrintInstanceInfo.h + minecraft/launch/ReconstructAssets.cpp + minecraft/launch/ReconstructAssets.h + minecraft/launch/ScanModFolders.cpp + minecraft/launch/ScanModFolders.h + + minecraft/legacy/LegacyModList.h + minecraft/legacy/LegacyModList.cpp + minecraft/legacy/LegacyInstance.h + minecraft/legacy/LegacyInstance.cpp + minecraft/legacy/LegacyUpgradeTask.h + minecraft/legacy/LegacyUpgradeTask.cpp + + minecraft/GradleSpecifier.h + minecraft/MinecraftInstance.cpp + minecraft/MinecraftInstance.h + minecraft/LaunchProfile.cpp + minecraft/LaunchProfile.h + minecraft/Component.cpp + minecraft/Component.h + minecraft/ComponentList.cpp + minecraft/ComponentList.h + minecraft/ComponentUpdateTask.cpp + minecraft/ComponentUpdateTask.h + minecraft/MinecraftLoadAndCheck.h + minecraft/MinecraftLoadAndCheck.cpp + minecraft/MinecraftUpdate.h + minecraft/MinecraftUpdate.cpp + minecraft/MojangVersionFormat.cpp + minecraft/MojangVersionFormat.h + minecraft/Rule.cpp + minecraft/Rule.h + minecraft/OneSixVersionFormat.cpp + minecraft/OneSixVersionFormat.h + minecraft/OpSys.cpp + minecraft/OpSys.h + minecraft/ParseUtils.cpp + minecraft/ParseUtils.h + minecraft/ProfileUtils.cpp + minecraft/ProfileUtils.h + minecraft/Library.cpp + minecraft/Library.h + minecraft/MojangDownloadInfo.h + minecraft/VersionFile.cpp + minecraft/VersionFile.h + minecraft/VersionFilterData.h + minecraft/VersionFilterData.cpp + minecraft/World.h + minecraft/World.cpp + minecraft/WorldList.h + minecraft/WorldList.cpp + + minecraft/mod/Mod.h + minecraft/mod/Mod.cpp + minecraft/mod/ModDetails.h + minecraft/mod/ModFolderModel.h + minecraft/mod/ModFolderModel.cpp + minecraft/mod/ModFolderLoadTask.h + minecraft/mod/ModFolderLoadTask.cpp + minecraft/mod/LocalModParseTask.h + minecraft/mod/LocalModParseTask.cpp + + # Assets + minecraft/AssetsUtils.h + minecraft/AssetsUtils.cpp + + # Forge and all things forge related + minecraft/forge/ForgeXzDownload.h + minecraft/forge/ForgeXzDownload.cpp + + # Skin upload utilities + minecraft/SkinUpload.cpp + minecraft/SkinUpload.h + ) add_unit_test(GradleSpecifier - SOURCES minecraft/GradleSpecifier_test.cpp - LIBS MultiMC_logic - ) + SOURCES minecraft/GradleSpecifier_test.cpp + LIBS MultiMC_logic + ) add_unit_test(MojangVersionFormat - SOURCES minecraft/MojangVersionFormat_test.cpp - LIBS MultiMC_logic - DATA minecraft/testdata - ) + SOURCES minecraft/MojangVersionFormat_test.cpp + LIBS MultiMC_logic + DATA minecraft/testdata + ) add_unit_test(Library - SOURCES minecraft/Library_test.cpp - LIBS MultiMC_logic - ) + SOURCES minecraft/Library_test.cpp + LIBS MultiMC_logic + ) # FIXME: shares data with FileSystem test -add_unit_test(ModList - SOURCES minecraft/ModList_test.cpp - DATA testdata - LIBS MultiMC_logic - ) +add_unit_test(ModFolderModel + SOURCES minecraft/mod/ModFolderModel_test.cpp + DATA testdata + LIBS MultiMC_logic + ) add_unit_test(ParseUtils - SOURCES minecraft/ParseUtils_test.cpp - LIBS MultiMC_logic - ) + SOURCES minecraft/ParseUtils_test.cpp + LIBS MultiMC_logic + ) # the screenshots feature set(SCREENSHOTS_SOURCES - screenshots/Screenshot.h - screenshots/ImgurUpload.h - screenshots/ImgurUpload.cpp - screenshots/ImgurAlbumCreation.h - screenshots/ImgurAlbumCreation.cpp + screenshots/Screenshot.h + screenshots/ImgurUpload.h + screenshots/ImgurUpload.cpp + screenshots/ImgurAlbumCreation.h + screenshots/ImgurAlbumCreation.cpp ) set(TASKS_SOURCES - # Tasks - tasks/Task.h - tasks/Task.cpp - tasks/SequentialTask.h - tasks/SequentialTask.cpp + # Tasks + tasks/Task.h + tasks/Task.cpp + tasks/SequentialTask.h + tasks/SequentialTask.cpp ) set(SETTINGS_SOURCES - # Settings - settings/INIFile.cpp - settings/INIFile.h - settings/INISettingsObject.cpp - settings/INISettingsObject.h - settings/OverrideSetting.cpp - settings/OverrideSetting.h - settings/PassthroughSetting.cpp - settings/PassthroughSetting.h - settings/Setting.cpp - settings/Setting.h - settings/SettingsObject.cpp - settings/SettingsObject.h + # Settings + settings/INIFile.cpp + settings/INIFile.h + settings/INISettingsObject.cpp + settings/INISettingsObject.h + settings/OverrideSetting.cpp + settings/OverrideSetting.h + settings/PassthroughSetting.cpp + settings/PassthroughSetting.h + settings/Setting.cpp + settings/Setting.h + settings/SettingsObject.cpp + settings/SettingsObject.h ) add_unit_test(INIFile - SOURCES settings/INIFile_test.cpp - LIBS MultiMC_logic - ) + SOURCES settings/INIFile_test.cpp + LIBS MultiMC_logic + ) set(JAVA_SOURCES - # Java related code - java/launch/CheckJava.cpp - java/launch/CheckJava.h - java/JavaChecker.h - java/JavaChecker.cpp - java/JavaCheckerJob.h - java/JavaCheckerJob.cpp - java/JavaInstall.h - java/JavaInstall.cpp - java/JavaInstallList.h - java/JavaInstallList.cpp - java/JavaUtils.h - java/JavaUtils.cpp - java/JavaVersion.h - java/JavaVersion.cpp + # Java related code + java/launch/CheckJava.cpp + java/launch/CheckJava.h + java/JavaChecker.h + java/JavaChecker.cpp + java/JavaCheckerJob.h + java/JavaCheckerJob.cpp + java/JavaInstall.h + java/JavaInstall.cpp + java/JavaInstallList.h + java/JavaInstallList.cpp + java/JavaUtils.h + java/JavaUtils.cpp + java/JavaVersion.h + java/JavaVersion.cpp ) add_unit_test(JavaVersion - SOURCES java/JavaVersion_test.cpp - LIBS MultiMC_logic - ) + SOURCES java/JavaVersion_test.cpp + LIBS MultiMC_logic + ) set(TRANSLATIONS_SOURCES - translations/TranslationsModel.h - translations/TranslationsModel.cpp + translations/TranslationsModel.h + translations/TranslationsModel.cpp + translations/POTranslator.h + translations/POTranslator.cpp ) set(TOOLS_SOURCES - # Tools - tools/BaseExternalTool.cpp - tools/BaseExternalTool.h - tools/BaseProfiler.cpp - tools/BaseProfiler.h - tools/JProfiler.cpp - tools/JProfiler.h - tools/JVisualVM.cpp - tools/JVisualVM.h - tools/MCEditTool.cpp - tools/MCEditTool.h + # Tools + tools/BaseExternalTool.cpp + tools/BaseExternalTool.h + tools/BaseProfiler.cpp + tools/BaseProfiler.h + tools/JProfiler.cpp + tools/JProfiler.h + tools/JVisualVM.cpp + tools/JVisualVM.h + tools/MCEditTool.cpp + tools/MCEditTool.h ) set(META_SOURCES - # Metadata sources - meta/JsonFormat.cpp - meta/JsonFormat.h - meta/BaseEntity.cpp - meta/BaseEntity.h - meta/VersionList.cpp - meta/VersionList.h - meta/Version.cpp - meta/Version.h - meta/Index.cpp - meta/Index.h + # Metadata sources + meta/JsonFormat.cpp + meta/JsonFormat.h + meta/BaseEntity.cpp + meta/BaseEntity.h + meta/VersionList.cpp + meta/VersionList.h + meta/Version.cpp + meta/Version.h + meta/Index.cpp + meta/Index.h ) set(FTB_SOURCES - modplatform/ftb/FtbPackFetchTask.h - modplatform/ftb/FtbPackFetchTask.cpp - modplatform/ftb/FtbPackInstallTask.h - modplatform/ftb/FtbPackInstallTask.cpp + modplatform/ftb/FtbPackFetchTask.h + modplatform/ftb/FtbPackFetchTask.cpp + modplatform/ftb/FtbPackInstallTask.h + modplatform/ftb/FtbPackInstallTask.cpp - modplatform/ftb/PackHelpers.h + modplatform/ftb/FtbPrivatePackManager.h + modplatform/ftb/FtbPrivatePackManager.cpp + + modplatform/ftb/PackHelpers.h ) set(FLAME_SOURCES - # Flame - modplatform/flame/PackManifest.h - modplatform/flame/PackManifest.cpp - modplatform/flame/FileResolvingTask.h - modplatform/flame/FileResolvingTask.cpp + # Flame + modplatform/flame/PackManifest.h + modplatform/flame/PackManifest.cpp + modplatform/flame/FileResolvingTask.h + modplatform/flame/FileResolvingTask.cpp + modplatform/flame/UrlResolvingTask.h + modplatform/flame/UrlResolvingTask.cpp ) add_unit_test(Index - SOURCES meta/Index_test.cpp - LIBS MultiMC_logic - ) + SOURCES meta/Index_test.cpp + LIBS MultiMC_logic + ) ################################ COMPILE ################################ @@ -448,25 +471,25 @@ add_unit_test(Index find_package(ZLIB REQUIRED) set(LOGIC_SOURCES - ${CORE_SOURCES} - ${PATHMATCHER_SOURCES} - ${NET_SOURCES} - ${LAUNCH_SOURCES} - ${UPDATE_SOURCES} - ${NOTIFICATIONS_SOURCES} - ${NEWS_SOURCES} - ${STATUS_SOURCES} - ${MINECRAFT_SOURCES} - ${SCREENSHOTS_SOURCES} - ${TASKS_SOURCES} - ${SETTINGS_SOURCES} - ${JAVA_SOURCES} - ${TRANSLATIONS_SOURCES} - ${TOOLS_SOURCES} - ${META_SOURCES} - ${ICONS_SOURCES} - ${FTB_SOURCES} - ${FLAME_SOURCES} + ${CORE_SOURCES} + ${PATHMATCHER_SOURCES} + ${NET_SOURCES} + ${LAUNCH_SOURCES} + ${UPDATE_SOURCES} + ${NOTIFICATIONS_SOURCES} + ${NEWS_SOURCES} + ${STATUS_SOURCES} + ${MINECRAFT_SOURCES} + ${SCREENSHOTS_SOURCES} + ${TASKS_SOURCES} + ${SETTINGS_SOURCES} + ${JAVA_SOURCES} + ${TRANSLATIONS_SOURCES} + ${TOOLS_SOURCES} + ${META_SOURCES} + ${ICONS_SOURCES} + ${FTB_SOURCES} + ${FLAME_SOURCES} ) add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) @@ -476,14 +499,14 @@ generate_export_header(MultiMC_logic) # Link target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES}) -qt5_use_modules(MultiMC_logic Core Xml Network Concurrent) +target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent) # Mark and export headers target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") # Install it install( - TARGETS MultiMC_logic - RUNTIME DESTINATION ${LIBRARY_DEST_DIR} - LIBRARY DESTINATION ${LIBRARY_DEST_DIR} + TARGETS MultiMC_logic + RUNTIME DESTINATION ${LIBRARY_DEST_DIR} + LIBRARY DESTINATION ${LIBRARY_DEST_DIR} ) diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp index eac9db09..59f1666b 100644 --- a/api/logic/Commandline.cpp +++ b/api/logic/Commandline.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -27,453 +27,453 @@ namespace Commandline // commandline splitter QStringList splitArgs(QString args) { - QStringList argv; - QString current; - bool escape = false; - QChar inquotes; - for (int i = 0; i < args.length(); i++) - { - QChar cchar = args.at(i); - - // \ escaped - if (escape) - { - current += cchar; - escape = false; - // in "quotes" - } - else if (!inquotes.isNull()) - { - if (cchar == '\\') - escape = true; - else if (cchar == inquotes) - inquotes = 0; - else - current += cchar; - // otherwise - } - else - { - if (cchar == ' ') - { - if (!current.isEmpty()) - { - argv << current; - current.clear(); - } - } - else if (cchar == '"' || cchar == '\'') - inquotes = cchar; - else - current += cchar; - } - } - if (!current.isEmpty()) - argv << current; - return argv; + QStringList argv; + QString current; + bool escape = false; + QChar inquotes; + for (int i = 0; i < args.length(); i++) + { + QChar cchar = args.at(i); + + // \ escaped + if (escape) + { + current += cchar; + escape = false; + // in "quotes" + } + else if (!inquotes.isNull()) + { + if (cchar == '\\') + escape = true; + else if (cchar == inquotes) + inquotes = 0; + else + current += cchar; + // otherwise + } + else + { + if (cchar == ' ') + { + if (!current.isEmpty()) + { + argv << current; + current.clear(); + } + } + else if (cchar == '"' || cchar == '\'') + inquotes = cchar; + else + current += cchar; + } + } + if (!current.isEmpty()) + argv << current; + return argv; } Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) { - m_flagStyle = flagStyle; - m_argStyle = argStyle; + m_flagStyle = flagStyle; + m_argStyle = argStyle; } // styles setter/getter void Parser::setArgumentStyle(ArgumentStyle::Enum style) { - m_argStyle = style; + m_argStyle = style; } ArgumentStyle::Enum Parser::argumentStyle() { - return m_argStyle; + return m_argStyle; } void Parser::setFlagStyle(FlagStyle::Enum style) { - m_flagStyle = style; + m_flagStyle = style; } FlagStyle::Enum Parser::flagStyle() { - return m_flagStyle; + return m_flagStyle; } // setup methods void Parser::addSwitch(QString name, bool def) { - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otSwitch; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otSwitch; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); } void Parser::addOption(QString name, QVariant def) { - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otOption; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); + if (m_params.contains(name)) + throw "Name not unique"; + + OptionDef *param = new OptionDef; + param->type = otOption; + param->name = name; + param->metavar = QString("<%1>").arg(name); + param->def = def; + + m_options[name] = param; + m_params[name] = (CommonDef *)param; + m_optionList.append(param); } void Parser::addArgument(QString name, bool required, QVariant def) { - if (m_params.contains(name)) - throw "Name not unique"; + if (m_params.contains(name)) + throw "Name not unique"; - PositionalDef *param = new PositionalDef; - param->name = name; - param->def = def; - param->required = required; - param->metavar = name; + PositionalDef *param = new PositionalDef; + param->name = name; + param->def = def; + param->required = required; + param->metavar = name; - m_positionals.append(param); - m_params[name] = (CommonDef *)param; + m_positionals.append(param); + m_params[name] = (CommonDef *)param; } void Parser::addDocumentation(QString name, QString doc, QString metavar) { - if (!m_params.contains(name)) - throw "Name does not exist"; + if (!m_params.contains(name)) + throw "Name does not exist"; - CommonDef *param = m_params[name]; - param->doc = doc; - if (!metavar.isNull()) - param->metavar = metavar; + CommonDef *param = m_params[name]; + param->doc = doc; + if (!metavar.isNull()) + param->metavar = metavar; } void Parser::addShortOpt(QString name, QChar flag) { - if (!m_params.contains(name)) - throw "Name does not exist"; - if (!m_options.contains(name)) - throw "Name is not an Option or Swtich"; - - OptionDef *param = m_options[name]; - m_flags[flag] = param; - param->flag = flag; + if (!m_params.contains(name)) + throw "Name does not exist"; + if (!m_options.contains(name)) + throw "Name is not an Option or Swtich"; + + OptionDef *param = m_options[name]; + m_flags[flag] = param; + param->flag = flag; } // help methods QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) { - QStringList help; - help << compileUsage(progName, useFlags) << "\r\n"; - - // positionals - if (!m_positionals.isEmpty()) - { - help << "\r\n"; - help << "Positional arguments:\r\n"; - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - help << " " << param->metavar; - help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); - help << param->doc << "\r\n"; - } - } - - // Options - if (!m_optionList.isEmpty()) - { - help << "\r\n"; - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - help << "Options & Switches:\r\n"; - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - help << " "; - int nameLength = optPrefix.length() + option->name.length(); - if (!option->flag.isNull()) - { - nameLength += 3 + flagPrefix.length(); - help << flagPrefix << option->flag << ", "; - } - help << optPrefix << option->name; - if (option->type == otOption) - { - QString arg = QString("%1%2").arg( - ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); - nameLength += arg.length(); - help << arg; - } - help << " " << QString(helpIndent - nameLength - 1, ' '); - help << option->doc << "\r\n"; - } - } - - return help.join(""); + QStringList help; + help << compileUsage(progName, useFlags) << "\r\n"; + + // positionals + if (!m_positionals.isEmpty()) + { + help << "\r\n"; + help << "Positional arguments:\r\n"; + QListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + help << " " << param->metavar; + help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); + help << param->doc << "\r\n"; + } + } + + // Options + if (!m_optionList.isEmpty()) + { + help << "\r\n"; + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + help << "Options & Switches:\r\n"; + QListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + help << " "; + int nameLength = optPrefix.length() + option->name.length(); + if (!option->flag.isNull()) + { + nameLength += 3 + flagPrefix.length(); + help << flagPrefix << option->flag << ", "; + } + help << optPrefix << option->name; + if (option->type == otOption) + { + QString arg = QString("%1%2").arg( + ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); + nameLength += arg.length(); + help << arg; + } + help << " " << QString(helpIndent - nameLength - 1, ' '); + help << option->doc << "\r\n"; + } + } + + return help.join(""); } QString Parser::compileUsage(QString progName, bool useFlags) { - QStringList usage; - usage << "Usage: " << progName; - - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - // options - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - usage << " ["; - if (!option->flag.isNull() && useFlags) - usage << flagPrefix << option->flag; - else - usage << optPrefix << option->name; - if (option->type == otOption) - usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; - usage << "]"; - } - - // arguments - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - usage << " " << (param->required ? "<" : "["); - usage << param->metavar; - usage << (param->required ? ">" : "]"); - } - - return usage.join(""); + QStringList usage; + usage << "Usage: " << progName; + + QString optPrefix, flagPrefix; + getPrefix(optPrefix, flagPrefix); + + // options + QListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + usage << " ["; + if (!option->flag.isNull() && useFlags) + usage << flagPrefix << option->flag; + else + usage << optPrefix << option->name; + if (option->type == otOption) + usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; + usage << "]"; + } + + // arguments + QListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *param = it2.next(); + usage << " " << (param->required ? "<" : "["); + usage << param->metavar; + usage << (param->required ? ">" : "]"); + } + + return usage.join(""); } // parsing QHash Parser::parse(QStringList argv) { - QHash map; + QHash map; - QStringListIterator it(argv); - QString programName = it.next(); + QStringListIterator it(argv); + QString programName = it.next(); - QString optionPrefix; - QString flagPrefix; - QListIterator positionals(m_positionals); - QStringList expecting; + QString optionPrefix; + QString flagPrefix; + QListIterator positionals(m_positionals); + QStringList expecting; - getPrefix(optionPrefix, flagPrefix); + getPrefix(optionPrefix, flagPrefix); - while (it.hasNext()) - { - QString arg = it.next(); + while (it.hasNext()) + { + QString arg = it.next(); - if (!expecting.isEmpty()) - // we were expecting an argument - { - QString name = expecting.first(); + if (!expecting.isEmpty()) + // we were expecting an argument + { + QString name = expecting.first(); /* - if (map.contains(name)) - throw ParsingError( - QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); + if (map.contains(name)) + throw ParsingError( + QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); */ - map[name] = QVariant(arg); - - expecting.removeFirst(); - continue; - } - - if (arg.startsWith(optionPrefix)) - // we have an option - { - // qDebug("Found option %s", qPrintable(arg)); - - QString name = arg.mid(optionPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - name.contains("=")) - { - int i = name.indexOf("="); - equals = name.mid(i + 1); - name = name.left(i); - } - - if (m_options.contains(name)) - { - /* - if (map.contains(name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(name, optionPrefix)); + map[name] = QVariant(arg); + + expecting.removeFirst(); + continue; + } + + if (arg.startsWith(optionPrefix)) + // we have an option + { + // qDebug("Found option %s", qPrintable(arg)); + + QString name = arg.mid(optionPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + name.contains("=")) + { + int i = name.indexOf("="); + equals = name.mid(i + 1); + name = name.left(i); + } + + if (m_options.contains(name)) + { + /* + if (map.contains(name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(name, optionPrefix)); */ - OptionDef *option = m_options[name]; - if (option->type == otSwitch) - map[name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(name); - else if (!equals.isNull()) - map[name] = equals; - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(name); - else - throw ParsingError(QString("Option %2%1 reqires an argument.") - .arg(name, optionPrefix)); - } - - continue; - } - - throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); - } - - if (arg.startsWith(flagPrefix)) - // we have (a) flag(s) - { - // qDebug("Found flags %s", qPrintable(arg)); - - QString flags = arg.mid(flagPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - flags.contains("=")) - { - int i = flags.indexOf("="); - equals = flags.mid(i + 1); - flags = flags.left(i); - } - - for (int i = 0; i < flags.length(); i++) - { - QChar flag = flags.at(i); - - if (!m_flags.contains(flag)) - throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); - - OptionDef *option = m_flags[flag]; + OptionDef *option = m_options[name]; + if (option->type == otSwitch) + map[name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(name); + else if (!equals.isNull()) + map[name] = equals; + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(name); + else + throw ParsingError(QString("Option %2%1 reqires an argument.") + .arg(name, optionPrefix)); + } + + continue; + } + + throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); + } + + if (arg.startsWith(flagPrefix)) + // we have (a) flag(s) + { + // qDebug("Found flags %s", qPrintable(arg)); + + QString flags = arg.mid(flagPrefix.length()); + QString equals; + + if ((m_argStyle == ArgumentStyle::Equals || + m_argStyle == ArgumentStyle::SpaceAndEquals) && + flags.contains("=")) + { + int i = flags.indexOf("="); + equals = flags.mid(i + 1); + flags = flags.left(i); + } + + for (int i = 0; i < flags.length(); i++) + { + QChar flag = flags.at(i); + + if (!m_flags.contains(flag)) + throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); + + OptionDef *option = m_flags[flag]; /* - if (map.contains(option->name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(option->name, optionPrefix)); + if (map.contains(option->name)) + throw ParsingError(QString("Option %2%1 was given multiple times") + .arg(option->name, optionPrefix)); */ - if (option->type == otSwitch) - map[option->name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(option->name); - else if (!equals.isNull()) - if (i == flags.length() - 1) - map[option->name] = equals; - else - throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " - "%1 not last flag in %4%3") - .arg(option->name, flag, flags, flagPrefix)); - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(option->name); - else - throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") - .arg(option->name, flag, flagPrefix)); - } - } - - continue; - } - - // must be a positional argument - if (!positionals.hasNext()) - throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); - - PositionalDef *param = positionals.next(); - - map[param->name] = arg; - } - - // check if we're missing something - if (!expecting.isEmpty()) - throw ParsingError(QString("Was still expecting arguments for %2%1").arg( - expecting.join(QString(", ") + optionPrefix), optionPrefix)); - - while (positionals.hasNext()) - { - PositionalDef *param = positionals.next(); - if (param->required) - throw ParsingError( - QString("Missing required positional argument '%1'").arg(param->name)); - else - map[param->name] = param->def; - } - - // fill out gaps - QListIterator iter(m_optionList); - while (iter.hasNext()) - { - OptionDef *option = iter.next(); - if (!map.contains(option->name)) - map[option->name] = option->def; - } - - return map; + if (option->type == otSwitch) + map[option->name] = true; + else // if (option->type == otOption) + { + if (m_argStyle == ArgumentStyle::Space) + expecting.append(option->name); + else if (!equals.isNull()) + if (i == flags.length() - 1) + map[option->name] = equals; + else + throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " + "%1 not last flag in %4%3") + .arg(option->name, flag, flags, flagPrefix)); + else if (m_argStyle == ArgumentStyle::SpaceAndEquals) + expecting.append(option->name); + else + throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") + .arg(option->name, flag, flagPrefix)); + } + } + + continue; + } + + // must be a positional argument + if (!positionals.hasNext()) + throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); + + PositionalDef *param = positionals.next(); + + map[param->name] = arg; + } + + // check if we're missing something + if (!expecting.isEmpty()) + throw ParsingError(QString("Was still expecting arguments for %2%1").arg( + expecting.join(QString(", ") + optionPrefix), optionPrefix)); + + while (positionals.hasNext()) + { + PositionalDef *param = positionals.next(); + if (param->required) + throw ParsingError( + QString("Missing required positional argument '%1'").arg(param->name)); + else + map[param->name] = param->def; + } + + // fill out gaps + QListIterator iter(m_optionList); + while (iter.hasNext()) + { + OptionDef *option = iter.next(); + if (!map.contains(option->name)) + map[option->name] = option->def; + } + + return map; } // clear defs void Parser::clear() { - m_flags.clear(); - m_params.clear(); - m_options.clear(); - - QMutableListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - it.remove(); - delete option; - } - - QMutableListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *arg = it2.next(); - it2.remove(); - delete arg; - } + m_flags.clear(); + m_params.clear(); + m_options.clear(); + + QMutableListIterator it(m_optionList); + while (it.hasNext()) + { + OptionDef *option = it.next(); + it.remove(); + delete option; + } + + QMutableListIterator it2(m_positionals); + while (it2.hasNext()) + { + PositionalDef *arg = it2.next(); + it2.remove(); + delete arg; + } } // Destructor Parser::~Parser() { - clear(); + clear(); } // getPrefix void Parser::getPrefix(QString &opt, QString &flag) { - if (m_flagStyle == FlagStyle::Windows) - opt = flag = "/"; - else if (m_flagStyle == FlagStyle::Unix) - opt = flag = "-"; - // else if (m_flagStyle == FlagStyle::GNU) - else - { - opt = "--"; - flag = "-"; - } + if (m_flagStyle == FlagStyle::Windows) + opt = flag = "/"; + else if (m_flagStyle == FlagStyle::Unix) + opt = flag = "-"; + // else if (m_flagStyle == FlagStyle::GNU) + else + { + opt = "--"; + flag = "-"; + } } // ParsingError diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h index c8c8be29..5f6e5510 100644 --- a/api/logic/Commandline.h +++ b/api/logic/Commandline.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -51,13 +51,13 @@ namespace FlagStyle { enum Enum { - GNU, /**< --option and -o (GNU Style) */ - Unix, /**< -option and -o (Unix Style) */ - Windows, /**< /option and /o (Windows Style) */ + GNU, /**< --option and -o (GNU Style) */ + Unix, /**< -option and -o (Unix Style) */ + Windows, /**< /option and /o (Windows Style) */ #ifdef Q_OS_WIN32 - Default = Windows + Default = Windows #else - Default = GNU + Default = GNU #endif }; } @@ -69,13 +69,13 @@ namespace ArgumentStyle { enum Enum { - Space, /**< --option=value */ - Equals, /**< --option value */ - SpaceAndEquals, /**< --option[= ]value */ + Space, /**< --option=value */ + Equals, /**< --option value */ + SpaceAndEquals, /**< --option[= ]value */ #ifdef Q_OS_WIN32 - Default = Equals + Default = Equals #else - Default = SpaceAndEquals + Default = SpaceAndEquals #endif }; } @@ -86,7 +86,7 @@ enum Enum class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error { public: - ParsingError(const QString &what); + ParsingError(const QString &what); }; /** @@ -95,158 +95,158 @@ public: class MULTIMC_LOGIC_EXPORT Parser { public: - /** - * @brief Parser constructor - * @param flagStyle the FlagStyle to use in this Parser - * @param argStyle the ArgumentStyle to use in this Parser - */ - Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, - ArgumentStyle::Enum argStyle = ArgumentStyle::Default); - - /** - * @brief set the flag style - * @param style - */ - void setFlagStyle(FlagStyle::Enum style); - - /** - * @brief get the flag style - * @return - */ - FlagStyle::Enum flagStyle(); - - /** - * @brief set the argument style - * @param style - */ - void setArgumentStyle(ArgumentStyle::Enum style); - - /** - * @brief get the argument style - * @return - */ - ArgumentStyle::Enum argumentStyle(); - - /** - * @brief define a boolean switch - * @param name the parameter name - * @param def the default value - */ - void addSwitch(QString name, bool def = false); - - /** - * @brief define an option that takes an additional argument - * @param name the parameter name - * @param def the default value - */ - void addOption(QString name, QVariant def = QVariant()); - - /** - * @brief define a positional argument - * @param name the parameter name - * @param required wether this argument is required - * @param def the default value - */ - void addArgument(QString name, bool required = true, QVariant def = QVariant()); - - /** - * @brief adds a flag to an existing parameter - * @param name the (existing) parameter name - * @param flag the flag character - * @see addSwitch addArgument addOption - * Note: any one parameter can only have one flag - */ - void addShortOpt(QString name, QChar flag); - - /** - * @brief adds documentation to a Parameter - * @param name the parameter name - * @param metavar a string to be displayed as placeholder for the value - * @param doc a QString containing the documentation - * Note: on positional arguments, metavar replaces the name as displayed. - * on options , metavar replaces the value placeholder - */ - void addDocumentation(QString name, QString doc, QString metavar = QString()); - - /** - * @brief generate a help message - * @param progName the program name to use in the help message - * @param helpIndent how much the parameter documentation should be indented - * @param flagsInUsage whether we should use flags instead of options in the usage - * @return a help message - */ - QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); - - /** - * @brief generate a short usage message - * @param progName the program name to use in the usage message - * @param useFlags whether we should use flags instead of options - * @return a usage message - */ - QString compileUsage(QString progName, bool useFlags = true); - - /** - * @brief parse - * @param argv a QStringList containing the program ARGV - * @return a QHash mapping argument names to their values - */ - QHash parse(QStringList argv); - - /** - * @brief clear all definitions - */ - void clear(); - - ~Parser(); + /** + * @brief Parser constructor + * @param flagStyle the FlagStyle to use in this Parser + * @param argStyle the ArgumentStyle to use in this Parser + */ + Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, + ArgumentStyle::Enum argStyle = ArgumentStyle::Default); + + /** + * @brief set the flag style + * @param style + */ + void setFlagStyle(FlagStyle::Enum style); + + /** + * @brief get the flag style + * @return + */ + FlagStyle::Enum flagStyle(); + + /** + * @brief set the argument style + * @param style + */ + void setArgumentStyle(ArgumentStyle::Enum style); + + /** + * @brief get the argument style + * @return + */ + ArgumentStyle::Enum argumentStyle(); + + /** + * @brief define a boolean switch + * @param name the parameter name + * @param def the default value + */ + void addSwitch(QString name, bool def = false); + + /** + * @brief define an option that takes an additional argument + * @param name the parameter name + * @param def the default value + */ + void addOption(QString name, QVariant def = QVariant()); + + /** + * @brief define a positional argument + * @param name the parameter name + * @param required wether this argument is required + * @param def the default value + */ + void addArgument(QString name, bool required = true, QVariant def = QVariant()); + + /** + * @brief adds a flag to an existing parameter + * @param name the (existing) parameter name + * @param flag the flag character + * @see addSwitch addArgument addOption + * Note: any one parameter can only have one flag + */ + void addShortOpt(QString name, QChar flag); + + /** + * @brief adds documentation to a Parameter + * @param name the parameter name + * @param metavar a string to be displayed as placeholder for the value + * @param doc a QString containing the documentation + * Note: on positional arguments, metavar replaces the name as displayed. + * on options , metavar replaces the value placeholder + */ + void addDocumentation(QString name, QString doc, QString metavar = QString()); + + /** + * @brief generate a help message + * @param progName the program name to use in the help message + * @param helpIndent how much the parameter documentation should be indented + * @param flagsInUsage whether we should use flags instead of options in the usage + * @return a help message + */ + QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); + + /** + * @brief generate a short usage message + * @param progName the program name to use in the usage message + * @param useFlags whether we should use flags instead of options + * @return a usage message + */ + QString compileUsage(QString progName, bool useFlags = true); + + /** + * @brief parse + * @param argv a QStringList containing the program ARGV + * @return a QHash mapping argument names to their values + */ + QHash parse(QStringList argv); + + /** + * @brief clear all definitions + */ + void clear(); + + ~Parser(); private: - FlagStyle::Enum m_flagStyle; - ArgumentStyle::Enum m_argStyle; - - enum OptionType - { - otSwitch, - otOption - }; - - // Important: the common part MUST BE COMMON ON ALL THREE structs - struct CommonDef - { - QString name; - QString doc; - QString metavar; - QVariant def; - }; - - struct OptionDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // option - OptionType type; - QChar flag; - }; - - struct PositionalDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // positional - bool required; - }; - - QHash m_options; - QHash m_flags; - QHash m_params; - QList m_positionals; - QList m_optionList; - - void getPrefix(QString &opt, QString &flag); + FlagStyle::Enum m_flagStyle; + ArgumentStyle::Enum m_argStyle; + + enum OptionType + { + otSwitch, + otOption + }; + + // Important: the common part MUST BE COMMON ON ALL THREE structs + struct CommonDef + { + QString name; + QString doc; + QString metavar; + QVariant def; + }; + + struct OptionDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // option + OptionType type; + QChar flag; + }; + + struct PositionalDef + { + // common + QString name; + QString doc; + QString metavar; + QVariant def; + // positional + bool required; + }; + + QHash m_options; + QHash m_flags; + QHash m_params; + QList m_positionals; + QList m_optionList; + + void getPrefix(QString &opt, QString &flag); }; } diff --git a/api/logic/DefaultVariable.h b/api/logic/DefaultVariable.h index 38d7ecc2..5c069bd3 100644 --- a/api/logic/DefaultVariable.h +++ b/api/logic/DefaultVariable.h @@ -4,32 +4,32 @@ template class DefaultVariable { public: - DefaultVariable(const T & value) - { - defaultValue = value; - } - DefaultVariable & operator =(const T & value) - { - currentValue = value; - is_default = currentValue == defaultValue; - is_explicit = true; - return *this; - } - operator const T &() const - { - return is_default ? defaultValue : currentValue; - } - bool isDefault() const - { - return is_default; - } - bool isExplicit() const - { - return is_explicit; - } + DefaultVariable(const T & value) + { + defaultValue = value; + } + DefaultVariable & operator =(const T & value) + { + currentValue = value; + is_default = currentValue == defaultValue; + is_explicit = true; + return *this; + } + operator const T &() const + { + return is_default ? defaultValue : currentValue; + } + bool isDefault() const + { + return is_default; + } + bool isExplicit() const + { + return is_explicit; + } private: - T currentValue; - T defaultValue; - bool is_default = true; - bool is_explicit = false; + T currentValue; + T defaultValue; + bool is_default = true; + bool is_explicit = false; }; diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp index 04a5ab23..77546bbc 100644 --- a/api/logic/Env.cpp +++ b/api/logic/Env.cpp @@ -15,11 +15,12 @@ struct Env::Private { - QNetworkAccessManager m_qnam; - shared_qobject_ptr m_metacache; - std::shared_ptr m_iconlist; - shared_qobject_ptr m_metadataIndex; - QString m_jarsPath; + QNetworkAccessManager m_qnam; + shared_qobject_ptr m_metacache; + std::shared_ptr m_iconlist; + shared_qobject_ptr m_metadataIndex; + QString m_jarsPath; + QSet m_features; }; static Env * instance; @@ -30,152 +31,179 @@ static Env * instance; Env::Env() { - d = new Private(); + d = new Private(); } Env::~Env() { - delete d; + delete d; } Env& Env::Env::getInstance() { - if(!instance) - { - instance = new Env(); - } - return *instance; + if(!instance) + { + instance = new Env(); + } + return *instance; } void Env::dispose() { - delete instance; - instance = nullptr; + delete instance; + instance = nullptr; } shared_qobject_ptr< HttpMetaCache > Env::metacache() { - return d->m_metacache; + return d->m_metacache; } QNetworkAccessManager& Env::qnam() const { - return d->m_qnam; + return d->m_qnam; } std::shared_ptr Env::icons() { - return d->m_iconlist; + return d->m_iconlist; } void Env::registerIconList(std::shared_ptr iconlist) { - d->m_iconlist = iconlist; + d->m_iconlist = iconlist; } shared_qobject_ptr Env::metadataIndex() { - if (!d->m_metadataIndex) - { - d->m_metadataIndex.reset(new Meta::Index()); - } - return d->m_metadataIndex; + if (!d->m_metadataIndex) + { + d->m_metadataIndex.reset(new Meta::Index()); + } + return d->m_metadataIndex; } void Env::initHttpMetaCache() { - auto &m_metacache = d->m_metacache; - m_metacache.reset(new HttpMetaCache("metacache")); - m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath()); - m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath()); - m_metacache->addBase("versions", QDir("versions").absolutePath()); - m_metacache->addBase("libraries", QDir("libraries").absolutePath()); - m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); - m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); - m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); - m_metacache->addBase("general", QDir("cache").absolutePath()); - m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); - m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); - m_metacache->addBase("root", QDir::currentPath()); - m_metacache->addBase("translations", QDir("translations").absolutePath()); - m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); - m_metacache->addBase("meta", QDir("meta").absolutePath()); - m_metacache->Load(); + auto &m_metacache = d->m_metacache; + m_metacache.reset(new HttpMetaCache("metacache")); + m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath()); + m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath()); + m_metacache->addBase("versions", QDir("versions").absolutePath()); + m_metacache->addBase("libraries", QDir("libraries").absolutePath()); + m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); + m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); + m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); + m_metacache->addBase("general", QDir("cache").absolutePath()); + m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath()); + m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); + m_metacache->addBase("root", QDir::currentPath()); + m_metacache->addBase("translations", QDir("translations").absolutePath()); + m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); + m_metacache->addBase("meta", QDir("meta").absolutePath()); + m_metacache->Load(); } void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password) { - // Set the application proxy settings. - if (proxyTypeStr == "SOCKS5") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); - } - else if (proxyTypeStr == "HTTP") - { - QNetworkProxy::setApplicationProxy( - QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); - } - else if (proxyTypeStr == "None") - { - // If we have no proxy set, set no proxy and return. - QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); - } - else - { - // If we have "Default" selected, set Qt to use the system proxy settings. - QNetworkProxyFactory::setUseSystemConfiguration(true); - } - - qDebug() << "Detecting proxy settings..."; - QNetworkProxy proxy = QNetworkProxy::applicationProxy(); - d->m_qnam.setProxy(proxy); - QString proxyDesc; - if (proxy.type() == QNetworkProxy::NoProxy) - { - qDebug() << "Using no proxy is an option!"; - return; - } - switch (proxy.type()) - { - case QNetworkProxy::DefaultProxy: - proxyDesc = "Default proxy: "; - break; - case QNetworkProxy::Socks5Proxy: - proxyDesc = "Socks5 proxy: "; - break; - case QNetworkProxy::HttpProxy: - proxyDesc = "HTTP proxy: "; - break; - case QNetworkProxy::HttpCachingProxy: - proxyDesc = "HTTP caching: "; - break; - case QNetworkProxy::FtpCachingProxy: - proxyDesc = "FTP caching: "; - break; - default: - proxyDesc = "DERP proxy: "; - break; - } - proxyDesc += QString("%3@%1:%2 pass %4") - .arg(proxy.hostName()) - .arg(proxy.port()) - .arg(proxy.user()) - .arg(proxy.password()); - qDebug() << proxyDesc; + // Set the application proxy settings. + if (proxyTypeStr == "SOCKS5") + { + QNetworkProxy::setApplicationProxy( + QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); + } + else if (proxyTypeStr == "HTTP") + { + QNetworkProxy::setApplicationProxy( + QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); + } + else if (proxyTypeStr == "None") + { + // If we have no proxy set, set no proxy and return. + QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); + } + else + { + // If we have "Default" selected, set Qt to use the system proxy settings. + QNetworkProxyFactory::setUseSystemConfiguration(true); + } + + qDebug() << "Detecting proxy settings..."; + QNetworkProxy proxy = QNetworkProxy::applicationProxy(); + d->m_qnam.setProxy(proxy); + QString proxyDesc; + if (proxy.type() == QNetworkProxy::NoProxy) + { + qDebug() << "Using no proxy is an option!"; + return; + } + switch (proxy.type()) + { + case QNetworkProxy::DefaultProxy: + proxyDesc = "Default proxy: "; + break; + case QNetworkProxy::Socks5Proxy: + proxyDesc = "Socks5 proxy: "; + break; + case QNetworkProxy::HttpProxy: + proxyDesc = "HTTP proxy: "; + break; + case QNetworkProxy::HttpCachingProxy: + proxyDesc = "HTTP caching: "; + break; + case QNetworkProxy::FtpCachingProxy: + proxyDesc = "FTP caching: "; + break; + default: + proxyDesc = "DERP proxy: "; + break; + } + proxyDesc += QString("%3@%1:%2 pass %4") + .arg(proxy.hostName()) + .arg(proxy.port()) + .arg(proxy.user()) + .arg(proxy.password()); + qDebug() << proxyDesc; } QString Env::getJarsPath() { - if(d->m_jarsPath.isEmpty()) - { - return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); - } - return d->m_jarsPath; + if(d->m_jarsPath.isEmpty()) + { + return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars"); + } + return d->m_jarsPath; } void Env::setJarsPath(const QString& path) { - d->m_jarsPath = path; + d->m_jarsPath = path; +} + +void Env::enableFeature(const QString& featureName, bool state) +{ + if(state) + { + d->m_features.insert(featureName); + } + else + { + d->m_features.remove(featureName); + } +} + +bool Env::isFeatureEnabled(const QString& featureName) const +{ + return d->m_features.contains(featureName); +} + +void Env::getEnabledFeatures(QSet& features) const +{ + features = d->m_features; +} + +void Env::setEnabledFeatures(const QSet& features) const +{ + d->m_features = features; } diff --git a/api/logic/Env.h b/api/logic/Env.h index 276d762d..8b9b827e 100644 --- a/api/logic/Env.h +++ b/api/logic/Env.h @@ -20,40 +20,46 @@ class Index; } #if defined(ENV) - #undef ENV + #undef ENV #endif #define ENV (Env::getInstance()) class MULTIMC_LOGIC_EXPORT Env { - friend class MultiMC; + friend class MultiMC; private: - struct Private; - Env(); - ~Env(); - static void dispose(); + struct Private; + Env(); + ~Env(); + static void dispose(); public: - static Env& getInstance(); + static Env& getInstance(); - QNetworkAccessManager &qnam() const; + QNetworkAccessManager &qnam() const; - shared_qobject_ptr metacache(); + shared_qobject_ptr metacache(); - std::shared_ptr icons(); + std::shared_ptr icons(); - /// init the cache. FIXME: possible future hook point - void initHttpMetaCache(); + /// init the cache. FIXME: possible future hook point + void initHttpMetaCache(); - /// Updates the application proxy settings from the settings object. - void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); + /// Updates the application proxy settings from the settings object. + void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); - void registerIconList(std::shared_ptr iconlist); + void registerIconList(std::shared_ptr iconlist); - shared_qobject_ptr metadataIndex(); + shared_qobject_ptr metadataIndex(); + + QString getJarsPath(); + void setJarsPath(const QString & path); + + bool isFeatureEnabled(const QString & featureName) const; + void enableFeature(const QString & featureName, bool state = true); + void getEnabledFeatures(QSet & features) const; + void setEnabledFeatures(const QSet & features) const; - QString getJarsPath(); - void setJarsPath(const QString & path); protected: - Private * d; + Private * d; }; diff --git a/api/logic/Exception.h b/api/logic/Exception.h index 30c7aa45..9400b3f8 100644 --- a/api/logic/Exception.h +++ b/api/logic/Exception.h @@ -11,24 +11,24 @@ class MULTIMC_LOGIC_EXPORT Exception : public std::exception { public: - Exception(const QString &message) : std::exception(), m_message(message) - { - qCritical() << "Exception:" << message; - } - Exception(const Exception &other) - : std::exception(), m_message(other.cause()) - { - } - virtual ~Exception() noexcept {} - const char *what() const noexcept - { - return m_message.toLatin1().constData(); - } - QString cause() const - { - return m_message; - } + Exception(const QString &message) : std::exception(), m_message(message) + { + qCritical() << "Exception:" << message; + } + Exception(const Exception &other) + : std::exception(), m_message(other.cause()) + { + } + virtual ~Exception() noexcept {} + const char *what() const noexcept + { + return m_message.toLatin1().constData(); + } + QString cause() const + { + return m_message; + } private: - QString m_message; + QString m_message; }; diff --git a/api/logic/ExponentialSeries.h b/api/logic/ExponentialSeries.h new file mode 100644 index 00000000..a9487f0a --- /dev/null +++ b/api/logic/ExponentialSeries.h @@ -0,0 +1,43 @@ + +#pragma once + +template +inline void clamp(T& current, T min, T max) +{ + if (current < min) + { + current = min; + } + else if(current > max) + { + current = max; + } +} + +// List of numbers from min to max. Next is exponent times bigger than previous. + +class ExponentialSeries +{ +public: + ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) + { + m_current = m_min = min; + m_max = max; + m_exponent = exponent; + } + void reset() + { + m_current = m_min; + } + unsigned operator()() + { + unsigned retval = m_current; + m_current *= m_exponent; + clamp(m_current, m_min, m_max); + return retval; + } + unsigned m_current; + unsigned m_min; + unsigned m_max; + unsigned m_exponent; +}; diff --git a/api/logic/FileSystem.cpp b/api/logic/FileSystem.cpp index 4b47f415..49af2927 100644 --- a/api/logic/FileSystem.cpp +++ b/api/logic/FileSystem.cpp @@ -12,262 +12,267 @@ #include #if defined Q_OS_WIN32 - #include - #include - #include - #include - #include - #include - #include - #include - #include + #include + #include + #include + #include + #include + #include + #include + #include + #include #else - #include + #include #endif namespace FS { void ensureExists(const QDir &dir) { - if (!QDir().mkpath(dir.absolutePath())) - { - throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + - dir.absolutePath() + ")"); - } + if (!QDir().mkpath(dir.absolutePath())) + { + throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + + dir.absolutePath() + ")"); + } } void write(const QString &filename, const QByteArray &data) { - ensureExists(QFileInfo(filename).dir()); - QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) - { - throw FileSystemException("Couldn't open " + filename + " for writing: " + - file.errorString()); - } - if (data.size() != file.write(data)) - { - throw FileSystemException("Error writing data to " + filename + ": " + - file.errorString()); - } - if (!file.commit()) - { - throw FileSystemException("Error while committing data to " + filename + ": " + - file.errorString()); - } + ensureExists(QFileInfo(filename).dir()); + QSaveFile file(filename); + if (!file.open(QSaveFile::WriteOnly)) + { + throw FileSystemException("Couldn't open " + filename + " for writing: " + + file.errorString()); + } + if (data.size() != file.write(data)) + { + throw FileSystemException("Error writing data to " + filename + ": " + + file.errorString()); + } + if (!file.commit()) + { + throw FileSystemException("Error while committing data to " + filename + ": " + + file.errorString()); + } } QByteArray read(const QString &filename) { - QFile file(filename); - if (!file.open(QFile::ReadOnly)) - { - throw FileSystemException("Unable to open " + filename + " for reading: " + - file.errorString()); - } - const qint64 size = file.size(); - QByteArray data(int(size), 0); - const qint64 ret = file.read(data.data(), size); - if (ret == -1 || ret != size) - { - throw FileSystemException("Error reading data from " + filename + ": " + - file.errorString()); - } - return data; + QFile file(filename); + if (!file.open(QFile::ReadOnly)) + { + throw FileSystemException("Unable to open " + filename + " for reading: " + + file.errorString()); + } + const qint64 size = file.size(); + QByteArray data(int(size), 0); + const qint64 ret = file.read(data.data(), size); + if (ret == -1 || ret != size) + { + throw FileSystemException("Error reading data from " + filename + ": " + + file.errorString()); + } + return data; } bool updateTimestamp(const QString& filename) { #ifdef Q_OS_WIN32 - std::wstring filename_utf_16 = filename.toStdWString(); - return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); + std::wstring filename_utf_16 = filename.toStdWString(); + return (_wutime64(filename_utf_16.c_str(), nullptr) == 0); #else - QByteArray filenameBA = QFile::encodeName(filename); - return (utime(filenameBA.data(), nullptr) == 0); + QByteArray filenameBA = QFile::encodeName(filename); + return (utime(filenameBA.data(), nullptr) == 0); #endif } bool ensureFilePathExists(QString filenamepath) { - QFileInfo a(filenamepath); - QDir dir; - QString ensuredPath = a.path(); - bool success = dir.mkpath(ensuredPath); - return success; + QFileInfo a(filenamepath); + QDir dir; + QString ensuredPath = a.path(); + bool success = dir.mkpath(ensuredPath); + return success; } bool ensureFolderPathExists(QString foldernamepath) { - QFileInfo a(foldernamepath); - QDir dir; - QString ensuredPath = a.filePath(); - bool success = dir.mkpath(ensuredPath); - return success; + QFileInfo a(foldernamepath); + QDir dir; + QString ensuredPath = a.filePath(); + bool success = dir.mkpath(ensuredPath); + return success; } bool copy::operator()(const QString &offset) { - //NOTE always deep copy on windows. the alternatives are too messy. - #if defined Q_OS_WIN32 - m_followSymlinks = true; - #endif - - auto src = PathCombine(m_src.absolutePath(), offset); - auto dst = PathCombine(m_dst.absolutePath(), offset); - - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; - - if(!m_followSymlinks && currentSrc.isSymLink()) - { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::link(currentSrc.symLinkTarget(), dst); - } - else if(currentSrc.isFile()) - { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } - else if(currentSrc.isDir()) - { - qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) - { - qWarning() << "Cannot create path!"; - return false; - } - QDir currentDir(src); - for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) - { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if(m_blacklist && m_blacklist->matches(inner_offset)) - { - continue; - } - if(!operator()(inner_offset)) - { - qWarning() << "Failed to copy" << inner_offset; - return false; - } - } - } - else - { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; - } - return true; + //NOTE always deep copy on windows. the alternatives are too messy. + #if defined Q_OS_WIN32 + m_followSymlinks = true; + #endif + + auto src = PathCombine(m_src.absolutePath(), offset); + auto dst = PathCombine(m_dst.absolutePath(), offset); + + QFileInfo currentSrc(src); + if (!currentSrc.exists()) + return false; + + if(!m_followSymlinks && currentSrc.isSymLink()) + { + qDebug() << "creating symlink" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::link(currentSrc.symLinkTarget(), dst); + } + else if(currentSrc.isFile()) + { + qDebug() << "copying file" << src << " - " << dst; + if (!ensureFilePathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + return QFile::copy(src, dst); + } + else if(currentSrc.isDir()) + { + qDebug() << "recursing" << offset; + if (!ensureFolderPathExists(dst)) + { + qWarning() << "Cannot create path!"; + return false; + } + QDir currentDir(src); + for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) + { + auto inner_offset = PathCombine(offset, f); + // ignore and skip stuff that matches the blacklist. + if(m_blacklist && m_blacklist->matches(inner_offset)) + { + continue; + } + if(!operator()(inner_offset)) + { + qWarning() << "Failed to copy" << inner_offset; + return false; + } + } + } + else + { + qCritical() << "Copy ERROR: Unknown filesystem object:" << src; + return false; + } + return true; } bool deletePath(QString path) { - bool OK = true; - QDir dir(path); - - if (!dir.exists()) - { - return OK; - } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | - QDir::AllDirs | QDir::Files, - QDir::DirsFirst); - - for(auto & info: allEntries) - { + bool OK = true; + QFileInfo finfo(path); + if(finfo.isFile()) { + return QFile::remove(path); + } + + QDir dir(path); + + if (!dir.exists()) + { + return OK; + } + auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | + QDir::AllDirs | QDir::Files, + QDir::DirsFirst); + + for(auto & info: allEntries) + { #if defined Q_OS_WIN32 - QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); - auto wString = nativePath.toStdWString(); - DWORD dwAttrs = GetFileAttributesW(wString.c_str()); - // Windows: check for junctions, reparse points and other nasty things of that sort - if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) - { - if (info.isFile()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - else if (info.isDir()) - { - OK &= dir.rmdir(info.absoluteFilePath()); - } - } + QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); + auto wString = nativePath.toStdWString(); + DWORD dwAttrs = GetFileAttributesW(wString.c_str()); + // Windows: check for junctions, reparse points and other nasty things of that sort + if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) + { + if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else if (info.isDir()) + { + OK &= dir.rmdir(info.absoluteFilePath()); + } + } #else - // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if(info.isSymLink()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } + // We do not trust Qt with reparse points, but do trust it with unix symlinks. + if(info.isSymLink()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } #endif - else if (info.isDir()) - { - OK &= deletePath(info.absoluteFilePath()); - } - else if (info.isFile()) - { - OK &= QFile::remove(info.absoluteFilePath()); - } - else - { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; + else if (info.isDir()) + { + OK &= deletePath(info.absoluteFilePath()); + } + else if (info.isFile()) + { + OK &= QFile::remove(info.absoluteFilePath()); + } + else + { + OK = false; + qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); + } + } + OK &= dir.rmdir(dir.absolutePath()); + return OK; } QString PathCombine(const QString & path1, const QString & path2) { - if(!path1.size()) - return path2; - if(!path2.size()) - return path1; + if(!path1.size()) + return path2; + if(!path2.size()) + return path1; return QDir::cleanPath(path1 + QDir::separator() + path2); } QString PathCombine(const QString & path1, const QString & path2, const QString & path3) { - return PathCombine(PathCombine(path1, path2), path3); + return PathCombine(PathCombine(path1, path2), path3); } QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4) { - return PathCombine(PathCombine(path1, path2, path3), path4); + return PathCombine(PathCombine(path1, path2, path3), path4); } QString AbsolutePath(QString path) { - return QFileInfo(path).absolutePath(); + return QFileInfo(path).absolutePath(); } QString ResolveExecutable(QString path) { - if (path.isEmpty()) - { - return QString(); - } - if(!path.contains('/')) - { - path = QStandardPaths::findExecutable(path); - } - QFileInfo pathInfo(path); - if(!pathInfo.exists() || !pathInfo.isExecutable()) - { - return QString(); - } - return pathInfo.absoluteFilePath(); + if (path.isEmpty()) + { + return QString(); + } + if(!path.contains('/')) + { + path = QStandardPaths::findExecutable(path); + } + QFileInfo pathInfo(path); + if(!pathInfo.exists() || !pathInfo.isExecutable()) + { + return QString(); + } + return pathInfo.absoluteFilePath(); } /** @@ -278,66 +283,66 @@ QString ResolveExecutable(QString path) */ QString NormalizePath(QString path) { - QDir a = QDir::currentPath(); - QString currentAbsolute = a.absolutePath(); - - QDir b(path); - QString newAbsolute = b.absolutePath(); - - if (newAbsolute.startsWith(currentAbsolute)) - { - return a.relativeFilePath(newAbsolute); - } - else - { - return newAbsolute; - } + QDir a = QDir::currentPath(); + QString currentAbsolute = a.absolutePath(); + + QDir b(path); + QString newAbsolute = b.absolutePath(); + + if (newAbsolute.startsWith(currentAbsolute)) + { + return a.relativeFilePath(newAbsolute); + } + else + { + return newAbsolute; + } } -QString badFilenameChars = "\"\\/?<>:*|!"; +QString badFilenameChars = "\"\\/?<>:*|!+\r\n"; QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) { - for (int i = 0; i < string.length(); i++) - { - if (badFilenameChars.contains(string[i])) - { - string[i] = replaceWith; - } - } - return string; + for (int i = 0; i < string.length(); i++) + { + if (badFilenameChars.contains(string[i])) + { + string[i] = replaceWith; + } + } + return string; } QString DirNameFromString(QString string, QString inDir) { - int num = 0; - QString baseName = RemoveInvalidFilenameChars(string, '-'); - QString dirName; - do - { - if(num == 0) - { - dirName = baseName; - } - else - { - dirName = baseName + QString::number(num);; - } - - // If it's over 9000 - if (num > 9000) - return ""; - num++; - } while (QFileInfo(PathCombine(inDir, dirName)).exists()); - return dirName; + int num = 0; + QString baseName = RemoveInvalidFilenameChars(string, '-'); + QString dirName; + do + { + if(num == 0) + { + dirName = baseName; + } + else + { + dirName = baseName + QString::number(num);; + } + + // If it's over 9000 + if (num > 9000) + return ""; + num++; + } while (QFileInfo(PathCombine(inDir, dirName)).exists()); + return dirName; } // Does the folder path contain any '!'? If yes, return true, otherwise false. // (This is a problem for Java) bool checkProblemticPathJava(QDir folder) { - QString pathfoldername = folder.absolutePath(); - return pathfoldername.contains("!", Qt::CaseInsensitive); + QString pathfoldername = folder.absolutePath(); + return pathfoldername.contains("!", Qt::CaseInsensitive); } // Win32 crap @@ -347,106 +352,106 @@ bool called_coinit = false; HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) { - HRESULT hres; - - if (!called_coinit) - { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) - { - qWarning("Failed to initialize COM. Error 0x%08X", hres); - return hres; - } - } - - IShellLink *link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, - (LPVOID *)&link); - - if (SUCCEEDED(hres)) - { - IPersistFile *persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); - if (SUCCEEDED(hres)) - { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; + HRESULT hres; + + if (!called_coinit) + { + hres = CoInitialize(NULL); + called_coinit = true; + + if (!SUCCEEDED(hres)) + { + qWarning("Failed to initialize COM. Error 0x%08lX", hres); + return hres; + } + } + + IShellLink *link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, + (LPVOID *)&link); + + if (SUCCEEDED(hres)) + { + IPersistFile *persistFile; + + link->SetPath(targetPath); + link->SetArguments(args); + + hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); + if (SUCCEEDED(hres)) + { + WCHAR wstr[MAX_PATH]; + + MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); + + hres = persistFile->Save(wstr, TRUE); + persistFile->Release(); + } + link->Release(); + } + return hres; } #endif QString getDesktopDir() { - return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); } // Cross-platform Shortcut creation bool createShortCut(QString location, QString dest, QStringList args, QString name, - QString icon) + QString icon) { #if defined Q_OS_LINUX - location = PathCombine(location, name + ".desktop"); + location = PathCombine(location, name + ".desktop"); - QFile f(location); - f.open(QIODevice::WriteOnly | QIODevice::Text); - QTextStream stream(&f); + QFile f(location); + f.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream stream(&f); - QString argstring; - if (!args.empty()) - argstring = " '" + args.join("' '") + "'"; + QString argstring; + if (!args.empty()) + argstring = " '" + args.join("' '") + "'"; - stream << "[Desktop Entry]" - << "\n"; - stream << "Type=Application" - << "\n"; - stream << "TryExec=" << dest.toLocal8Bit() << "\n"; - stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; - stream << "Name=" << name.toLocal8Bit() << "\n"; - stream << "Icon=" << icon.toLocal8Bit() << "\n"; + stream << "[Desktop Entry]" + << "\n"; + stream << "Type=Application" + << "\n"; + stream << "TryExec=" << dest.toLocal8Bit() << "\n"; + stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; + stream << "Name=" << name.toLocal8Bit() << "\n"; + stream << "Icon=" << icon.toLocal8Bit() << "\n"; - stream.flush(); - f.close(); + stream.flush(); + f.close(); - f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | - QFileDevice::ExeOther); + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | + QFileDevice::ExeOther); - return true; + return true; #elif defined Q_OS_WIN - // TODO: Fix - // QFile file(PathCombine(location, name + ".lnk")); - // WCHAR *file_w; - // WCHAR *dest_w; - // WCHAR *args_w; - // file.fileName().toWCharArray(file_w); - // dest.toWCharArray(dest_w); - - // QString argStr; - // for (int i = 0; i < args.count(); i++) - // { - // argStr.append(args[i]); - // argStr.append(" "); - // } - // argStr.toWCharArray(args_w); - - // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); - return false; + // TODO: Fix + // QFile file(PathCombine(location, name + ".lnk")); + // WCHAR *file_w; + // WCHAR *dest_w; + // WCHAR *args_w; + // file.fileName().toWCharArray(file_w); + // dest.toWCharArray(dest_w); + + // QString argStr; + // for (int i = 0; i < args.count(); i++) + // { + // argStr.append(args[i]); + // argStr.append(" "); + // } + // argStr.toWCharArray(args_w); + + // return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); + return false; #else - qWarning("Desktop Shortcuts not supported on your platform!"); - return false; + qWarning("Desktop Shortcuts not supported on your platform!"); + return false; #endif } } diff --git a/api/logic/FileSystem.h b/api/logic/FileSystem.h index de8774ff..55ec6a58 100644 --- a/api/logic/FileSystem.h +++ b/api/logic/FileSystem.h @@ -15,7 +15,7 @@ namespace FS class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception { public: - FileSystemException(const QString &message) : Exception(message) {} + FileSystemException(const QString &message) : Exception(message) {} }; /** @@ -48,34 +48,34 @@ MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath); class MULTIMC_LOGIC_EXPORT copy { public: - copy(const QString & src, const QString & dst) - { - m_src = src; - m_dst = dst; - } - copy & followSymlinks(const bool follow) - { - m_followSymlinks = follow; - return *this; - } - copy & blacklist(const IPathMatcher * filter) - { - m_blacklist = filter; - return *this; - } - bool operator()() - { - return operator()(QString()); - } + copy(const QString & src, const QString & dst) + { + m_src = src; + m_dst = dst; + } + copy & followSymlinks(const bool follow) + { + m_followSymlinks = follow; + return *this; + } + copy & blacklist(const IPathMatcher * filter) + { + m_blacklist = filter; + return *this; + } + bool operator()() + { + return operator()(QString()); + } private: - bool operator()(const QString &offset); + bool operator()(const QString &offset); private: - bool m_followSymlinks = true; - const IPathMatcher * m_blacklist = nullptr; - QDir m_src; - QDir m_dst; + bool m_followSymlinks = true; + const IPathMatcher * m_blacklist = nullptr; + QDir m_src; + QDir m_dst; }; /** diff --git a/api/logic/FileSystem_test.cpp b/api/logic/FileSystem_test.cpp index d5e1eedb..df653ea1 100644 --- a/api/logic/FileSystem_test.cpp +++ b/api/logic/FileSystem_test.cpp @@ -7,155 +7,155 @@ class FileSystemTest : public QObject { - Q_OBJECT + Q_OBJECT - const QString bothSlash = "/foo/"; - const QString trailingSlash = "foo/"; - const QString leadingSlash = "/foo"; + const QString bothSlash = "/foo/"; + const QString trailingSlash = "foo/"; + const QString leadingSlash = "/foo"; private slots: - void test_pathCombine() - { - QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); - - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); - QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); - QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); - } - - void test_PathCombine1_data() - { - QTest::addColumn("result"); - QTest::addColumn("path1"); - QTest::addColumn("path2"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; + void test_pathCombine() + { + QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); + QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); + QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); + + QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); + QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); + QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); + } + + void test_PathCombine1_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; - QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; - QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; + QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; + QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; + QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; #endif - } - - void test_PathCombine1() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - - QCOMPARE(FS::PathCombine(path1, path2), result); - } - - void test_PathCombine2_data() - { - QTest::addColumn("result"); - QTest::addColumn("path1"); - QTest::addColumn("path2"); - QTest::addColumn("path3"); - - QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; - QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; - QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; - QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; + } + + void test_PathCombine1() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + + QCOMPARE(FS::PathCombine(path1, path2), result); + } + + void test_PathCombine2_data() + { + QTest::addColumn("result"); + QTest::addColumn("path1"); + QTest::addColumn("path2"); + QTest::addColumn("path3"); + + QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; + QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; + QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; + QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; #if defined(Q_OS_WIN) - QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; - QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; - QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; - QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; + QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; + QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; + QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; #endif - } - - void test_PathCombine2() - { - QFETCH(QString, result); - QFETCH(QString, path1); - QFETCH(QString, path2); - QFETCH(QString, path3); - - QCOMPARE(FS::PathCombine(path1, path2, path3), result); - } - - void test_copy() - { - QString folder = QFINDTESTDATA("data/test_folder"); - auto f = [&folder]() - { - QTemporaryDir tempDir; - tempDir.setAutoRemove(true); - qDebug() << "From:" << folder << "To:" << tempDir.path(); - - QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); - qDebug() << tempDir.path(); - qDebug() << target_dir.path(); - FS::copy c(folder, target_dir.path()); - c(); - - for(auto entry: target_dir.entryList()) - { - qDebug() << entry; - } - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // first try variant without trailing / - QVERIFY(!folder.endsWith('/')); - f(); - - // then variant with trailing / - folder.append('/'); - QVERIFY(folder.endsWith('/')); - f(); - } - - void test_getDesktop() - { - QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); - } + } + + void test_PathCombine2() + { + QFETCH(QString, result); + QFETCH(QString, path1); + QFETCH(QString, path2); + QFETCH(QString, path3); + + QCOMPARE(FS::PathCombine(path1, path2, path3), result); + } + + void test_copy() + { + QString folder = QFINDTESTDATA("data/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(folder, target_dir.path()); + c(); + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + } + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + + void test_getDesktop() + { + QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + } // this is only valid on linux // FIXME: implement on windows, OSX, then test. #if defined(Q_OS_LINUX) - void test_createShortcut_data() - { - QTest::addColumn("location"); - QTest::addColumn("dest"); - QTest::addColumn("args"); - QTest::addColumn("name"); - QTest::addColumn("iconLocation"); - QTest::addColumn("result"); - - QTest::newRow("unix") << QDir::currentPath() - << "asdfDest" - << (QStringList() << "arg1" << "arg2") - << "asdf" - << QString() - #if defined(Q_OS_LINUX) - << MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix") - #elif defined(Q_OS_WIN) - << QByteArray() - #endif - ; - } - - void test_createShortcut() - { - QFETCH(QString, location); - QFETCH(QString, dest); - QFETCH(QStringList, args); - QFETCH(QString, name); - QFETCH(QString, iconLocation); - QFETCH(QByteArray, result); - - QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation)); - QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); - - //QDir().remove(location); - } + void test_createShortcut_data() + { + QTest::addColumn("location"); + QTest::addColumn("dest"); + QTest::addColumn("args"); + QTest::addColumn("name"); + QTest::addColumn("iconLocation"); + QTest::addColumn("result"); + + QTest::newRow("unix") << QDir::currentPath() + << "asdfDest" + << (QStringList() << "arg1" << "arg2") + << "asdf" + << QString() + #if defined(Q_OS_LINUX) + << MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix") + #elif defined(Q_OS_WIN) + << QByteArray() + #endif + ; + } + + void test_createShortcut() + { + QFETCH(QString, location); + QFETCH(QString, dest); + QFETCH(QStringList, args); + QFETCH(QString, name); + QFETCH(QString, iconLocation); + QFETCH(QByteArray, result); + + QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation)); + QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); + + //QDir().remove(location); + } #endif }; diff --git a/api/logic/Filter.cpp b/api/logic/Filter.cpp index 7f6667ae..c65ca0ce 100644 --- a/api/logic/Filter.cpp +++ b/api/logic/Filter.cpp @@ -6,26 +6,26 @@ ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){} ContainsFilter::~ContainsFilter(){} bool ContainsFilter::accepts(const QString& value) { - return value.contains(pattern); + return value.contains(pattern); } ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){} ExactFilter::~ExactFilter(){} bool ExactFilter::accepts(const QString& value) { - return value.contains(pattern); + return value == pattern; } RegexpFilter::RegexpFilter(const QString& regexp, bool invert) - :invert(invert) + :invert(invert) { - pattern.setPattern(regexp); - pattern.optimize(); + pattern.setPattern(regexp); + pattern.optimize(); } RegexpFilter::~RegexpFilter(){} bool RegexpFilter::accepts(const QString& value) { - auto match = pattern.match(value); - bool matched = match.hasMatch(); - return invert ? (!matched) : (matched); + auto match = pattern.match(value); + bool matched = match.hasMatch(); + return invert ? (!matched) : (matched); } diff --git a/api/logic/Filter.h b/api/logic/Filter.h index 8de7d8f9..1ba48e48 100644 --- a/api/logic/Filter.h +++ b/api/logic/Filter.h @@ -8,37 +8,37 @@ class MULTIMC_LOGIC_EXPORT Filter { public: - virtual ~Filter(); - virtual bool accepts(const QString & value) = 0; + virtual ~Filter(); + virtual bool accepts(const QString & value) = 0; }; class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter { public: - ContainsFilter(const QString &pattern); - virtual ~ContainsFilter(); - bool accepts(const QString & value) override; + ContainsFilter(const QString &pattern); + virtual ~ContainsFilter(); + bool accepts(const QString & value) override; private: - QString pattern; + QString pattern; }; class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter { public: - ExactFilter(const QString &pattern); - virtual ~ExactFilter(); - bool accepts(const QString & value) override; + ExactFilter(const QString &pattern); + virtual ~ExactFilter(); + bool accepts(const QString & value) override; private: - QString pattern; + QString pattern; }; class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter { public: - RegexpFilter(const QString ®exp, bool invert); - virtual ~RegexpFilter(); - bool accepts(const QString & value) override; + RegexpFilter(const QString ®exp, bool invert); + virtual ~RegexpFilter(); + bool accepts(const QString & value) override; private: - QRegularExpression pattern; - bool invert = false; + QRegularExpression pattern; + bool invert = false; }; diff --git a/api/logic/FolderInstanceProvider.cpp b/api/logic/FolderInstanceProvider.cpp deleted file mode 100644 index 69ba6c82..00000000 --- a/api/logic/FolderInstanceProvider.cpp +++ /dev/null @@ -1,462 +0,0 @@ -#include "FolderInstanceProvider.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/legacy/LegacyInstance.h" -#include "NullInstance.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -const static int GROUP_FILE_FORMAT_VERSION = 1; - -struct WatchLock -{ - WatchLock(QFileSystemWatcher * watcher, const QString& instDir) - : m_watcher(watcher), m_instDir(instDir) - { - m_watcher->removePath(m_instDir); - } - ~WatchLock() - { - m_watcher->addPath(m_instDir); - } - QFileSystemWatcher * m_watcher; - QString m_instDir; -}; - -FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir) - : BaseInstanceProvider(settings) -{ - // Create aand normalize path - if (!QDir::current().exists(instDir)) - { - QDir::current().mkpath(instDir); - } - // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! - m_instDir = QDir(instDir).canonicalPath(); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged); - m_watcher->addPath(m_instDir); -} - -QList< InstanceId > FolderInstanceProvider::discoverInstances() -{ - QList out; - QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); - while (iter.hasNext()) - { - QString subDir = iter.next(); - QFileInfo dirInfo(subDir); - if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) - continue; - // if it is a symlink, ignore it if it goes to the instance folder - if(dirInfo.isSymLink()) - { - QFileInfo targetInfo(dirInfo.symLinkTarget()); - QFileInfo instDirInfo(m_instDir); - if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) - { - qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; - continue; - } - } - auto id = dirInfo.fileName(); - out.append(id); - qDebug() << "Found instance ID" << id; - } - return out; -} - -InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id) -{ - if(!m_groupsLoaded) - { - loadGroupList(); - } - - auto instanceRoot = FS::PathCombine(m_instDir, id); - auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); - InstancePtr inst; - - instanceSettings->registerSetting("InstanceType", "Legacy"); - - QString inst_type = instanceSettings->get("InstanceType").toString(); - - if (inst_type == "OneSix" || inst_type == "Nostalgia") - { - inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else if (inst_type == "Legacy") - { - inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - else - { - inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); - } - inst->init(); - inst->setProvider(this); - auto iter = groupMap.find(id); - if (iter != groupMap.end()) - { - inst->setGroupInitial((*iter)); - } - connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged); - qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); - return inst; -} - -void FolderInstanceProvider::saveGroupList() -{ - WatchLock foo(m_watcher, m_instDir); - QString groupFileName = m_instDir + "/instgroups.json"; - QMap> reverseGroupMap; - for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++) - { - QString id = iter.key(); - QString group = iter.value(); - if (group.isEmpty()) - continue; - - if (!reverseGroupMap.count(group)) - { - QSet set; - set.insert(id); - reverseGroupMap[group] = set; - } - else - { - QSet &set = reverseGroupMap[group]; - set.insert(id); - } - } - QJsonObject toplevel; - toplevel.insert("formatVersion", QJsonValue(QString("1"))); - QJsonObject groupsArr; - for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.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); - try - { - FS::write(groupFileName, doc.toJson()); - } - catch(FS::FileSystemException & e) - { - qCritical() << "Failed to write instance group file :" << e.cause(); - } -} - -void FolderInstanceProvider::loadGroupList() -{ - QSet groupSet; - - QString groupFileName = m_instDir + "/instgroups.json"; - - // if there's no group file, fail - if (!QFileInfo(groupFileName).exists()) - return; - - QByteArray jsonData; - try - { - jsonData = FS::read(groupFileName); - } - catch (FS::FileSystemException & e) - { - qCritical() << "Failed to read instance group file :" << e.cause(); - return; - } - - QJsonParseError error; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); - - // if the json was bad, fail - if (error.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse instance group file: %1 at offset %2") - .arg(error.errorString(), QString::number(error.offset)) - .toUtf8(); - return; - } - - // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) - { - qWarning() << "Invalid group file. Root entry should be an object."; - return; - } - - QJsonObject rootObj = jsonDoc.object(); - - // Make sure the format version matches, otherwise fail. - if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) - return; - - // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) - { - qWarning() << "Invalid group list JSON: 'groups' should be an object."; - return; - } - - groupMap.clear(); - - // Iterate through all the groups. - QJsonObject groupMapping = rootObj.value("groups").toObject(); - for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) - { - QString groupName = iter.key(); - - // If not an object, complain and skip to the next one. - if (!iter.value().isObject()) - { - qWarning() << QString("Group '%1' in the group list should " - "be an object.") - .arg(groupName) - .toUtf8(); - continue; - } - - QJsonObject groupObj = iter.value().toObject(); - if (!groupObj.value("instances").isArray()) - { - qWarning() << QString("Group '%1' in the group list is invalid. " - "It should contain an array " - "called 'instances'.") - .arg(groupName) - .toUtf8(); - continue; - } - - // keep a list/set of groups for choosing - groupSet.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; - } - } - m_groupsLoaded = true; - emit groupsChanged(groupSet); -} - -void FolderInstanceProvider::groupChanged() -{ - // save the groups. save all of them. - auto instance = (BaseInstance *) QObject::sender(); - auto id = instance->id(); - groupMap[id] = instance->group(); - emit groupsChanged({instance->group()}); - saveGroupList(); -} - - -void FolderInstanceProvider::instanceDirContentsChanged(const QString& path) -{ - Q_UNUSED(path); - emit instancesChanged(); -} - -void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value) -{ - QString newInstDir = QDir(value.toString()).canonicalPath(); - if(newInstDir != m_instDir) - { - if(m_groupsLoaded) - { - saveGroupList(); - } - m_instDir = newInstDir; - m_groupsLoaded = false; - emit instancesChanged(); - } -} - -template -static void clamp(T& current, T min, T max) -{ - if (current < min) - { - current = min; - } - else if(current > max) - { - current = max; - } -} - -// List of numbers from min to max. Next is exponent times bigger than previous. -class ExponentialSeries -{ -public: - ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2) - { - m_current = m_min = min; - m_max = max; - m_exponent = exponent; - } - void reset() - { - m_current = m_min; - } - unsigned operator()() - { - unsigned retval = m_current; - m_current *= m_exponent; - clamp(m_current, m_min, m_max); - return retval; - } - unsigned m_current; - unsigned m_min; - unsigned m_max; - unsigned m_exponent; -}; - -/* - * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. - * Basically, it starts messing things up while MultiMC is extracting/creating instances - * and causes that horrible failure that is NTFS to lock files in place because they are open. - */ -class FolderInstanceStaging : public Task -{ -Q_OBJECT - const unsigned minBackoff = 1; - const unsigned maxBackoff = 16; -public: - FolderInstanceStaging ( - FolderInstanceProvider * parent, - Task * child, - const QString & stagingPath, - const QString& instanceName, - const QString& groupName ) - : backoff(minBackoff, maxBackoff) - { - m_parent = parent; - m_child.reset(child); - connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded); - connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed); - connect(child, &Task::status, this, &FolderInstanceStaging::setStatus); - connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress); - m_instanceName = instanceName; - m_groupName = groupName; - m_stagingPath = stagingPath; - m_backoffTimer.setSingleShot(true); - connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded); - } - -protected: - virtual void executeTask() override - { - m_child->start(); - } - QStringList warnings() const override - { - return m_child->warnings(); - } - -private slots: - void childSucceded() - { - unsigned sleepTime = backoff(); - if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) - { - emitSucceeded(); - return; - } - // we actually failed, retry? - if(sleepTime == maxBackoff) - { - emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); - return; - } - qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; - m_backoffTimer.start(sleepTime * 500); - } - void childFailed(const QString & reason) - { - m_parent->destroyStagingPath(m_stagingPath); - emitFailed(reason); - } - -private: - ExponentialSeries backoff; - QString m_stagingPath; - FolderInstanceProvider * m_parent; - unique_qobject_ptr m_child; - QString m_instanceName; - QString m_groupName; - QTimer m_backoffTimer; -}; - -#include "InstanceTask.h" -Task * FolderInstanceProvider::wrapInstanceTask(InstanceTask * task) -{ - auto stagingPath = getStagedInstancePath(); - task->setStagingPath(stagingPath); - task->setParentSettings(m_globalSettings); - return new FolderInstanceStaging(this, task, stagingPath, task->name(), task->group()); -} - -QString FolderInstanceProvider::getStagedInstancePath() -{ - QString key = QUuid::createUuid().toString(); - QString relPath = FS::PathCombine("_MMC_TEMP/" , key); - QDir rootPath(m_instDir); - auto path = FS::PathCombine(m_instDir, relPath); - if(!rootPath.mkpath(relPath)) - { - return QString(); - } - return path; -} - -bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) -{ - QDir dir; - QString instID = FS::DirNameFromString(instanceName, m_instDir); - { - WatchLock lock(m_watcher, m_instDir); - QString destination = FS::PathCombine(m_instDir, instID); - if(!dir.rename(path, destination)) - { - qWarning() << "Failed to move" << path << "to" << destination; - return false; - } - groupMap[instID] = groupName; - emit groupsChanged({groupName}); - emit instancesChanged(); - } - saveGroupList(); - return true; -} - -bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath) -{ - return FS::deletePath(keyPath); -} - -#include "FolderInstanceProvider.moc" diff --git a/api/logic/FolderInstanceProvider.h b/api/logic/FolderInstanceProvider.h deleted file mode 100644 index e13dcfe9..00000000 --- a/api/logic/FolderInstanceProvider.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "BaseInstanceProvider.h" -#include - -class QFileSystemWatcher; -class InstanceTask; - -class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider -{ - Q_OBJECT -public: - FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir); - -public: - /// used by InstanceList to @return a list of plausible IDs to probe for - QList discoverInstances() override; - - /// used by InstanceList to (re)load an instance with the given @id. - InstancePtr loadInstance(const InstanceId& id) override; - - - /* - // create instance in this provider - Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon); - - // copy instance to this provider - Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves); - - // import zipped instance into this provider - Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon); - - //create FtbInstance - Task * ftbCreationTask(FtbPackDownloader *downloader, const QString &instName, const QString &instGroup, const QString &instIcon); - - // migrate an instance to the current format - Task * legacyUpgradeTask(const InstancePtr& oldInstance); -*/ - - // Wrap an instance creation task in some more task machinery and make it ready to be used - Task * wrapInstanceTask(InstanceTask * task); - - /** - * Create a new empty staging area for instance creation and @return a path/key top commit it later. - * Used by instance manipulation tasks. - */ - QString getStagedInstancePath() override; - /** - * Commit the staging area given by @keyPath to the provider - used when creation succeeds. - * Used by instance manipulation tasks. - */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override; - /** - * Destroy a previously created staging area given by @keyPath - used when creation fails. - * Used by instance manipulation tasks. - */ - bool destroyStagingPath(const QString & keyPath) override; - -public slots: - void on_InstFolderChanged(const Setting &setting, QVariant value); - -private slots: - void instanceDirContentsChanged(const QString &path); - void groupChanged(); - -private: /* methods */ - void loadGroupList() override; - void saveGroupList() override; - -private: /* data */ - QString m_instDir; - QFileSystemWatcher * m_watcher; - QMap groupMap; - bool m_groupsLoaded = false; -}; diff --git a/api/logic/GZip.cpp b/api/logic/GZip.cpp index 38605df6..0368c32d 100644 --- a/api/logic/GZip.cpp +++ b/api/logic/GZip.cpp @@ -4,112 +4,112 @@ bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes) { - if (compressedBytes.size() == 0) - { - uncompressedBytes = compressedBytes; - return true; - } - - unsigned uncompLength = compressedBytes.size(); - uncompressedBytes.clear(); - uncompressedBytes.resize(uncompLength); - - z_stream strm; - memset(&strm, 0, sizeof(strm)); - strm.next_in = (Bytef *)compressedBytes.data(); - strm.avail_in = compressedBytes.size(); - - bool done = false; - - if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) - { - return false; - } - - int err = Z_OK; - - while (!done) - { - // If our output buffer is too small - if (strm.total_out >= uncompLength) - { - uncompressedBytes.resize(uncompLength * 2); - uncompLength *= 2; - } - - strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); - strm.avail_out = uncompLength - strm.total_out; - - // Inflate another chunk. - err = inflate(&strm, Z_SYNC_FLUSH); - if (err == Z_STREAM_END) - done = true; - else if (err != Z_OK) - { - break; - } - } - - if (inflateEnd(&strm) != Z_OK || !done) - { - return false; - } - - uncompressedBytes.resize(strm.total_out); - return true; + if (compressedBytes.size() == 0) + { + uncompressedBytes = compressedBytes; + return true; + } + + unsigned uncompLength = compressedBytes.size(); + uncompressedBytes.clear(); + uncompressedBytes.resize(uncompLength); + + z_stream strm; + memset(&strm, 0, sizeof(strm)); + strm.next_in = (Bytef *)compressedBytes.data(); + strm.avail_in = compressedBytes.size(); + + bool done = false; + + if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) + { + return false; + } + + int err = Z_OK; + + while (!done) + { + // If our output buffer is too small + if (strm.total_out >= uncompLength) + { + uncompressedBytes.resize(uncompLength * 2); + uncompLength *= 2; + } + + strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); + strm.avail_out = uncompLength - strm.total_out; + + // Inflate another chunk. + err = inflate(&strm, Z_SYNC_FLUSH); + if (err == Z_STREAM_END) + done = true; + else if (err != Z_OK) + { + break; + } + } + + if (inflateEnd(&strm) != Z_OK || !done) + { + return false; + } + + uncompressedBytes.resize(strm.total_out); + return true; } bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) { - if (uncompressedBytes.size() == 0) - { - compressedBytes = uncompressedBytes; - return true; - } - - unsigned compLength = std::min(uncompressedBytes.size(), 16); - compressedBytes.clear(); - compressedBytes.resize(compLength); - - z_stream zs; - memset(&zs, 0, sizeof(zs)); - - if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) - { - return false; - } - - zs.next_in = (Bytef*)uncompressedBytes.data(); - zs.avail_in = uncompressedBytes.size(); - - int ret; - compressedBytes.resize(uncompressedBytes.size()); - - unsigned offset = 0; - unsigned temp = 0; - do - { - auto remaining = compressedBytes.size() - offset; - if(remaining < 1) - { - compressedBytes.resize(compressedBytes.size() * 2); - } - zs.next_out = (Bytef *) (compressedBytes.data() + offset); - temp = zs.avail_out = compressedBytes.size() - offset; - ret = deflate(&zs, Z_FINISH); - offset += temp - zs.avail_out; - } while (ret == Z_OK); - - compressedBytes.resize(offset); - - if (deflateEnd(&zs) != Z_OK) - { - return false; - } - - if (ret != Z_STREAM_END) - { - return false; - } - return true; + if (uncompressedBytes.size() == 0) + { + compressedBytes = uncompressedBytes; + return true; + } + + unsigned compLength = std::min(uncompressedBytes.size(), 16); + compressedBytes.clear(); + compressedBytes.resize(compLength); + + z_stream zs; + memset(&zs, 0, sizeof(zs)); + + if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) + { + return false; + } + + zs.next_in = (Bytef*)uncompressedBytes.data(); + zs.avail_in = uncompressedBytes.size(); + + int ret; + compressedBytes.resize(uncompressedBytes.size()); + + unsigned offset = 0; + unsigned temp = 0; + do + { + auto remaining = compressedBytes.size() - offset; + if(remaining < 1) + { + compressedBytes.resize(compressedBytes.size() * 2); + } + zs.next_out = (Bytef *) (compressedBytes.data() + offset); + temp = zs.avail_out = compressedBytes.size() - offset; + ret = deflate(&zs, Z_FINISH); + offset += temp - zs.avail_out; + } while (ret == Z_OK); + + compressedBytes.resize(offset); + + if (deflateEnd(&zs) != Z_OK) + { + return false; + } + + if (ret != Z_STREAM_END) + { + return false; + } + return true; } \ No newline at end of file diff --git a/api/logic/GZip.h b/api/logic/GZip.h index 6993a222..c7eddbb3 100644 --- a/api/logic/GZip.h +++ b/api/logic/GZip.h @@ -6,7 +6,7 @@ class MULTIMC_LOGIC_EXPORT GZip { public: - static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); - static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); + static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); + static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); }; diff --git a/api/logic/GZip_test.cpp b/api/logic/GZip_test.cpp index f4c9214c..3f4d181c 100644 --- a/api/logic/GZip_test.cpp +++ b/api/logic/GZip_test.cpp @@ -6,50 +6,50 @@ void fib(int &prev, int &cur) { - auto ret = prev + cur; - prev = cur; - cur = ret; + auto ret = prev + cur; + prev = cur; + cur = ret; } class GZipTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Through() - { - // test up to 10 MB - static const int size = 10 * 1024 * 1024; - QByteArray random; - QByteArray compressed; - QByteArray decompressed; - std::default_random_engine eng((std::random_device())()); - std::uniform_int_distribution idis(0, std::numeric_limits::max()); - - // initialize random buffer - for(int i = 0; i < size; i++) - { - random.append((char)idis(eng)); - } - - // initialize fibonacci - int prev = 1; - int cur = 1; - - // test if fibonacci long random buffers pass through GZip - do - { - QByteArray copy = random; - copy.resize(cur); - compressed.clear(); - decompressed.clear(); - QVERIFY(GZip::zip(copy, compressed)); - QVERIFY(GZip::unzip(compressed, decompressed)); - QCOMPARE(decompressed, copy); - fib(prev, cur); - } while (cur < size); - } + void test_Through() + { + // test up to 10 MB + static const int size = 10 * 1024 * 1024; + QByteArray random; + QByteArray compressed; + QByteArray decompressed; + std::default_random_engine eng((std::random_device())()); + std::uniform_int_distribution idis(0, std::numeric_limits::max()); + + // initialize random buffer + for(int i = 0; i < size; i++) + { + random.append((char)idis(eng)); + } + + // initialize fibonacci + int prev = 1; + int cur = 1; + + // test if fibonacci long random buffers pass through GZip + do + { + QByteArray copy = random; + copy.resize(cur); + compressed.clear(); + decompressed.clear(); + QVERIFY(GZip::zip(copy, compressed)); + QVERIFY(GZip::unzip(compressed, decompressed)); + QCOMPARE(decompressed, copy); + fib(prev, cur); + } while (cur < size); + } }; QTEST_GUILESS_MAIN(GZipTest) diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp index 62c22362..a576a0fd 100644 --- a/api/logic/InstanceCopyTask.cpp +++ b/api/logic/InstanceCopyTask.cpp @@ -1,5 +1,4 @@ #include "InstanceCopyTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" #include "NullInstance.h" @@ -8,50 +7,50 @@ InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves) { - m_origInstance = origInstance; - - if(!copySaves) - { - // FIXME: get this from the original instance type... - auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); - matcherReal->caseSensitive(false); - m_matcher.reset(matcherReal); - } + m_origInstance = origInstance; + + if(!copySaves) + { + // FIXME: get this from the original instance type... + auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); + matcherReal->caseSensitive(false); + m_matcher.reset(matcherReal); + } } void InstanceCopyTask::executeTask() { - setStatus(tr("Copying instance %1").arg(m_origInstance->name())); + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(false).blacklist(m_matcher.get()); + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(false).blacklist(m_matcher.get()); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); - connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &InstanceCopyTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &InstanceCopyTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); } void InstanceCopyTask::copyFinished() { - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - // FIXME: shouldn't this be able to report errors? - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - - InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); - inst->setName(m_instName); - inst->setIconKey(m_instIcon); - emitSucceeded(); + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + // FIXME: shouldn't this be able to report errors? + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + + InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); + inst->setName(m_instName); + inst->setIconKey(m_instIcon); + emitSucceeded(); } void InstanceCopyTask::copyAborted() { - emitFailed(tr("Instance folder copy has been aborted.")); - return; + emitFailed(tr("Instance folder copy has been aborted.")); + return; } diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h index a8dc9783..8dd55b40 100644 --- a/api/logic/InstanceCopyTask.h +++ b/api/logic/InstanceCopyTask.h @@ -11,23 +11,21 @@ #include "BaseInstance.h" #include "InstanceTask.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves); + explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves); protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); private: /* data */ - InstancePtr m_origInstance; - QFuture m_copyFuture; - QFutureWatcher m_copyFutureWatcher; - std::unique_ptr m_matcher; + InstancePtr m_origInstance; + QFuture m_copyFuture; + QFutureWatcher m_copyFutureWatcher; + std::unique_ptr m_matcher; }; diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp index 6dc2496c..978d4687 100644 --- a/api/logic/InstanceCreationTask.cpp +++ b/api/logic/InstanceCreationTask.cpp @@ -1,5 +1,4 @@ #include "InstanceCreationTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" @@ -9,25 +8,24 @@ InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) { - m_version = version; + m_version = version; } void InstanceCreationTask::executeTask() { - setStatus(tr("Creating instance from version %1").arg(m_version->name())); - { - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->suspendSave(); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - auto components = inst.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - inst.setName(m_instName); - inst.setIconKey(m_instIcon); - inst.init(); - instanceSettings->resumeSave(); - } - emitSucceeded(); + setStatus(tr("Creating instance from version %1").arg(m_version->name())); + { + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->suspendSave(); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + inst.setName(m_instName); + inst.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + } + emitSucceeded(); } diff --git a/api/logic/InstanceCreationTask.h b/api/logic/InstanceCreationTask.h index e06eacbb..154a854f 100644 --- a/api/logic/InstanceCreationTask.h +++ b/api/logic/InstanceCreationTask.h @@ -10,14 +10,14 @@ class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceCreationTask(BaseVersionPtr version); + explicit InstanceCreationTask(BaseVersionPtr version); protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: /* data */ - BaseVersionPtr m_version; + BaseVersionPtr m_version; }; diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp index b5375cbc..1e93174c 100644 --- a/api/logic/InstanceImportTask.cpp +++ b/api/logic/InstanceImportTask.cpp @@ -1,12 +1,12 @@ #include "InstanceImportTask.h" #include "BaseInstance.h" -#include "BaseInstanceProvider.h" #include "FileSystem.h" #include "Env.h" #include "MMCZip.h" #include "NullInstance.h" #include "settings/INISettingsObject.h" #include "icons/IIconList.h" +#include "icons/IconUtils.h" #include // FIXME: this does not belong here, it's Minecraft/Flame specific @@ -18,394 +18,394 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) { - m_sourceUrl = sourceUrl; + m_sourceUrl = sourceUrl; } void InstanceImportTask::executeTask() { - InstancePtr newInstance; + InstancePtr newInstance; - if (m_sourceUrl.isLocalFile()) - { - m_archivePath = m_sourceUrl.toLocalFile(); - processZipPack(); - } - else - { - setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); - m_downloadRequired = true; + if (m_sourceUrl.isLocalFile()) + { + m_archivePath = m_sourceUrl.toLocalFile(); + processZipPack(); + } + else + { + setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); + m_downloadRequired = true; - const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); - auto entry = ENV.metacache()->resolveEntry("general", path); - entry->setStale(true); - m_filesNetJob.reset(new NetJob(tr("Modpack download"))); - m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - m_archivePath = entry->getFullPath(); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); - m_filesNetJob->start(); - } + const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); + auto entry = ENV.metacache()->resolveEntry("general", path); + entry->setStale(true); + m_filesNetJob.reset(new NetJob(tr("Modpack download"))); + m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); + m_archivePath = entry->getFullPath(); + auto job = m_filesNetJob.get(); + connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); + connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); + connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); + m_filesNetJob->start(); + } } void InstanceImportTask::downloadSucceeded() { - processZipPack(); - m_filesNetJob.reset(); + processZipPack(); + m_filesNetJob.reset(); } void InstanceImportTask::downloadFailed(QString reason) { - emitFailed(reason); - m_filesNetJob.reset(); + emitFailed(reason); + m_filesNetJob.reset(); } void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) { - setProgress(current / 2, total); + setProgress(current / 2, total); } void InstanceImportTask::processZipPack() { - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - qDebug() << "Attempting to create instance from" << m_archivePath; + setStatus(tr("Extracting modpack")); + QDir extractDir(m_stagingPath); + qDebug() << "Attempting to create instance from" << m_archivePath; - // open the zip and find relevant files in it - m_packZip.reset(new QuaZip(m_archivePath)); - if (!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Unable to open supplied modpack zip file.")); - return; - } + // open the zip and find relevant files in it + m_packZip.reset(new QuaZip(m_archivePath)); + if (!m_packZip->open(QuaZip::mdUnzip)) + { + emitFailed(tr("Unable to open supplied modpack zip file.")); + return; + } - QStringList blacklist = {"instance.cfg", "manifest.json"}; - QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); - QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); - QString root; - if(!mmcFound.isNull()) - { - // process as MultiMC instance/pack - qDebug() << "MultiMC:" << mmcFound; - root = mmcFound; - m_modpackType = ModpackType::MultiMC; - } - else if(!flameFound.isNull()) - { - // process as Flame pack - qDebug() << "Flame:" << flameFound; - root = flameFound; - m_modpackType = ModpackType::Flame; - } + QStringList blacklist = {"instance.cfg", "manifest.json"}; + QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); + QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); + QString root; + if(!mmcFound.isNull()) + { + // process as MultiMC instance/pack + qDebug() << "MultiMC:" << mmcFound; + root = mmcFound; + m_modpackType = ModpackType::MultiMC; + } + else if(!flameFound.isNull()) + { + // process as Flame pack + qDebug() << "Flame:" << flameFound; + root = flameFound; + m_modpackType = ModpackType::Flame; + } - if(m_modpackType == ModpackType::Unknown) - { - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } + if(m_modpackType == ModpackType::Unknown) + { + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; + } - // make sure we extract just the pack - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &InstanceImportTask::extractAborted); - m_extractFutureWatcher.setFuture(m_extractFuture); + // make sure we extract just the pack + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath()); + connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &InstanceImportTask::extractFinished); + connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &InstanceImportTask::extractAborted); + m_extractFutureWatcher.setFuture(m_extractFuture); } void InstanceImportTask::extractFinished() { - m_packZip.reset(); - if (m_extractFuture.result().isEmpty()) - { - emitFailed(tr("Failed to extract modpack")); - return; - } - QDir extractDir(m_stagingPath); + m_packZip.reset(); + if (m_extractFuture.result().isEmpty()) + { + emitFailed(tr("Failed to extract modpack")); + return; + } + QDir extractDir(m_stagingPath); - qDebug() << "Fixing permissions for extracted pack files..."; - QDirIterator it(extractDir, QDirIterator::Subdirectories); - while (it.hasNext()) - { - auto filepath = it.next(); - QFileInfo file(filepath); - auto permissions = QFile::permissions(filepath); - auto origPermissions = permissions; - if(file.isDir()) - { - // Folder +rwx for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; - } - else - { - // File +rw for current user - permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; - } - if(origPermissions != permissions) - { - if(!QFile::setPermissions(filepath, permissions)) - { - logWarning(tr("Could not fix permissions for %1").arg(filepath)); - } - else - { - qDebug() << "Fixed" << filepath; - } - } - } + qDebug() << "Fixing permissions for extracted pack files..."; + QDirIterator it(extractDir, QDirIterator::Subdirectories); + while (it.hasNext()) + { + auto filepath = it.next(); + QFileInfo file(filepath); + auto permissions = QFile::permissions(filepath); + auto origPermissions = permissions; + if(file.isDir()) + { + // Folder +rwx for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser; + } + else + { + // File +rw for current user + permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser; + } + if(origPermissions != permissions) + { + if(!QFile::setPermissions(filepath, permissions)) + { + logWarning(tr("Could not fix permissions for %1").arg(filepath)); + } + else + { + qDebug() << "Fixed" << filepath; + } + } + } - switch(m_modpackType) - { - case ModpackType::Flame: - processFlame(); - return; - case ModpackType::MultiMC: - processMultiMC(); - return; - case ModpackType::Unknown: - emitFailed(tr("Archive does not contain a recognized modpack type.")); - return; - } + switch(m_modpackType) + { + case ModpackType::Flame: + processFlame(); + return; + case ModpackType::MultiMC: + processMultiMC(); + return; + case ModpackType::Unknown: + emitFailed(tr("Archive does not contain a recognized modpack type.")); + return; + } } void InstanceImportTask::extractAborted() { - emitFailed(tr("Instance import has been aborted.")); - return; + emitFailed(tr("Instance import has been aborted.")); + return; } void InstanceImportTask::processFlame() { - const static QMap forgemap = { - {"1.2.5", "3.4.9.171"}, - {"1.4.2", "6.0.1.355"}, - {"1.4.7", "6.6.2.534"}, - {"1.5.2", "7.8.1.737"} - }; - Flame::Manifest pack; - try - { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); - } - catch (JSONValidationError & e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - if(!pack.overrides.isEmpty()) - { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); - if (QFile::exists(overridePath)) - { - QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) - { - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; - } - } - else - { - logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); - } - } + const static QMap forgemap = { + {"1.2.5", "3.4.9.171"}, + {"1.4.2", "6.0.1.355"}, + {"1.4.7", "6.6.2.534"}, + {"1.5.2", "7.8.1.737"} + }; + Flame::Manifest pack; + try + { + QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); + Flame::loadManifest(pack, configPath); + QFile::remove(configPath); + } + catch (const JSONValidationError &e) + { + emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); + return; + } + if(!pack.overrides.isEmpty()) + { + QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + if (QFile::exists(overridePath)) + { + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) + { + emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); + return; + } + } + else + { + logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + } + } - QString forgeVersion; - for(auto &loader: pack.minecraft.modLoaders) - { - auto id = loader.id; - if(id.startsWith("forge-")) - { - id.remove("forge-"); - forgeVersion = id; - continue; - } - logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); - } + QString forgeVersion; + for(auto &loader: pack.minecraft.modLoaders) + { + auto id = loader.id; + if(id.startsWith("forge-")) + { + id.remove("forge-"); + forgeVersion = id; + continue; + } + logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); + } - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; - // Hack to correct some 'special sauce'... - if(mcVersion.endsWith('.')) - { - mcVersion.remove(QRegExp("[.]+$")); - logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); - } - auto components = instance.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", mcVersion, true); - if(!forgeVersion.isEmpty()) - { - // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. - if(forgeVersion == "recommended") - { - if(forgemap.contains(mcVersion)) - { - forgeVersion = forgemap[mcVersion]; - } - else - { - logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); - } - } - components->setComponentVersion("net.minecraftforge", forgeVersion); - } - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - if(pack.name.contains("Direwolf20")) - { - instance.setIconKey("steve"); - } - else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) - { - instance.setIconKey("ftb_logo"); - } - else - { - // default to something other than the MultiMC default to distinguish these - instance.setIconKey("flame"); - } - } - instance.init(); - QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); - QFileInfo jarmodsInfo(jarmodsPath); - if(jarmodsInfo.isDir()) - { - // install all the jar mods - qDebug() << "Found jarmods:"; - QDir jarmodsDir(jarmodsPath); - QStringList jarMods; - for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << info.fileName(); - jarMods.push_back(info.absoluteFilePath()); - } - auto profile = instance.getComponentList(); - profile->installJarMods(jarMods); - // nuke the original files - FS::deletePath(jarmodsPath); - } - instance.setName(m_instName); - m_modIdResolver.reset(new Flame::FileResolvingTask(pack)); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() - { - auto results = m_modIdResolver->getResults(); - m_filesNetJob.reset(new NetJob(tr("Mod download"))); - for(auto result: results.files) - { - QString filename = result.fileName; - if(!result.required) - { - filename += ".disabled"; - } + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto mcVersion = pack.minecraft.version; + // Hack to correct some 'special sauce'... + if(mcVersion.endsWith('.')) + { + mcVersion.remove(QRegExp("[.]+$")); + logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); + } + auto components = instance.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", mcVersion, true); + if(!forgeVersion.isEmpty()) + { + // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. + if(forgeVersion == "recommended") + { + if(forgemap.contains(mcVersion)) + { + forgeVersion = forgemap[mcVersion]; + } + else + { + logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion)); + } + } + components->setComponentVersion("net.minecraftforge", forgeVersion); + } + if (m_instIcon != "default") + { + instance.setIconKey(m_instIcon); + } + else + { + if(pack.name.contains("Direwolf20")) + { + instance.setIconKey("steve"); + } + else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) + { + instance.setIconKey("ftb_logo"); + } + else + { + // default to something other than the MultiMC default to distinguish these + instance.setIconKey("flame"); + } + } + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); + QFileInfo jarmodsInfo(jarmodsPath); + if(jarmodsInfo.isDir()) + { + // install all the jar mods + qDebug() << "Found jarmods:"; + QDir jarmodsDir(jarmodsPath); + QStringList jarMods; + for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) + { + qDebug() << info.fileName(); + jarMods.push_back(info.absoluteFilePath()); + } + auto profile = instance.getComponentList(); + profile->installJarMods(jarMods); + // nuke the original files + FS::deletePath(jarmodsPath); + } + instance.setName(m_instName); + m_modIdResolver.reset(new Flame::FileResolvingTask(pack)); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() + { + auto results = m_modIdResolver->getResults(); + m_filesNetJob.reset(new NetJob(tr("Mod download"))); + for(auto result: results.files) + { + QString filename = result.fileName; + if(!result.required) + { + filename += ".disabled"; + } - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath , relpath); + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath , relpath); - switch(result.type) - { - case Flame::File::Type::Folder: - { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: - { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) - { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } - ); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) - { - m_modIdResolver.reset(); - emitFailed(tr("Unable to resolve mod IDs:\n") + reason); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) - { - setStatus(status); - }); - m_modIdResolver->start(); + switch(result.type) + { + case Flame::File::Type::Folder: + { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: + { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_filesNetJob->addNetAction(dl); + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + m_modIdResolver.reset(); + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() + { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) + { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); + } + ); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) + { + m_modIdResolver.reset(); + emitFailed(tr("Unable to resolve mod IDs:\n") + reason); + }); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) + { + setStatus(status); + }); + m_modIdResolver->start(); } void InstanceImportTask::processMultiMC() { - // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); + // FIXME: copy from FolderInstanceProvider!!! FIX IT!!! + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); - NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - // reset time played on import... because packs. - instance.resetTimePlayed(); + // reset time played on import... because packs. + instance.resetTimePlayed(); - // set a new nice name - instance.setName(m_instName); + // set a new nice name + instance.setName(m_instName); - // if the icon was specified by user, use that. otherwise pull icon from the pack - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - m_instIcon = instance.iconKey(); - auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png"); - if (QFile::exists(importIconPath)) - { - // import icon - auto iconList = ENV.icons(); - if (iconList->iconFileExists(m_instIcon)) - { - iconList->deleteIcon(m_instIcon); - } - iconList->installIcons({importIconPath}); - } - } - emitSucceeded(); + // if the icon was specified by user, use that. otherwise pull icon from the pack + if (m_instIcon != "default") + { + instance.setIconKey(m_instIcon); + } + else + { + m_instIcon = instance.iconKey(); + + auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); + if (!importIconPath.isNull() && QFile::exists(importIconPath)) + { + // import icon + auto iconList = ENV.icons(); + if (iconList->iconFileExists(m_instIcon)) + { + iconList->deleteIcon(m_instIcon); + } + iconList->installIcons({importIconPath}); + } + } + emitSucceeded(); } diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h index 06778dfe..d326391b 100644 --- a/api/logic/InstanceImportTask.h +++ b/api/logic/InstanceImportTask.h @@ -10,46 +10,45 @@ #include "QObjectPtr.h" class QuaZip; -class BaseInstanceProvider; namespace Flame { - class FileResolvingTask; + class FileResolvingTask; } class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit InstanceImportTask(const QUrl sourceUrl); + explicit InstanceImportTask(const QUrl sourceUrl); protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: - void processZipPack(); - void processMultiMC(); - void processFlame(); + void processZipPack(); + void processMultiMC(); + void processFlame(); private slots: - void downloadSucceeded(); - void downloadFailed(QString reason); - void downloadProgressChanged(qint64 current, qint64 total); - void extractFinished(); - void extractAborted(); + void downloadSucceeded(); + void downloadFailed(QString reason); + void downloadProgressChanged(qint64 current, qint64 total); + void extractFinished(); + void extractAborted(); private: /* data */ - NetJobPtr m_filesNetJob; - shared_qobject_ptr m_modIdResolver; - QUrl m_sourceUrl; - QString m_archivePath; - bool m_downloadRequired = false; - std::unique_ptr m_packZip; - QFuture m_extractFuture; - QFutureWatcher m_extractFutureWatcher; - enum class ModpackType{ - Unknown, - MultiMC, - Flame - } m_modpackType = ModpackType::Unknown; + NetJobPtr m_filesNetJob; + shared_qobject_ptr m_modIdResolver; + QUrl m_sourceUrl; + QString m_archivePath; + bool m_downloadRequired = false; + std::unique_ptr m_packZip; + QFuture m_extractFuture; + QFutureWatcher m_extractFutureWatcher; + enum class ModpackType{ + Unknown, + MultiMC, + Flame + } m_modpackType = ModpackType::Unknown; }; diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp index 75b523e4..58e2ee0c 100644 --- a/api/logic/InstanceList.cpp +++ b/api/logic/InstanceList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,49 @@ */ #include +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include #include "InstanceList.h" #include "BaseInstance.h" +#include "InstanceTask.h" +#include "settings/INISettingsObject.h" +#include "minecraft/legacy/LegacyInstance.h" +#include "NullInstance.h" +#include "minecraft/MinecraftInstance.h" +#include "FileSystem.h" +#include "ExponentialSeries.h" +#include "WatchLock.h" -#include "FolderInstanceProvider.h" +const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(QObject *parent) - : QAbstractListModel(parent) +InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) + : QAbstractListModel(parent), m_globalSettings(settings) { - resumeWatch(); + resumeWatch(); + // Create aand normalize path + if (!QDir::current().exists(instDir)) + { + QDir::current().mkpath(instDir); + } + + connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated); + + // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block! + m_instDir = QDir(instDir).canonicalPath(); + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &InstanceList::instanceDirContentsChanged); + m_watcher->addPath(m_instDir); } InstanceList::~InstanceList() @@ -38,310 +65,773 @@ InstanceList::~InstanceList() int InstanceList::rowCount(const QModelIndex &parent) const { - Q_UNUSED(parent); - return m_instances.count(); + 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()); + 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: + 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: - { - return pdata->iconKey(); - } - // HACK: see GroupView.h in gui! - case GroupRole: - { - return pdata->group(); - } - default: - break; - } - return QVariant(); + case Qt::EditRole: + case Qt::DisplayRole: + { + return pdata->name(); + } + case Qt::AccessibleTextRole: + { + return tr("%1 Instance").arg(pdata->name()); + } + case Qt::ToolTipRole: + { + return pdata->instanceRoot(); + } + case Qt::DecorationRole: + { + return pdata->iconKey(); + } + // HACK: see GroupView.h in gui! + case GroupRole: + { + return getInstanceGroup(pdata->id()); + } + default: + break; + } + return QVariant(); +} + +bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) + { + return false; + } + if(role != Qt::EditRole) + { + return false; + } + BaseInstance *pdata = static_cast(index.internalPointer()); + auto newName = value.toString(); + if(pdata->name() == newName) + { + return true; + } + pdata->setName(newName); + return true; } Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const { - Qt::ItemFlags f; - if (index.isValid()) - { - f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable); - } - return f; + Qt::ItemFlags f; + if (index.isValid()) + { + f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + } + return f; +} + +GroupId InstanceList::getInstanceGroup(const InstanceId& id) const +{ + auto inst = getInstanceById(id); + if(!inst) + { + return GroupId(); + } + auto iter = m_groupMap.find(inst->id()); + if(iter != m_groupMap.end()) + { + return *iter; + } + return GroupId(); +} + +void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) +{ + auto inst = getInstanceById(id); + if(!inst) + { + qDebug() << "Attempt to set a null instance's group"; + return; + } + + bool changed = false; + auto iter = m_groupMap.find(inst->id()); + if(iter != m_groupMap.end()) + { + if(*iter != name) + { + *iter = name; + changed = true; + } + } + else + { + changed = true; + m_groupMap[id] = name; + } + + if(changed) + { + m_groups.insert(name); + auto idx = getInstIndex(inst.get()); + emit dataChanged(index(idx), index(idx), {GroupRole}); + saveGroupList(); + } } QStringList InstanceList::getGroups() { - return m_groups.toList(); + return m_groups.toList(); } void InstanceList::deleteGroup(const QString& name) { - for(auto & instance: m_instances) - { - auto instGroupName = instance->group(); - if(instGroupName == name) - { - instance->setGroupPost(QString()); - } - } + bool removed = false; + qDebug() << "Delete group" << name; + for(auto & instance: m_instances) + { + const auto & instID = instance->id(); + auto instGroupName = getInstanceGroup(instID); + if(instGroupName == name) + { + m_groupMap.remove(instID); + qDebug() << "Remove" << instID << "from group" << name; + removed = true; + auto idx = getInstIndex(instance.get()); + if(idx > 0) + { + emit dataChanged(index(idx), index(idx), {GroupRole}); + } + } + } + if(removed) + { + saveGroupList(); + } +} + +void InstanceList::deleteInstance(const InstanceId& id) +{ + auto inst = getInstanceById(id); + if(!inst) + { + qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; + return; + } + + if(m_groupMap.remove(id)) + { + saveGroupList(); + } + + qDebug() << "Will delete instance" << id; + if(!FS::deletePath(inst->instanceRoot())) + { + qWarning() << "Deletion of instance" << id << "has not been completely successful ..."; + return; + } + + qDebug() << "Instance" << id << "has been deleted by MultiMC."; } static QMap getIdMapping(const QList &list) { - QMap out; - int i = 0; - for(auto & item: list) - { - auto id = item->id(); - if(out.contains(id)) - { - qWarning() << "Duplicate ID" << id << "in instance list"; - } - out[id] = std::make_pair(item, i); - i++; - } - return out; -} - -InstanceList::InstListError InstanceList::loadList(bool complete) -{ - auto existingIds = getIdMapping(m_instances); - - QList newList; - - auto processIds = [&](BaseInstanceProvider * provider, QList ids) - { - for(auto & id: ids) - { - if(existingIds.contains(id)) - { - auto instPair = existingIds[id]; - /* - auto & instPtr = instPair.first; - auto & instIdx = instPair.second; - */ - existingIds.remove(id); - qDebug() << "Should keep and soft-reload" << id; - } - else - { - InstancePtr instPtr = provider->loadInstance(id); - if(instPtr) - { - newList.append(instPtr); - } - } - } - }; - if(complete) - { - for(auto & item: m_providers) - { - processIds(item.get(), item->discoverInstances()); - } - } - else - { - for (auto & item: m_updatedProviders) - { - processIds(item, item->discoverInstances()); - } - } - - // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. - if(!existingIds.isEmpty()) - { - // get the list of removed instances and sort it by their original index, from last to first - auto deadList = existingIds.values(); - auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool - { - return a.second > b.second; - }; - std::sort(deadList.begin(), deadList.end(), orderSortPredicate); - // remove the contiguous ranges of rows - int front_bookmark = -1; - int back_bookmark = -1; - int currentItem = -1; - auto removeNow = [&]() - { - beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); - m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); - endRemoveRows(); - front_bookmark = -1; - back_bookmark = currentItem; - }; - for(auto & removedItem: deadList) - { - auto instPtr = removedItem.first; - if(!complete && !m_updatedProviders.contains(instPtr->provider())) - { - continue; - } - instPtr->invalidate(); - currentItem = removedItem.second; - if(back_bookmark == -1) - { - // no bookmark yet - back_bookmark = currentItem; - } - else if(currentItem == front_bookmark - 1) - { - // part of contiguous sequence, continue - } - else - { - // seam between previous and current item - removeNow(); - } - front_bookmark = currentItem; - } - if(back_bookmark != -1) - { - removeNow(); - } - } - if(newList.size()) - { - add(newList); - } - m_updatedProviders.clear(); - return NoError; + QMap out; + int i = 0; + for(auto & item: list) + { + auto id = item->id(); + if(out.contains(id)) + { + qWarning() << "Duplicate ID" << id << "in instance list"; + } + out[id] = std::make_pair(item, i); + i++; + } + return out; +} + +QList< InstanceId > InstanceList::discoverInstances() +{ + qDebug() << "Discovering instances in" << m_instDir; + QList out; + QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); + while (iter.hasNext()) + { + QString subDir = iter.next(); + QFileInfo dirInfo(subDir); + if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) + continue; + // if it is a symlink, ignore it if it goes to the instance folder + if(dirInfo.isSymLink()) + { + QFileInfo targetInfo(dirInfo.symLinkTarget()); + QFileInfo instDirInfo(m_instDir); + if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) + { + qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; + continue; + } + } + auto id = dirInfo.fileName(); + out.append(id); + qDebug() << "Found instance ID" << id; + } + instanceSet = out.toSet(); + m_instancesProbed = true; + return out; +} + +InstanceList::InstListError InstanceList::loadList() +{ + auto existingIds = getIdMapping(m_instances); + + QList newList; + + for(auto & id: discoverInstances()) + { + if(existingIds.contains(id)) + { + auto instPair = existingIds[id]; + existingIds.remove(id); + qDebug() << "Should keep and soft-reload" << id; + } + else + { + InstancePtr instPtr = loadInstance(id); + if(instPtr) + { + newList.append(instPtr); + } + } + } + + // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. + if(!existingIds.isEmpty()) + { + // get the list of removed instances and sort it by their original index, from last to first + auto deadList = existingIds.values(); + auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool + { + return a.second > b.second; + }; + std::sort(deadList.begin(), deadList.end(), orderSortPredicate); + // remove the contiguous ranges of rows + int front_bookmark = -1; + int back_bookmark = -1; + int currentItem = -1; + auto removeNow = [&]() + { + beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); + m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); + endRemoveRows(); + front_bookmark = -1; + back_bookmark = currentItem; + }; + for(auto & removedItem: deadList) + { + auto instPtr = removedItem.first; + instPtr->invalidate(); + currentItem = removedItem.second; + if(back_bookmark == -1) + { + // no bookmark yet + back_bookmark = currentItem; + } + else if(currentItem == front_bookmark - 1) + { + // part of contiguous sequence, continue + } + else + { + // seam between previous and current item + removeNow(); + } + front_bookmark = currentItem; + } + if(back_bookmark != -1) + { + removeNow(); + } + } + if(newList.size()) + { + add(newList); + } + m_dirty = false; + return NoError; } void InstanceList::saveNow() { - for(auto & item: m_instances) - { - item->saveNow(); - } + for(auto & item: m_instances) + { + item->saveNow(); + } } void InstanceList::add(const QList &t) { - beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); - m_instances.append(t); - for(auto & ptr : t) - { - connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); - } - endInsertRows(); + beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); + m_instances.append(t); + for(auto & ptr : t) + { + connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); + } + endInsertRows(); } void InstanceList::resumeWatch() { - if(m_watchLevel > 0) - { - qWarning() << "Bad suspend level resume in instance list"; - return; - } - m_watchLevel++; - if(m_watchLevel > 0 && !m_updatedProviders.isEmpty()) - { - loadList(); - } + if(m_watchLevel > 0) + { + qWarning() << "Bad suspend level resume in instance list"; + return; + } + m_watchLevel++; + if(m_watchLevel > 0 && m_dirty) + { + loadList(); + } } void InstanceList::suspendWatch() { - m_watchLevel --; + m_watchLevel --; } void InstanceList::providerUpdated() { - auto provider = dynamic_cast(QObject::sender()); - if(!provider) - { - qWarning() << "InstanceList::providerUpdated triggered by a non-provider"; - return; - } - m_updatedProviders.insert(provider); - if(m_watchLevel == 1) - { - loadList(); - } + m_dirty = true; + if(m_watchLevel == 1) + { + loadList(); + } } -void InstanceList::groupsPublished(QSet newGroups) +InstancePtr InstanceList::getInstanceById(QString instId) const { - m_groups.unite(newGroups); + if(instId.isEmpty()) + return InstancePtr(); + for(auto & inst: m_instances) + { + if (inst->id() == instId) + { + return inst; + } + } + return InstancePtr(); } -void InstanceList::addInstanceProvider(BaseInstanceProvider* provider) +QModelIndex InstanceList::getInstanceIndexById(const QString &id) const { - connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated); - connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished); - m_providers.append(provider); + return index(getInstIndex(getInstanceById(id).get())); } -InstancePtr InstanceList::getInstanceById(QString instId) const +int InstanceList::getInstIndex(BaseInstance *inst) const { - if(instId.isEmpty()) - return InstancePtr(); - for(auto & inst: m_instances) - { - if (inst->id() == instId) - { - return inst; - } - } - return InstancePtr(); + int count = m_instances.count(); + for (int i = 0; i < count; i++) + { + if (inst == m_instances[i].get()) + { + return i; + } + } + return -1; } -QModelIndex InstanceList::getInstanceIndexById(const QString &id) const +void InstanceList::propertiesChanged(BaseInstance *inst) { - return index(getInstIndex(getInstanceById(id).get())); + int i = getInstIndex(inst); + if (i != -1) + { + emit dataChanged(index(i), index(i)); + } } -int InstanceList::getInstIndex(BaseInstance *inst) const +InstancePtr InstanceList::loadInstance(const InstanceId& id) { - int count = m_instances.count(); - for (int i = 0; i < count; i++) - { - if (inst == m_instances[i].get()) - { - return i; - } - } - return -1; + if(!m_groupsLoaded) + { + loadGroupList(); + } + + auto instanceRoot = FS::PathCombine(m_instDir, id); + auto instanceSettings = std::make_shared(FS::PathCombine(instanceRoot, "instance.cfg")); + InstancePtr inst; + + instanceSettings->registerSetting("InstanceType", "Legacy"); + + QString inst_type = instanceSettings->get("InstanceType").toString(); + + if (inst_type == "OneSix" || inst_type == "Nostalgia") + { + inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + else if (inst_type == "Legacy") + { + inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + else + { + inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot)); + } + qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot(); + return inst; } -void InstanceList::propertiesChanged(BaseInstance *inst) +void InstanceList::saveGroupList() { - int i = getInstIndex(inst); - if (i != -1) - { - emit dataChanged(index(i), index(i)); - } + qDebug() << "Will save group list now."; + if(!m_instancesProbed) + { + qDebug() << "Group saving prevented because we don't know the full list of instances yet."; + return; + } + WatchLock foo(m_watcher, m_instDir); + QString groupFileName = m_instDir + "/instgroups.json"; + QMap> reverseGroupMap; + for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++) + { + QString id = iter.key(); + QString group = iter.value(); + if (group.isEmpty()) + continue; + if(!instanceSet.contains(id)) + { + qDebug() << "Skipping saving missing instance" << id << "to groups list."; + continue; + } + + if (!reverseGroupMap.count(group)) + { + QSet set; + set.insert(id); + reverseGroupMap[group] = set; + } + else + { + QSet &set = reverseGroupMap[group]; + set.insert(id); + } + } + QJsonObject toplevel; + toplevel.insert("formatVersion", QJsonValue(QString("1"))); + QJsonObject groupsArr; + for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.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); + try + { + FS::write(groupFileName, doc.toJson()); + qDebug() << "Group list saved."; + } + catch (const FS::FileSystemException &e) + { + qCritical() << "Failed to write instance group file :" << e.cause(); + } } + +void InstanceList::loadGroupList() +{ + qDebug() << "Will load group list now."; + QSet groupSet; + + QString groupFileName = m_instDir + "/instgroups.json"; + + // if there's no group file, fail + if (!QFileInfo(groupFileName).exists()) + return; + + QByteArray jsonData; + try + { + jsonData = FS::read(groupFileName); + } + catch (const FS::FileSystemException &e) + { + qCritical() << "Failed to read instance group file :" << e.cause(); + return; + } + + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); + + // if the json was bad, fail + if (error.error != QJsonParseError::NoError) + { + qCritical() << QString("Failed to parse instance group file: %1 at offset %2") + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); + return; + } + + // if the root of the json wasn't an object, fail + if (!jsonDoc.isObject()) + { + qWarning() << "Invalid group file. Root entry should be an object."; + return; + } + + QJsonObject rootObj = jsonDoc.object(); + + // Make sure the format version matches, otherwise fail. + if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION) + return; + + // Get the groups. if it's not an object, fail + if (!rootObj.value("groups").isObject()) + { + qWarning() << "Invalid group list JSON: 'groups' should be an object."; + return; + } + + m_groupMap.clear(); + + // Iterate through all the groups. + QJsonObject groupMapping = rootObj.value("groups").toObject(); + for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) + { + QString groupName = iter.key(); + + // If not an object, complain and skip to the next one. + if (!iter.value().isObject()) + { + qWarning() << QString("Group '%1' in the group list should " + "be an object.") + .arg(groupName) + .toUtf8(); + continue; + } + + QJsonObject groupObj = iter.value().toObject(); + if (!groupObj.value("instances").isArray()) + { + qWarning() << QString("Group '%1' in the group list is invalid. " + "It should contain an array " + "called 'instances'.") + .arg(groupName) + .toUtf8(); + continue; + } + + // keep a list/set of groups for choosing + groupSet.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++) + { + m_groupMap[(*iter2).toString()] = groupName; + } + } + m_groupsLoaded = true; + m_groups.unite(groupSet); + qDebug() << "Group list loaded."; +} + +void InstanceList::instanceDirContentsChanged(const QString& path) +{ + Q_UNUSED(path); + emit instancesChanged(); +} + +void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) +{ + QString newInstDir = QDir(value.toString()).canonicalPath(); + if(newInstDir != m_instDir) + { + if(m_groupsLoaded) + { + saveGroupList(); + } + m_instDir = newInstDir; + m_groupsLoaded = false; + emit instancesChanged(); + } +} + +class InstanceStaging : public Task +{ +Q_OBJECT + const unsigned minBackoff = 1; + const unsigned maxBackoff = 16; +public: + InstanceStaging ( + InstanceList * parent, + Task * child, + const QString & stagingPath, + const QString& instanceName, + const QString& groupName ) + : backoff(minBackoff, maxBackoff) + { + m_parent = parent; + m_child.reset(child); + connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); + connect(child, &Task::failed, this, &InstanceStaging::childFailed); + connect(child, &Task::status, this, &InstanceStaging::setStatus); + connect(child, &Task::progress, this, &InstanceStaging::setProgress); + m_instanceName = instanceName; + m_groupName = groupName; + m_stagingPath = stagingPath; + m_backoffTimer.setSingleShot(true); + connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); + } + + virtual ~InstanceStaging() {}; + + + // FIXME/TODO: add ability to abort during instance commit retries + bool abort() override + { + if(m_child && m_child->canAbort()) + { + return m_child->abort(); + } + return false; + } + bool canAbort() const override + { + if(m_child && m_child->canAbort()) + { + return true; + } + return false; + } + +protected: + virtual void executeTask() override + { + m_child->start(); + } + QStringList warnings() const override + { + return m_child->warnings(); + } + +private slots: + void childSucceded() + { + unsigned sleepTime = backoff(); + if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) + { + emitSucceeded(); + return; + } + // we actually failed, retry? + if(sleepTime == maxBackoff) + { + emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); + return; + } + qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + m_backoffTimer.start(sleepTime * 500); + } + void childFailed(const QString & reason) + { + m_parent->destroyStagingPath(m_stagingPath); + emitFailed(reason); + } + +private: + /* + * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. + * Basically, it starts messing things up while MultiMC is extracting/creating instances + * and causes that horrible failure that is NTFS to lock files in place because they are open. + */ + ExponentialSeries backoff; + QString m_stagingPath; + InstanceList * m_parent; + unique_qobject_ptr m_child; + QString m_instanceName; + QString m_groupName; + QTimer m_backoffTimer; +}; + +Task * InstanceList::wrapInstanceTask(InstanceTask * task) +{ + auto stagingPath = getStagedInstancePath(); + task->setStagingPath(stagingPath); + task->setParentSettings(m_globalSettings); + return new InstanceStaging(this, task, stagingPath, task->name(), task->group()); +} + +QString InstanceList::getStagedInstancePath() +{ + QString key = QUuid::createUuid().toString(); + QString relPath = FS::PathCombine("_MMC_TEMP/" , key); + QDir rootPath(m_instDir); + auto path = FS::PathCombine(m_instDir, relPath); + if(!rootPath.mkpath(relPath)) + { + return QString(); + } + return path; +} + +bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) +{ + QDir dir; + QString instID = FS::DirNameFromString(instanceName, m_instDir); + { + WatchLock lock(m_watcher, m_instDir); + QString destination = FS::PathCombine(m_instDir, instID); + if(!dir.rename(path, destination)) + { + qWarning() << "Failed to move" << path << "to" << destination; + return false; + } + m_groupMap[instID] = groupName; + instanceSet.insert(instID); + m_groups.insert(groupName); + emit instancesChanged(); + emit instanceSelectRequest(instID); + } + saveGroupList(); + return true; +} + +bool InstanceList::destroyStagingPath(const QString& keyPath) +{ + return FS::deletePath(keyPath); +} + +#include "InstanceList.moc" \ No newline at end of file diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h index bb879c83..e0abd890 100644 --- a/api/logic/InstanceList.h +++ b/api/logic/InstanceList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,85 +21,146 @@ #include #include "BaseInstance.h" -#include "BaseInstanceProvider.h" #include "multimc_logic_export.h" #include "QObjectPtr.h" -class BaseInstance; +class QFileSystemWatcher; +class InstanceTask; +using InstanceId = QString; +using GroupId = QString; +using InstanceLocator = std::pair; + +enum class InstCreateError +{ + NoCreateError = 0, + NoSuchVersion, + UnknownCreateError, + InstExists, + CantCreateDir +}; + +enum class GroupsState +{ + NotLoaded, + Steady, + Dirty +}; + class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit InstanceList(QObject *parent = 0); - virtual ~InstanceList(); + explicit InstanceList(SettingsObjectPtr settings, 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 - { - GroupRole = Qt::UserRole, - 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 - }; - - InstancePtr at(int i) const - { - return m_instances.at(i); - } - - int count() const - { - return m_instances.count(); - } - - InstListError loadList(bool complete = false); - void saveNow(); - - /// Add an instance provider. Takes ownership of it. Should only be done before the first load. - void addInstanceProvider(BaseInstanceProvider * provider); - - InstancePtr getInstanceById(QString id) const; - QModelIndex getInstanceIndexById(const QString &id) const; - QStringList getGroups(); - - void deleteGroup(const QString & name); + QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + bool setData(const QModelIndex & index, const QVariant & value, int role) override; + + enum AdditionalRoles + { + GroupRole = Qt::UserRole, + 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 + }; + + InstancePtr at(int i) const + { + return m_instances.at(i); + } + + int count() const + { + return m_instances.count(); + } + + InstListError loadList(); + void saveNow(); + + + InstancePtr getInstanceById(QString id) const; + QModelIndex getInstanceIndexById(const QString &id) const; + QStringList getGroups(); + GroupId getInstanceGroup(const InstanceId & id) const; + void setInstanceGroup(const InstanceId & id, const GroupId& name); + + void deleteGroup(const GroupId & name); + void deleteInstance(const InstanceId & id); + + // Wrap an instance creation task in some more task machinery and make it ready to be used + Task * wrapInstanceTask(InstanceTask * task); + + /** + * Create a new empty staging area for instance creation and @return a path/key top commit it later. + * Used by instance manipulation tasks. + */ + QString getStagedInstancePath(); + + /** + * Commit the staging area given by @keyPath to the provider - used when creation succeeds. + * Used by instance manipulation tasks. + */ + bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName); + + /** + * Destroy a previously created staging area given by @keyPath - used when creation fails. + * Used by instance manipulation tasks. + */ + bool destroyStagingPath(const QString & keyPath); signals: - void dataIsInvalid(); + void dataIsInvalid(); + void instancesChanged(); + void instanceSelectRequest(QString instanceId); + void groupsChanged(QSet groups); + +public slots: + void on_InstFolderChanged(const Setting &setting, QVariant value); private slots: - void propertiesChanged(BaseInstance *inst); - void groupsPublished(QSet); - void providerUpdated(); + void propertiesChanged(BaseInstance *inst); + void providerUpdated(); + void instanceDirContentsChanged(const QString &path); + +private: + int getInstIndex(BaseInstance *inst) const; + void suspendWatch(); + void resumeWatch(); + void add(const QList &list); + void loadGroupList(); + void saveGroupList(); + QList discoverInstances(); + InstancePtr loadInstance(const InstanceId& id); private: - int getInstIndex(BaseInstance *inst) const; - void suspendWatch(); - void resumeWatch(); - void add(const QList &list); - -protected: - int m_watchLevel = 0; - QSet m_updatedProviders; - QList m_instances; - QSet m_groups; - QVector> m_providers; + int m_watchLevel = 0; + bool m_dirty = false; + QList m_instances; + QSet m_groups; + + SettingsObjectPtr m_globalSettings; + QString m_instDir; + QFileSystemWatcher * m_watcher; + QMap m_groupMap; + QSet instanceSet; + bool m_groupsLoaded = false; + bool m_instancesProbed = false; }; diff --git a/api/logic/InstanceTask.h b/api/logic/InstanceTask.h index 8fc98eb7..c5f6c7fd 100644 --- a/api/logic/InstanceTask.h +++ b/api/logic/InstanceTask.h @@ -4,52 +4,50 @@ #include "multimc_logic_export.h" #include "settings/SettingsObject.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT InstanceTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit InstanceTask(); - virtual ~InstanceTask(); - - void setParentSettings(SettingsObjectPtr settings) - { - m_globalSettings = settings; - } - - void setStagingPath(const QString &stagingPath) - { - m_stagingPath = stagingPath; - } - - void setName(const QString &name) - { - m_instName = name; - } - QString name() const - { - return m_instName; - } - - void setIcon(const QString &icon) - { - m_instIcon = icon; - } - - void setGroup(const QString &group) - { - m_instGroup = group; - } - QString group() const - { - return m_instGroup; - } + explicit InstanceTask(); + virtual ~InstanceTask(); + + void setParentSettings(SettingsObjectPtr settings) + { + m_globalSettings = settings; + } + + void setStagingPath(const QString &stagingPath) + { + m_stagingPath = stagingPath; + } + + void setName(const QString &name) + { + m_instName = name; + } + QString name() const + { + return m_instName; + } + + void setIcon(const QString &icon) + { + m_instIcon = icon; + } + + void setGroup(const QString &group) + { + m_instGroup = group; + } + QString group() const + { + return m_instGroup; + } protected: /* data */ - SettingsObjectPtr m_globalSettings; - QString m_instName; - QString m_instIcon; - QString m_instGroup; - QString m_stagingPath; + SettingsObjectPtr m_globalSettings; + QString m_instName; + QString m_instIcon; + QString m_instGroup; + QString m_stagingPath; }; diff --git a/api/logic/Json.cpp b/api/logic/Json.cpp index f2cbc8a3..37ada1aa 100644 --- a/api/logic/Json.cpp +++ b/api/logic/Json.cpp @@ -11,262 +11,262 @@ namespace Json { void write(const QJsonDocument &doc, const QString &filename) { - FS::write(filename, doc.toJson()); + FS::write(filename, doc.toJson()); } void write(const QJsonObject &object, const QString &filename) { - write(QJsonDocument(object), filename); + write(QJsonDocument(object), filename); } void write(const QJsonArray &array, const QString &filename) { - write(QJsonDocument(array), filename); + write(QJsonDocument(array), filename); } QByteArray toBinary(const QJsonObject &obj) { - return QJsonDocument(obj).toBinaryData(); + return QJsonDocument(obj).toBinaryData(); } QByteArray toBinary(const QJsonArray &array) { - return QJsonDocument(array).toBinaryData(); + return QJsonDocument(array).toBinaryData(); } QByteArray toText(const QJsonObject &obj) { - return QJsonDocument(obj).toJson(QJsonDocument::Compact); + return QJsonDocument(obj).toJson(QJsonDocument::Compact); } QByteArray toText(const QJsonArray &array) { - return QJsonDocument(array).toJson(QJsonDocument::Compact); + return QJsonDocument(array).toJson(QJsonDocument::Compact); } static bool isBinaryJson(const QByteArray &data) { - decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; - return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; + decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; + return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; } QJsonDocument requireDocument(const QByteArray &data, const QString &what) { - if (isBinaryJson(data)) - { - QJsonDocument doc = QJsonDocument::fromBinaryData(data); - if (doc.isNull()) - { - throw JsonException(what + ": Invalid JSON (binary JSON detected)"); - } - return doc; - } - else - { - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - if (error.error != QJsonParseError::NoError) - { - throw JsonException(what + ": Error parsing JSON: " + error.errorString()); - } - return doc; - } + if (isBinaryJson(data)) + { + QJsonDocument doc = QJsonDocument::fromBinaryData(data); + if (doc.isNull()) + { + throw JsonException(what + ": Invalid JSON (binary JSON detected)"); + } + return doc; + } + else + { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + if (error.error != QJsonParseError::NoError) + { + throw JsonException(what + ": Error parsing JSON: " + error.errorString()); + } + return doc; + } } QJsonDocument requireDocument(const QString &filename, const QString &what) { - return requireDocument(FS::read(filename), what); + return requireDocument(FS::read(filename), what); } QJsonObject requireObject(const QJsonDocument &doc, const QString &what) { - if (!doc.isObject()) - { - throw JsonException(what + " is not an object"); - } - return doc.object(); + if (!doc.isObject()) + { + throw JsonException(what + " is not an object"); + } + return doc.object(); } QJsonArray requireArray(const QJsonDocument &doc, const QString &what) { - if (!doc.isArray()) - { - throw JsonException(what + " is not an array"); - } - return doc.array(); + if (!doc.isArray()) + { + throw JsonException(what + " is not an array"); + } + return doc.array(); } void writeString(QJsonObject &to, const QString &key, const QString &value) { - if (!value.isEmpty()) - { - to.insert(key, value); - } + if (!value.isEmpty()) + { + to.insert(key, value); + } } void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) { - if (!values.isEmpty()) - { - QJsonArray array; - for(auto value: values) - { - array.append(value); - } - to.insert(key, array); - } + if (!values.isEmpty()) + { + QJsonArray array; + for(auto value: values) + { + array.append(value); + } + to.insert(key, array); + } } template<> QJsonValue toJson(const QUrl &url) { - return QJsonValue(url.toString(QUrl::FullyEncoded)); + return QJsonValue(url.toString(QUrl::FullyEncoded)); } template<> QJsonValue toJson(const QByteArray &data) { - return QJsonValue(QString::fromLatin1(data.toHex())); + return QJsonValue(QString::fromLatin1(data.toHex())); } template<> QJsonValue toJson(const QDateTime &datetime) { - return QJsonValue(datetime.toString(Qt::ISODate)); + return QJsonValue(datetime.toString(Qt::ISODate)); } template<> QJsonValue toJson(const QDir &dir) { - return QDir::current().relativeFilePath(dir.absolutePath()); + return QDir::current().relativeFilePath(dir.absolutePath()); } template<> QJsonValue toJson(const QUuid &uuid) { - return uuid.toString(); + return uuid.toString(); } template<> QJsonValue toJson(const QVariant &variant) { - return QJsonValue::fromVariant(variant); + return QJsonValue::fromVariant(variant); } template<> QByteArray requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, what); - // ensure that the string can be safely cast to Latin1 - if (string != QString::fromLatin1(string.toLatin1())) - { - throw JsonException(what + " is not encodable as Latin1"); - } - return QByteArray::fromHex(string.toLatin1()); + const QString string = ensureIsType(value, what); + // ensure that the string can be safely cast to Latin1 + if (string != QString::fromLatin1(string.toLatin1())) + { + throw JsonException(what + " is not encodable as Latin1"); + } + return QByteArray::fromHex(string.toLatin1()); } template<> QJsonArray requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isArray()) - { - throw JsonException(what + " is not an array"); - } - return value.toArray(); + if (!value.isArray()) + { + throw JsonException(what + " is not an array"); + } + return value.toArray(); } template<> QString requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isString()) - { - throw JsonException(what + " is not a string"); - } - return value.toString(); + if (!value.isString()) + { + throw JsonException(what + " is not a string"); + } + return value.toString(); } template<> bool requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isBool()) - { - throw JsonException(what + " is not a bool"); - } - return value.toBool(); + if (!value.isBool()) + { + throw JsonException(what + " is not a bool"); + } + return value.toBool(); } template<> double requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isDouble()) - { - throw JsonException(what + " is not a double"); - } - return value.toDouble(); + if (!value.isDouble()) + { + throw JsonException(what + " is not a double"); + } + return value.toDouble(); } template<> int requireIsType(const QJsonValue &value, const QString &what) { - const double doubl = requireIsType(value, what); - if (fmod(doubl, 1) != 0) - { - throw JsonException(what + " is not an integer"); - } - return int(doubl); + const double doubl = requireIsType(value, what); + if (fmod(doubl, 1) != 0) + { + throw JsonException(what + " is not an integer"); + } + return int(doubl); } template<> QDateTime requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); - if (!datetime.isValid()) - { - throw JsonException(what + " is not a ISO formatted date/time value"); - } - return datetime; + const QString string = requireIsType(value, what); + const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); + if (!datetime.isValid()) + { + throw JsonException(what + " is not a ISO formatted date/time value"); + } + return datetime; } template<> QUrl requireIsType(const QJsonValue &value, const QString &what) { - const QString string = ensureIsType(value, what); - if (string.isEmpty()) - { - return QUrl(); - } - const QUrl url = QUrl(string, QUrl::StrictMode); - if (!url.isValid()) - { - throw JsonException(what + " is not a correctly formatted URL"); - } - return url; + const QString string = ensureIsType(value, what); + if (string.isEmpty()) + { + return QUrl(); + } + const QUrl url = QUrl(string, QUrl::StrictMode); + if (!url.isValid()) + { + throw JsonException(what + " is not a correctly formatted URL"); + } + return url; } template<> QDir requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - // FIXME: does not handle invalid characters! - return QDir::current().absoluteFilePath(string); + const QString string = requireIsType(value, what); + // FIXME: does not handle invalid characters! + return QDir::current().absoluteFilePath(string); } template<> QUuid requireIsType(const QJsonValue &value, const QString &what) { - const QString string = requireIsType(value, what); - const QUuid uuid = QUuid(string); - if (uuid.toString() != string) // converts back => valid - { - throw JsonException(what + " is not a valid UUID"); - } - return uuid; + const QString string = requireIsType(value, what); + const QUuid uuid = QUuid(string); + if (uuid.toString() != string) // converts back => valid + { + throw JsonException(what + " is not a valid UUID"); + } + return uuid; } template<> QJsonObject requireIsType(const QJsonValue &value, const QString &what) { - if (!value.isObject()) - { - throw JsonException(what + " is not an object"); - } - return value.toObject(); + if (!value.isObject()) + { + throw JsonException(what + " is not an object"); + } + return value.toObject(); } template<> QVariant requireIsType(const QJsonValue &value, const QString &what) { - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value.toVariant(); + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value.toVariant(); } template<> QJsonValue requireIsType(const QJsonValue &value, const QString &what) { - if (value.isNull() || value.isUndefined()) - { - throw JsonException(what + " is null or undefined"); - } - return value; + if (value.isNull() || value.isUndefined()) + { + throw JsonException(what + " is null or undefined"); + } + return value; } } diff --git a/api/logic/Json.h b/api/logic/Json.h index e59b25de..34ff6fe2 100644 --- a/api/logic/Json.h +++ b/api/logic/Json.h @@ -19,7 +19,7 @@ namespace Json class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception { public: - JsonException(const QString &message) : Exception(message) {} + JsonException(const QString &message) : Exception(message) {} }; /// @throw FileSystemException @@ -51,7 +51,7 @@ void writeStringList(QJsonObject & to, const QString &key, const QStringList &va template QJsonValue toJson(const T &t) { - return QJsonValue(t); + return QJsonValue(t); } template<> QJsonValue toJson(const QUrl &url); @@ -69,12 +69,12 @@ QJsonValue toJson(const QVariant &variant); template QJsonArray toJsonArray(const QList &container) { - QJsonArray array; - for (const T item : container) - { - array.append(toJson(item)); - } - return array; + QJsonArray array; + for (const T item : container) + { + array.append(toJson(item)); + } + return array; } ////////////////// READING //////////////////// @@ -115,119 +115,119 @@ template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType(const QJsonValue &value template T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value") { - if (value.isUndefined() || value.isNull()) - { - return default_; - } - try - { - return requireIsType(value, what); - } - catch (JsonException &) - { - return default_; - } + if (value.isUndefined() || value.isNull()) + { + return default_; + } + try + { + return requireIsType(value, what); + } + catch (const JsonException &) + { + return default_; + } } /// @throw JsonException template T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return requireIsType(parent.value(key), localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return requireIsType(parent.value(key), localWhat); } template T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsType(parent.value(key), default_, localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsType(parent.value(key), default_, localWhat); } template QVector requireIsArrayOf(const QJsonDocument &doc) { - const QJsonArray array = requireArray(doc); - QVector out; - for (const QJsonValue val : array) - { - out.append(requireIsType(val, "Document")); - } - return out; + const QJsonArray array = requireArray(doc); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, "Document")); + } + return out; } template QVector ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") { - const QJsonArray array = ensureIsType(value, QJsonArray(), what); - QVector out; - for (const QJsonValue val : array) - { - out.append(requireIsType(val, what)); - } - return out; + const QJsonArray array = ensureIsType(value, QJsonArray(), what); + QVector out; + for (const QJsonValue val : array) + { + out.append(requireIsType(val, what)); + } + return out; } template QVector ensureIsArrayOf(const QJsonValue &value, const QVector default_, const QString &what = "Value") { - if (value.isUndefined()) - { - return default_; - } - return ensureIsArrayOf(value, what); + if (value.isUndefined()) + { + return default_; + } + return ensureIsArrayOf(value, what); } /// @throw JsonException template QVector requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - throw JsonException(localWhat + "s parent does not contain " + localWhat); - } - return ensureIsArrayOf(parent.value(key), localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + throw JsonException(localWhat + "s parent does not contain " + localWhat); + } + return ensureIsArrayOf(parent.value(key), localWhat); } template QVector ensureIsArrayOf(const QJsonObject &parent, const QString &key, - const QVector &default_ = QVector(), const QString &what = "__placeholder__") + const QVector &default_ = QVector(), const QString &what = "__placeholder__") { - const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); - if (!parent.contains(key)) - { - return default_; - } - return ensureIsArrayOf(parent.value(key), default_, localWhat); + const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); + if (!parent.contains(key)) + { + return default_; + } + return ensureIsArrayOf(parent.value(key), default_, localWhat); } // this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers #define JSON_HELPERFUNCTIONS(NAME, TYPE) \ - inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ - { \ - return requireIsType(value, what); \ - } \ - inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ - { \ - return ensureIsType(value, default_, what); \ - } \ - inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ - { \ - return requireIsType(parent, key, what); \ - } \ - inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ - { \ - return ensureIsType(parent, key, default_, what); \ - } + inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ + { \ + return requireIsType(value, what); \ + } \ + inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ + { \ + return ensureIsType(value, default_, what); \ + } \ + inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ + { \ + return requireIsType(parent, key, what); \ + } \ + inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ + { \ + return ensureIsType(parent, key, default_, what); \ + } JSON_HELPERFUNCTIONS(Array, QJsonArray) JSON_HELPERFUNCTIONS(Object, QJsonObject) diff --git a/api/logic/LoggedProcess.cpp b/api/logic/LoggedProcess.cpp index f89b4acc..822c0f04 100644 --- a/api/logic/LoggedProcess.cpp +++ b/api/logic/LoggedProcess.cpp @@ -4,157 +4,157 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) { - // QProcess has a strange interface... let's map a lot of those into a few. - connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); - connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); - connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); - connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); - connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); + // QProcess has a strange interface... let's map a lot of those into a few. + connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); + connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); + connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); + connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); + connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); } LoggedProcess::~LoggedProcess() { - if(m_is_detachable) - { - setProcessState(QProcess::NotRunning); - } + if(m_is_detachable) + { + setProcessState(QProcess::NotRunning); + } } QStringList reprocess(const QByteArray & data, QString & leftover) { - QString str = leftover + QString::fromLocal8Bit(data); + QString str = leftover + QString::fromLocal8Bit(data); - str.remove('\r'); - QStringList lines = str.split("\n"); - leftover = lines.takeLast(); - return lines; + str.remove('\r'); + QStringList lines = str.split("\n"); + leftover = lines.takeLast(); + return lines; } void LoggedProcess::on_stdErr() { - auto lines = reprocess(readAllStandardError(), m_err_leftover); - emit log(lines, MessageLevel::StdErr); + auto lines = reprocess(readAllStandardError(), m_err_leftover); + emit log(lines, MessageLevel::StdErr); } void LoggedProcess::on_stdOut() { - auto lines = reprocess(readAllStandardOutput(), m_out_leftover); - emit log(lines, MessageLevel::StdOut); + auto lines = reprocess(readAllStandardOutput(), m_out_leftover); + emit log(lines, MessageLevel::StdOut); } void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status) { - // save the exit code - m_exit_code = exit_code; - - // Flush console window - if (!m_err_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdErr); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdOut); - m_out_leftover.clear(); - } - - // based on state, send signals - if (!m_is_aborting) - { - if (status == QProcess::NormalExit) - { - //: Message displayed on instance exit - emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Finished); - } - else - { - //: Message displayed on instance crashed - if(exit_code == -1) - emit log({tr("Process crashed.")}, MessageLevel::MultiMC); - else - emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); - changeState(LoggedProcess::Crashed); - } - } - else - { - //: Message displayed after the instance exits due to kill request - emit log({tr("Process was killed by user.")}, MessageLevel::Error); - changeState(LoggedProcess::Aborted); - } + // save the exit code + m_exit_code = exit_code; + + // Flush console window + if (!m_err_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdErr); + m_err_leftover.clear(); + } + if (!m_out_leftover.isEmpty()) + { + emit log({m_err_leftover}, MessageLevel::StdOut); + m_out_leftover.clear(); + } + + // based on state, send signals + if (!m_is_aborting) + { + if (status == QProcess::NormalExit) + { + //: Message displayed on instance exit + emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Finished); + } + else + { + //: Message displayed on instance crashed + if(exit_code == -1) + emit log({tr("Process crashed.")}, MessageLevel::MultiMC); + else + emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); + changeState(LoggedProcess::Crashed); + } + } + else + { + //: Message displayed after the instance exits due to kill request + emit log({tr("Process was killed by user.")}, MessageLevel::Error); + changeState(LoggedProcess::Aborted); + } } void LoggedProcess::on_error(QProcess::ProcessError error) { - switch(error) - { - case QProcess::FailedToStart: - { - emit log({tr("The process failed to start.")}, MessageLevel::Fatal); - changeState(LoggedProcess::FailedToStart); - break; - } - // we'll just ignore those... never needed them - case QProcess::Crashed: - case QProcess::ReadError: - case QProcess::Timedout: - case QProcess::UnknownError: - case QProcess::WriteError: - break; - } + switch(error) + { + case QProcess::FailedToStart: + { + emit log({tr("The process failed to start.")}, MessageLevel::Fatal); + changeState(LoggedProcess::FailedToStart); + break; + } + // we'll just ignore those... never needed them + case QProcess::Crashed: + case QProcess::ReadError: + case QProcess::Timedout: + case QProcess::UnknownError: + case QProcess::WriteError: + break; + } } void LoggedProcess::kill() { - m_is_aborting = true; - QProcess::kill(); + m_is_aborting = true; + QProcess::kill(); } int LoggedProcess::exitCode() const { - return m_exit_code; + return m_exit_code; } void LoggedProcess::changeState(LoggedProcess::State state) { - if(state == m_state) - return; - m_state = state; - emit stateChanged(m_state); + if(state == m_state) + return; + m_state = state; + emit stateChanged(m_state); } LoggedProcess::State LoggedProcess::state() const { - return m_state; + return m_state; } void LoggedProcess::on_stateChange(QProcess::ProcessState state) { - switch(state) - { - case QProcess::NotRunning: - break; // let's not - there are too many that handle this already. - case QProcess::Starting: - { - if(m_state != LoggedProcess::NotRunning) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; - } - changeState(LoggedProcess::Starting); - return; - } - case QProcess::Running: - { - if(m_state != LoggedProcess::Starting) - { - qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; - } - changeState(LoggedProcess::Running); - return; - } - } + switch(state) + { + case QProcess::NotRunning: + break; // let's not - there are too many that handle this already. + case QProcess::Starting: + { + if(m_state != LoggedProcess::NotRunning) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; + } + changeState(LoggedProcess::Starting); + return; + } + case QProcess::Running: + { + if(m_state != LoggedProcess::Starting) + { + qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; + } + changeState(LoggedProcess::Running); + return; + } + } } #if defined Q_OS_WIN32 @@ -172,5 +172,5 @@ qint64 LoggedProcess::processId() const void LoggedProcess::setDetachable(bool detachable) { - m_is_detachable = detachable; + m_is_detachable = detachable; } diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h index 6a80365f..256c0c45 100644 --- a/api/logic/LoggedProcess.h +++ b/api/logic/LoggedProcess.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,54 +27,54 @@ class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess { Q_OBJECT public: - enum State - { - NotRunning, - Starting, - FailedToStart, - Running, - Finished, - Crashed, - Aborted - }; + enum State + { + NotRunning, + Starting, + FailedToStart, + Running, + Finished, + Crashed, + Aborted + }; public: - explicit LoggedProcess(QObject* parent = 0); - virtual ~LoggedProcess(); + explicit LoggedProcess(QObject* parent = 0); + virtual ~LoggedProcess(); - State state() const; - int exitCode() const; - qint64 processId() const; + State state() const; + int exitCode() const; + qint64 processId() const; - void setDetachable(bool detachable); + void setDetachable(bool detachable); signals: - void log(QStringList lines, MessageLevel::Enum level); - void stateChanged(LoggedProcess::State state); + void log(QStringList lines, MessageLevel::Enum level); + void stateChanged(LoggedProcess::State state); public slots: - /** - * @brief kill the process - equivalent to kill -9 - */ - void kill(); + /** + * @brief kill the process - equivalent to kill -9 + */ + void kill(); private slots: - void on_stdErr(); - void on_stdOut(); - void on_exit(int exit_code, QProcess::ExitStatus status); - void on_error(QProcess::ProcessError error); - void on_stateChange(QProcess::ProcessState); + void on_stdErr(); + void on_stdOut(); + void on_exit(int exit_code, QProcess::ExitStatus status); + void on_error(QProcess::ProcessError error); + void on_stateChange(QProcess::ProcessState); private: - void changeState(LoggedProcess::State state); + void changeState(LoggedProcess::State state); private: - QString m_err_leftover; - QString m_out_leftover; - bool m_killed = false; - State m_state = NotRunning; - int m_exit_code = 0; - bool m_is_aborting = false; - bool m_is_detachable = false; + QString m_err_leftover; + QString m_out_leftover; + bool m_killed = false; + State m_state = NotRunning; + int m_exit_code = 0; + bool m_is_aborting = false; + bool m_is_detachable = false; }; diff --git a/api/logic/MMCStrings.cpp b/api/logic/MMCStrings.cpp index c50d596e..dc91c8d6 100644 --- a/api/logic/MMCStrings.cpp +++ b/api/logic/MMCStrings.cpp @@ -3,74 +3,74 @@ /// TAKEN FROM Qt, because it doesn't expose it intelligently static inline QChar getNextChar(const QString &s, int location) { - return (location < s.length()) ? s.at(location) : QChar(); + return (location < s.length()) ? s.at(location) : QChar(); } /// TAKEN FROM Qt, because it doesn't expose it intelligently int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) { - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { - // skip spaces, tabs and 0's - QChar c1 = getNextChar(s1, l1); - while (c1.isSpace()) - c1 = getNextChar(s1, ++l1); - QChar c2 = getNextChar(s2, l2); - while (c2.isSpace()) - c2 = getNextChar(s2, ++l2); + for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) + { + // skip spaces, tabs and 0's + QChar c1 = getNextChar(s1, l1); + while (c1.isSpace()) + c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); + while (c2.isSpace()) + c2 = getNextChar(s2, ++l2); - if (c1.isDigit() && c2.isDigit()) - { - while (c1.digitValue() == 0) - c1 = getNextChar(s1, ++l1); - while (c2.digitValue() == 0) - c2 = getNextChar(s2, ++l2); + if (c1.isDigit() && c2.isDigit()) + { + while (c1.digitValue() == 0) + c1 = getNextChar(s1, ++l1); + while (c2.digitValue() == 0) + c2 = getNextChar(s2, ++l2); - int lookAheadLocation1 = l1; - int lookAheadLocation2 = l2; - int currentReturnValue = 0; - // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), - lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { - bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); - bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); - if (!is1ADigit && !is2ADigit) - break; - if (!is1ADigit) - return -1; - if (!is2ADigit) - return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { - currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { - currentReturnValue = 1; - } - } - } - if (currentReturnValue != 0) - return currentReturnValue; - } - if (cs == Qt::CaseInsensitive) - { - if (!c1.isLower()) - c1 = c1.toLower(); - if (!c2.isLower()) - c2 = c2.toLower(); - } - int r = QString::localeAwareCompare(c1, c2); - if (r < 0) - return -1; - if (r > 0) - return 1; - } - // The two strings are the same (02 == 2) so fall back to the normal sort - return QString::compare(s1, s2, cs); + int lookAheadLocation1 = l1; + int lookAheadLocation2 = l2; + int currentReturnValue = 0; + // find the last digit, setting currentReturnValue as we go if it isn't equal + for (QChar lookAhead1 = c1, lookAhead2 = c2; + (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), + lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) + { + bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); + bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); + if (!is1ADigit && !is2ADigit) + break; + if (!is1ADigit) + return -1; + if (!is2ADigit) + return 1; + if (currentReturnValue == 0) + { + if (lookAhead1 < lookAhead2) + { + currentReturnValue = -1; + } + else if (lookAhead1 > lookAhead2) + { + currentReturnValue = 1; + } + } + } + if (currentReturnValue != 0) + return currentReturnValue; + } + if (cs == Qt::CaseInsensitive) + { + if (!c1.isLower()) + c1 = c1.toLower(); + if (!c2.isLower()) + c2 = c2.toLower(); + } + int r = QString::localeAwareCompare(c1, c2); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + // The two strings are the same (02 == 2) so fall back to the normal sort + return QString::compare(s1, s2, cs); } diff --git a/api/logic/MMCStrings.h b/api/logic/MMCStrings.h index 5606b909..493ba3d2 100644 --- a/api/logic/MMCStrings.h +++ b/api/logic/MMCStrings.h @@ -6,5 +6,5 @@ namespace Strings { - int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); + int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); } diff --git a/api/logic/MMCZip.cpp b/api/logic/MMCZip.cpp index 1e7ad51d..3afdbf5e 100644 --- a/api/logic/MMCZip.cpp +++ b/api/logic/MMCZip.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,231 +25,231 @@ // ours bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, const JlCompress::FilterFunction filter) { - QuaZip modZip(from.filePath()); - modZip.open(QuaZip::mdUnzip); - - QuaZipFile fileInsideMod(&modZip); - QuaZipFile zipOutFile(into); - for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) - { - QString filename = modZip.getCurrentFileName(); - if (filter && !filter(filename)) - { - qDebug() << "Skipping file " << filename << " from " - << from.fileName() << " - filtered"; - continue; - } - if (contained.contains(filename)) - { - qDebug() << "Skipping already contained file " << filename << " from " - << from.fileName(); - continue; - } - contained.insert(filename); - - if (!fileInsideMod.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to open " << filename << " from " << from.fileName(); - return false; - } - - QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); - - if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) - { - qCritical() << "Failed to open " << filename << " in the jar"; - fileInsideMod.close(); - return false; - } - if (!JlCompress::copyData(fileInsideMod, zipOutFile)) - { - zipOutFile.close(); - fileInsideMod.close(); - qCritical() << "Failed to copy data of " << filename << " into the jar"; - return false; - } - zipOutFile.close(); - fileInsideMod.close(); - } - return true; + QuaZip modZip(from.filePath()); + modZip.open(QuaZip::mdUnzip); + + QuaZipFile fileInsideMod(&modZip); + QuaZipFile zipOutFile(into); + for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile()) + { + QString filename = modZip.getCurrentFileName(); + if (filter && !filter(filename)) + { + qDebug() << "Skipping file " << filename << " from " + << from.fileName() << " - filtered"; + continue; + } + if (contained.contains(filename)) + { + qDebug() << "Skipping already contained file " << filename << " from " + << from.fileName(); + continue; + } + contained.insert(filename); + + if (!fileInsideMod.open(QIODevice::ReadOnly)) + { + qCritical() << "Failed to open " << filename << " from " << from.fileName(); + return false; + } + + QuaZipNewInfo info_out(fileInsideMod.getActualFileName()); + + if (!zipOutFile.open(QIODevice::WriteOnly, info_out)) + { + qCritical() << "Failed to open " << filename << " in the jar"; + fileInsideMod.close(); + return false; + } + if (!JlCompress::copyData(fileInsideMod, zipOutFile)) + { + zipOutFile.close(); + fileInsideMod.close(); + qCritical() << "Failed to copy data of " << filename << " into the jar"; + return false; + } + zipOutFile.close(); + fileInsideMod.close(); + } + return true; } // ours bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods) { - QuaZip zipOut(targetJarPath); - if (!zipOut.open(QuaZip::mdCreate)) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to open the minecraft.jar for modding"; - return false; - } - // Files already added to the jar. - // These files will be skipped. - QSet addedFiles; - - // Modify the jar - QListIterator i(mods); + QuaZip zipOut(targetJarPath); + if (!zipOut.open(QuaZip::mdCreate)) + { + QFile::remove(targetJarPath); + qCritical() << "Failed to open the minecraft.jar for modding"; + return false; + } + // Files already added to the jar. + // These files will be skipped. + QSet addedFiles; + + // Modify the jar + QListIterator i(mods); i.toBack(); while (i.hasPrevious()) - { - const Mod &mod = i.previous(); - // do not merge disabled mods. - if (!mod.enabled()) - continue; - if (mod.type() == Mod::MOD_ZIPFILE) - { - if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - } - else if (mod.type() == Mod::MOD_SINGLEFILE) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - addedFiles.insert(filename.fileName()); - } - else if (mod.type() == Mod::MOD_FOLDER) - { - // FIXME: buggy - does not work with addedFiles - auto filename = mod.filename(); - QString what_to_zip = filename.absoluteFilePath(); - QDir dir(what_to_zip); - dir.cdUp(); - QString parent_dir = dir.absolutePath(); - if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; - return false; - } - qDebug() << "Adding folder " << filename.fileName() << " from " - << filename.absoluteFilePath(); - } - else - { - // Make sure we do not continue launching when something is missing or undefined... - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; - return false; - } - } - - if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) - { - zipOut.close(); - QFile::remove(targetJarPath); - qCritical() << "Failed to insert minecraft.jar contents."; - return false; - } - - // Recompress the jar - zipOut.close(); - if (zipOut.getZipError() != 0) - { - QFile::remove(targetJarPath); - qCritical() << "Failed to finalize minecraft.jar!"; - return false; - } - return true; + { + const Mod &mod = i.previous(); + // do not merge disabled mods. + if (!mod.enabled()) + continue; + if (mod.type() == Mod::MOD_ZIPFILE) + { + if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles)) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + } + else if (mod.type() == Mod::MOD_SINGLEFILE) + { + // FIXME: buggy - does not work with addedFiles + auto filename = mod.filename(); + if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName())) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + addedFiles.insert(filename.fileName()); + } + else if (mod.type() == Mod::MOD_FOLDER) + { + // FIXME: buggy - does not work with addedFiles + auto filename = mod.filename(); + QString what_to_zip = filename.absoluteFilePath(); + QDir dir(what_to_zip); + dir.cdUp(); + QString parent_dir = dir.absolutePath(); + if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; + return false; + } + qDebug() << "Adding folder " << filename.fileName() << " from " + << filename.absoluteFilePath(); + } + else + { + // Make sure we do not continue launching when something is missing or undefined... + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar."; + return false; + } + } + + if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");})) + { + zipOut.close(); + QFile::remove(targetJarPath); + qCritical() << "Failed to insert minecraft.jar contents."; + return false; + } + + // Recompress the jar + zipOut.close(); + if (zipOut.getZipError() != 0) + { + QFile::remove(targetJarPath); + qCritical() << "Failed to finalize minecraft.jar!"; + return false; + } + return true; } // ours QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root) { - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - return root; - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - QString result = findFolderOfFileInZip(zip, what, root + fileName); - if(!result.isEmpty()) - { - return result; - } - } - return QString(); + QuaZipDir rootDir(zip, root); + for(auto fileName: rootDir.entryList(QDir::Files)) + { + if(fileName == what) + return root; + } + for(auto fileName: rootDir.entryList(QDir::Dirs)) + { + QString result = findFolderOfFileInZip(zip, what, root + fileName); + if(!result.isEmpty()) + { + return result; + } + } + return QString(); } // ours bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) { - QuaZipDir rootDir(zip, root); - for(auto fileName: rootDir.entryList(QDir::Files)) - { - if(fileName == what) - { - result.append(root); - return true; - } - } - for(auto fileName: rootDir.entryList(QDir::Dirs)) - { - findFilesInZip(zip, what, result, root + fileName); - } - return !result.isEmpty(); + QuaZipDir rootDir(zip, root); + for(auto fileName: rootDir.entryList(QDir::Files)) + { + if(fileName == what) + { + result.append(root); + return true; + } + } + for(auto fileName: rootDir.entryList(QDir::Dirs)) + { + findFilesInZip(zip, what, result, root + fileName); + } + return !result.isEmpty(); } // ours QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) { - QDir directory(target); - QStringList extracted; - qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; - if (!zip->goToFirstFile()) - { - qWarning() << "Failed to seek to first file in zip"; - return QStringList(); - } - do - { - QString name = zip->getCurrentFileName(); - if(!name.startsWith(subdir)) - { - continue; - } - name.remove(0, subdir.size()); - QString absFilePath = directory.absoluteFilePath(name); - if(name.isEmpty()) - { - absFilePath += "/"; - } - if (!JlCompress::extractFile(zip, "", absFilePath)) - { - qWarning() << "Failed to extract file" << name << "to" << absFilePath; - JlCompress::removeFile(extracted); - return QStringList(); - } - extracted.append(absFilePath); - qDebug() << "Extracted file" << name; - } while (zip->goToNextFile()); - return extracted; + QDir directory(target); + QStringList extracted; + qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target; + if (!zip->goToFirstFile()) + { + qWarning() << "Failed to seek to first file in zip"; + return QStringList(); + } + do + { + QString name = zip->getCurrentFileName(); + if(!name.startsWith(subdir)) + { + continue; + } + name.remove(0, subdir.size()); + QString absFilePath = directory.absoluteFilePath(name); + if(name.isEmpty()) + { + absFilePath += "/"; + } + if (!JlCompress::extractFile(zip, "", absFilePath)) + { + qWarning() << "Failed to extract file" << name << "to" << absFilePath; + JlCompress::removeFile(extracted); + return QStringList(); + } + extracted.append(absFilePath); + qDebug() << "Extracted file" << name; + } while (zip->goToNextFile()); + return extracted; } // ours QStringList MMCZip::extractDir(QString fileCompressed, QString dir) { - QuaZip zip(fileCompressed); - if (!zip.open(QuaZip::mdUnzip)) - { - return {}; - } - return MMCZip::extractSubDir(&zip, "", dir); + QuaZip zip(fileCompressed); + if (!zip.open(QuaZip::mdUnzip)) + { + return {}; + } + return MMCZip::extractSubDir(&zip, "", dir); } diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h index 68094b2c..85ac7802 100644 --- a/api/logic/MMCZip.h +++ b/api/logic/MMCZip.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ #include #include #include -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include #include "multimc_logic_export.h" @@ -28,44 +28,44 @@ namespace MMCZip { - /** - * Merge two zip files, using a filter function - */ - bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, - const JlCompress::FilterFunction filter = nullptr); + /** + * Merge two zip files, using a filter function + */ + bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet &contained, + const JlCompress::FilterFunction filter = nullptr); - /** - * take a source jar, add mods to it, resulting in target jar - */ - bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); + /** + * take a source jar, add mods to it, resulting in target jar + */ + bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList& mods); - /** - * Find a single file in archive by file name (not path) - * - * \return the path prefix where the file is - */ - QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); + /** + * Find a single file in archive by file name (not path) + * + * \return the path prefix where the file is + */ + QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString("")); - /** - * Find a multiple files of the same name in archive by file name - * If a file is found in a path, no deeper paths are searched - * - * \return true if anything was found - */ - bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); + /** + * Find a multiple files of the same name in archive by file name + * If a file is found in a path, no deeper paths are searched + * + * \return true if anything was found + */ + bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString()); - /** - * Extract a subdirectory from an archive - */ - QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); + /** + * Extract a subdirectory from an archive + */ + QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); - /** - * Extract a whole archive. - * - * \param fileCompressed The name of the archive. - * \param dir The directory to extract to, the current directory if left empty. - * \return The list of the full paths of the files extracted, empty on failure. - */ - QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir); + /** + * Extract a whole archive. + * + * \param fileCompressed The name of the archive. + * \param dir The directory to extract to, the current directory if left empty. + * \return The list of the full paths of the files extracted, empty on failure. + */ + QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir); } diff --git a/api/logic/MessageLevel.cpp b/api/logic/MessageLevel.cpp index a5191290..0a2cd23d 100644 --- a/api/logic/MessageLevel.cpp +++ b/api/logic/MessageLevel.cpp @@ -2,35 +2,35 @@ MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) { - if (levelName == "MultiMC") - return MessageLevel::MultiMC; - else if (levelName == "Debug") - return MessageLevel::Debug; - else if (levelName == "Info") - return MessageLevel::Info; - else if (levelName == "Message") - return MessageLevel::Message; - else if (levelName == "Warning") - return MessageLevel::Warning; - else if (levelName == "Error") - return MessageLevel::Error; - else if (levelName == "Fatal") - return MessageLevel::Fatal; - // Skip PrePost, it's not exposed to !![]! - // Also skip StdErr and StdOut - else - return MessageLevel::Unknown; + if (levelName == "MultiMC") + return MessageLevel::MultiMC; + else if (levelName == "Debug") + return MessageLevel::Debug; + else if (levelName == "Info") + return MessageLevel::Info; + else if (levelName == "Message") + return MessageLevel::Message; + else if (levelName == "Warning") + return MessageLevel::Warning; + else if (levelName == "Error") + return MessageLevel::Error; + else if (levelName == "Fatal") + return MessageLevel::Fatal; + // Skip PrePost, it's not exposed to !![]! + // Also skip StdErr and StdOut + else + return MessageLevel::Unknown; } MessageLevel::Enum MessageLevel::fromLine(QString &line) { - // Level prefix - int endmark = line.indexOf("]!"); - if (line.startsWith("!![") && endmark != -1) - { - auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); - line = line.mid(endmark + 2); - return level; - } - return MessageLevel::Unknown; + // Level prefix + int endmark = line.indexOf("]!"); + if (line.startsWith("!![") && endmark != -1) + { + auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); + line = line.mid(endmark + 2); + return level; + } + return MessageLevel::Unknown; } diff --git a/api/logic/MessageLevel.h b/api/logic/MessageLevel.h index 0128148d..f19048e3 100644 --- a/api/logic/MessageLevel.h +++ b/api/logic/MessageLevel.h @@ -10,16 +10,16 @@ namespace MessageLevel { enum Enum { - Unknown, /**< No idea what this is or where it came from */ - StdOut, /**< Undetermined stderr messages */ - StdErr, /**< Undetermined stdout messages */ - MultiMC, /**< MultiMC Messages */ - Debug, /**< Debug Messages */ - Info, /**< Info Messages */ - Message, /**< Standard Messages */ - Warning, /**< Warnings */ - Error, /**< Errors */ - Fatal, /**< Fatal Errors */ + Unknown, /**< No idea what this is or where it came from */ + StdOut, /**< Undetermined stderr messages */ + StdErr, /**< Undetermined stdout messages */ + MultiMC, /**< MultiMC Messages */ + Debug, /**< Debug Messages */ + Info, /**< Info Messages */ + Message, /**< Standard Messages */ + Warning, /**< Warnings */ + Error, /**< Errors */ + Fatal, /**< Fatal Errors */ }; MessageLevel::Enum getLevel(const QString &levelName); diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h index 64965277..e9ba1a13 100644 --- a/api/logic/NullInstance.h +++ b/api/logic/NullInstance.h @@ -1,77 +1,76 @@ #pragma once #include "BaseInstance.h" +#include "launch/LaunchTask.h" class NullInstance: public BaseInstance { + Q_OBJECT public: - NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) - :BaseInstance(globalSettings, settings, rootDir) - { - setVersionBroken(true); - } - virtual ~NullInstance() {}; - virtual void init() override - { - } - virtual void saveNow() override - { - } - virtual QString getStatusbarDescription() override - { - return tr("Unknown instance type"); - }; - virtual QSet< QString > traits() const override - { - return {}; - }; - virtual QString instanceConfigFolder() const override - { - return instanceRoot(); - }; - virtual std::shared_ptr createLaunchTask(AuthSessionPtr) override - { - return nullptr; - } - virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override - { - return nullptr; - } - virtual QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - virtual QMap getVariables() const override - { - return QMap(); - } - virtual IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - virtual QString getLogFileRoot() override - { - return instanceRoot(); - } - virtual QString typeName() const override - { - return "Null"; - } - bool canExport() const override - { - return false; - } - bool canEdit() const override - { - return false; - } - bool canLaunch() const override - { - return false; - } - QStringList verboseDescription(AuthSessionPtr session) override - { - QStringList out; - out << "Null instance - placeholder."; - return out; - } + NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) + :BaseInstance(globalSettings, settings, rootDir) + { + setVersionBroken(true); + } + virtual ~NullInstance() {}; + void saveNow() override + { + } + QString getStatusbarDescription() override + { + return tr("Unknown instance type"); + }; + QSet< QString > traits() const override + { + return {}; + }; + QString instanceConfigFolder() const override + { + return instanceRoot(); + }; + shared_qobject_ptr createLaunchTask(AuthSessionPtr) override + { + return nullptr; + } + shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override + { + return nullptr; + } + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap getVariables() const override + { + return QMap(); + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override + { + return instanceRoot(); + } + QString typeName() const override + { + return "Null"; + } + bool canExport() const override + { + return false; + } + bool canEdit() const override + { + return false; + } + bool canLaunch() const override + { + return false; + } + QStringList verboseDescription(AuthSessionPtr session) override + { + QStringList out; + out << "Null instance - placeholder."; + return out; + } }; diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h index 978710f0..33c9d364 100644 --- a/api/logic/ProblemProvider.h +++ b/api/logic/ProblemProvider.h @@ -4,46 +4,46 @@ enum class ProblemSeverity { - None, - Warning, - Error + None, + Warning, + Error }; struct PatchProblem { - ProblemSeverity m_severity; - QString m_description; + ProblemSeverity m_severity; + QString m_description; }; class MULTIMC_LOGIC_EXPORT ProblemProvider { public: - virtual ~ProblemProvider() {}; - virtual const QList getProblems() const = 0; - virtual ProblemSeverity getProblemSeverity() const = 0; + virtual ~ProblemProvider() {}; + virtual const QList getProblems() const = 0; + virtual ProblemSeverity getProblemSeverity() const = 0; }; class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider { public: - const QList getProblems() const override - { - return m_problems; - } - ProblemSeverity getProblemSeverity() const override - { - return m_problemSeverity; - } - virtual void addProblem(ProblemSeverity severity, const QString &description) - { - if(severity > m_problemSeverity) - { - m_problemSeverity = severity; - } - m_problems.append({severity, description}); - } + const QList getProblems() const override + { + return m_problems; + } + ProblemSeverity getProblemSeverity() const override + { + return m_problemSeverity; + } + virtual void addProblem(ProblemSeverity severity, const QString &description) + { + if(severity > m_problemSeverity) + { + m_problemSeverity = severity; + } + m_problems.append({severity, description}); + } private: - QList m_problems; - ProblemSeverity m_problemSeverity = ProblemSeverity::None; + QList m_problems; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/api/logic/QObjectPtr.h b/api/logic/QObjectPtr.h index 4a42f728..0ff51136 100644 --- a/api/logic/QObjectPtr.h +++ b/api/logic/QObjectPtr.h @@ -8,10 +8,10 @@ namespace details { struct DeleteQObjectLater { - void operator()(QObject *obj) const - { - obj->deleteLater(); - } + void operator()(QObject *obj) const + { + obj->deleteLater(); + } }; } /** @@ -28,56 +28,56 @@ template class shared_qobject_ptr { public: - shared_qobject_ptr(){} - shared_qobject_ptr(T * wrap) - { - reset(wrap); - } - shared_qobject_ptr(const shared_qobject_ptr& other) - { - m_ptr = other.m_ptr; - } - template - shared_qobject_ptr(const shared_qobject_ptr &other) - { - m_ptr = other.unwrap(); - } + shared_qobject_ptr(){} + shared_qobject_ptr(T * wrap) + { + reset(wrap); + } + shared_qobject_ptr(const shared_qobject_ptr& other) + { + m_ptr = other.m_ptr; + } + template + shared_qobject_ptr(const shared_qobject_ptr &other) + { + m_ptr = other.unwrap(); + } public: - void reset(T * wrap) - { - using namespace std::placeholders; - m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); - } - void reset(const shared_qobject_ptr &other) - { - m_ptr = other.m_ptr; - } - void reset() - { - m_ptr.reset(); - } - T * get() const - { - return m_ptr.get(); - } - T * operator->() const - { - return m_ptr.get(); - } - T & operator*() const - { - return *m_ptr.get(); - } - operator bool() const - { - return m_ptr.get() != nullptr; - } - const std::shared_ptr unwrap() const - { - return m_ptr; - } + void reset(T * wrap) + { + using namespace std::placeholders; + m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); + } + void reset(const shared_qobject_ptr &other) + { + m_ptr = other.m_ptr; + } + void reset() + { + m_ptr.reset(); + } + T * get() const + { + return m_ptr.get(); + } + T * operator->() const + { + return m_ptr.get(); + } + T & operator*() const + { + return *m_ptr.get(); + } + operator bool() const + { + return m_ptr.get() != nullptr; + } + const std::shared_ptr unwrap() const + { + return m_ptr; + } private: - std::shared_ptr m_ptr; + std::shared_ptr m_ptr; }; diff --git a/api/logic/RWStorage.h b/api/logic/RWStorage.h index 4afbd96c..5d792367 100644 --- a/api/logic/RWStorage.h +++ b/api/logic/RWStorage.h @@ -5,59 +5,59 @@ template class RWStorage { public: - void add(K key, V value) - { - QWriteLocker l(&lock); - cache[key] = value; - stale_entries.remove(key); - } - V get(K key) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - return cache[key]; - } - else return V(); - } - bool get(K key, V& value) - { - QReadLocker l(&lock); - if(cache.contains(key)) - { - value = cache[key]; - return true; - } - else return false; - } - bool has(K key) - { - QReadLocker l(&lock); - return cache.contains(key); - } - bool stale(K key) - { - QReadLocker l(&lock); - if(!cache.contains(key)) - return true; - return stale_entries.contains(key); - } - void setStale(K key) - { - QWriteLocker l(&lock); - if(cache.contains(key)) - { - stale_entries.insert(key); - } - } - void clear() - { - QWriteLocker l(&lock); - cache.clear(); - stale_entries.clear(); - } + void add(K key, V value) + { + QWriteLocker l(&lock); + cache[key] = value; + stale_entries.remove(key); + } + V get(K key) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + return cache[key]; + } + else return V(); + } + bool get(K key, V& value) + { + QReadLocker l(&lock); + if(cache.contains(key)) + { + value = cache[key]; + return true; + } + else return false; + } + bool has(K key) + { + QReadLocker l(&lock); + return cache.contains(key); + } + bool stale(K key) + { + QReadLocker l(&lock); + if(!cache.contains(key)) + return true; + return stale_entries.contains(key); + } + void setStale(K key) + { + QWriteLocker l(&lock); + if(cache.contains(key)) + { + stale_entries.insert(key); + } + } + void clear() + { + QWriteLocker l(&lock); + cache.clear(); + stale_entries.clear(); + } private: - QReadWriteLock lock; - QMap cache; - QSet stale_entries; + QReadWriteLock lock; + QMap cache; + QSet stale_entries; }; diff --git a/api/logic/RecursiveFileSystemWatcher.cpp b/api/logic/RecursiveFileSystemWatcher.cpp index 59c3f0f0..b7417cdf 100644 --- a/api/logic/RecursiveFileSystemWatcher.cpp +++ b/api/logic/RecursiveFileSystemWatcher.cpp @@ -4,108 +4,108 @@ #include RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent) - : QObject(parent), m_watcher(new QFileSystemWatcher(this)) + : QObject(parent), m_watcher(new QFileSystemWatcher(this)) { - connect(m_watcher, &QFileSystemWatcher::fileChanged, this, - &RecursiveFileSystemWatcher::fileChange); - connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, - &RecursiveFileSystemWatcher::directoryChange); + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, + &RecursiveFileSystemWatcher::fileChange); + connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, + &RecursiveFileSystemWatcher::directoryChange); } void RecursiveFileSystemWatcher::setRootDir(const QDir &root) { - bool wasEnabled = m_isEnabled; - disable(); - m_root = root; - setFiles(scanRecursive(m_root)); - if (wasEnabled) - { - enable(); - } + bool wasEnabled = m_isEnabled; + disable(); + m_root = root; + setFiles(scanRecursive(m_root)); + if (wasEnabled) + { + enable(); + } } void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles) { - bool wasEnabled = m_isEnabled; - disable(); - m_watchFiles = watchFiles; - if (wasEnabled) - { - enable(); - } + bool wasEnabled = m_isEnabled; + disable(); + m_watchFiles = watchFiles; + if (wasEnabled) + { + enable(); + } } void RecursiveFileSystemWatcher::enable() { - if (m_isEnabled) - { - return; - } - Q_ASSERT(m_root != QDir::root()); - addFilesToWatcherRecursive(m_root); - m_isEnabled = true; + if (m_isEnabled) + { + return; + } + Q_ASSERT(m_root != QDir::root()); + addFilesToWatcherRecursive(m_root); + m_isEnabled = true; } void RecursiveFileSystemWatcher::disable() { - if (!m_isEnabled) - { - return; - } - m_isEnabled = false; - m_watcher->removePaths(m_watcher->files()); - m_watcher->removePaths(m_watcher->directories()); + if (!m_isEnabled) + { + return; + } + m_isEnabled = false; + m_watcher->removePaths(m_watcher->files()); + m_watcher->removePaths(m_watcher->directories()); } void RecursiveFileSystemWatcher::setFiles(const QStringList &files) { - if (files != m_files) - { - m_files = files; - emit filesChanged(); - } + if (files != m_files) + { + m_files = files; + emit filesChanged(); + } } void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir) { - m_watcher->addPath(dir.absolutePath()); - for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) - { - addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); - } - if (m_watchFiles) - { - for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) - { - m_watcher->addPath(info.absoluteFilePath()); - } - } + m_watcher->addPath(dir.absolutePath()); + for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) + { + addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); + } + if (m_watchFiles) + { + for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) + { + m_watcher->addPath(info.absoluteFilePath()); + } + } } QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory) { - QStringList ret; - if(!m_matcher) - { - return {}; - } - for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) - { - ret.append(scanRecursive(directory.absoluteFilePath(dir))); - } - for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) - { - auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); - if (m_matcher->matches(relPath)) - { - ret.append(relPath); - } - } - return ret; + QStringList ret; + if(!m_matcher) + { + return {}; + } + for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) + { + ret.append(scanRecursive(directory.absoluteFilePath(dir))); + } + for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) + { + auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); + if (m_matcher->matches(relPath)) + { + ret.append(relPath); + } + } + return ret; } void RecursiveFileSystemWatcher::fileChange(const QString &path) { - emit fileChanged(path); + emit fileChanged(path); } void RecursiveFileSystemWatcher::directoryChange(const QString &path) { - setFiles(scanRecursive(m_root)); + setFiles(scanRecursive(m_root)); } diff --git a/api/logic/RecursiveFileSystemWatcher.h b/api/logic/RecursiveFileSystemWatcher.h index 07bce0b9..c9c39f49 100644 --- a/api/logic/RecursiveFileSystemWatcher.h +++ b/api/logic/RecursiveFileSystemWatcher.h @@ -8,56 +8,56 @@ class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject { - Q_OBJECT + Q_OBJECT public: - RecursiveFileSystemWatcher(QObject *parent); - - void setRootDir(const QDir &root); - QDir rootDir() const - { - return m_root; - } - - // WARNING: setting this to true may be bad for performance - void setWatchFiles(const bool watchFiles); - bool watchFiles() const - { - return m_watchFiles; - } - - void setMatcher(IPathMatcher::Ptr matcher) - { - m_matcher = matcher; - } - - QStringList files() const - { - return m_files; - } + RecursiveFileSystemWatcher(QObject *parent); + + void setRootDir(const QDir &root); + QDir rootDir() const + { + return m_root; + } + + // WARNING: setting this to true may be bad for performance + void setWatchFiles(const bool watchFiles); + bool watchFiles() const + { + return m_watchFiles; + } + + void setMatcher(IPathMatcher::Ptr matcher) + { + m_matcher = matcher; + } + + QStringList files() const + { + return m_files; + } signals: - void filesChanged(); - void fileChanged(const QString &path); + void filesChanged(); + void fileChanged(const QString &path); public slots: - void enable(); - void disable(); + void enable(); + void disable(); private: - QDir m_root; - bool m_watchFiles = false; - bool m_isEnabled = false; - IPathMatcher::Ptr m_matcher; + QDir m_root; + bool m_watchFiles = false; + bool m_isEnabled = false; + IPathMatcher::Ptr m_matcher; - QFileSystemWatcher *m_watcher; + QFileSystemWatcher *m_watcher; - QStringList m_files; - void setFiles(const QStringList &files); + QStringList m_files; + void setFiles(const QStringList &files); - void addFilesToWatcherRecursive(const QDir &dir); - QStringList scanRecursive(const QDir &dir); + void addFilesToWatcherRecursive(const QDir &dir); + QStringList scanRecursive(const QDir &dir); private slots: - void fileChange(const QString &path); - void directoryChange(const QString &path); + void fileChange(const QString &path); + void directoryChange(const QString &path); }; diff --git a/api/logic/SeparatorPrefixTree.h b/api/logic/SeparatorPrefixTree.h index fd149af0..7a841cb7 100644 --- a/api/logic/SeparatorPrefixTree.h +++ b/api/logic/SeparatorPrefixTree.h @@ -7,292 +7,292 @@ template class SeparatorPrefixTree { public: - SeparatorPrefixTree(QStringList paths) - { - insert(paths); - } + SeparatorPrefixTree(QStringList paths) + { + insert(paths); + } - SeparatorPrefixTree(bool contained = false) - { - m_contained = contained; - } + SeparatorPrefixTree(bool contained = false) + { + m_contained = contained; + } - void insert(QStringList paths) - { - for(auto &path: paths) - { - insert(path); - } - } + void insert(QStringList paths) + { + for(auto &path: paths) + { + insert(path); + } + } - /// insert an exact path into the tree - SeparatorPrefixTree & insert(QString path) - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - children[path] = SeparatorPrefixTree(true); - return children[path]; - } - else - { - auto prefix = path.left(sepIndex); - if(!children.contains(prefix)) - { - children[prefix] = SeparatorPrefixTree(false); - } - return children[prefix].insert(path.mid(sepIndex + 1)); - } - } + /// insert an exact path into the tree + SeparatorPrefixTree & insert(QString path) + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + children[path] = SeparatorPrefixTree(true); + return children[path]; + } + else + { + auto prefix = path.left(sepIndex); + if(!children.contains(prefix)) + { + children[prefix] = SeparatorPrefixTree(false); + } + return children[prefix].insert(path.mid(sepIndex + 1)); + } + } - /// is the path fully contained in the tree? - bool contains(QString path) const - { - auto node = find(path); - return node != nullptr; - } + /// is the path fully contained in the tree? + bool contains(QString path) const + { + auto node = find(path); + return node != nullptr; + } - /// does the tree cover a path? That means the prefix of the path is contained in the tree - bool covers(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return true; - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return (*found).covers(QString()); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).covers(path.mid(sepIndex + 1)); - } - } + /// does the tree cover a path? That means the prefix of the path is contained in the tree + bool covers(QString path) const + { + // if we found some valid node, it's good enough. the tree covers the path + if(m_contained) + { + return true; + } + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return false; + } + return (*found).covers(QString()); + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return false; + } + return (*found).covers(path.mid(sepIndex + 1)); + } + } - /// return the contained path that covers the path specified - QString cover(QString path) const - { - // if we found some valid node, it's good enough. the tree covers the path - if(m_contained) - { - return QString(""); - } - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(QString()); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return path; - return path + Tseparator + nested; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return QString(); - } - auto nested = (*found).cover(path.mid(sepIndex + 1)); - if(nested.isNull()) - { - return nested; - } - if(nested.isEmpty()) - return prefix; - return prefix + Tseparator + nested; - } - } + /// return the contained path that covers the path specified + QString cover(QString path) const + { + // if we found some valid node, it's good enough. the tree covers the path + if(m_contained) + { + return QString(""); + } + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return QString(); + } + auto nested = (*found).cover(QString()); + if(nested.isNull()) + { + return nested; + } + if(nested.isEmpty()) + return path; + return path + Tseparator + nested; + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return QString(); + } + auto nested = (*found).cover(path.mid(sepIndex + 1)); + if(nested.isNull()) + { + return nested; + } + if(nested.isEmpty()) + return prefix; + return prefix + Tseparator + nested; + } + } - /// Does the path-specified node exist in the tree? It does not have to be contained. - bool exists(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return false; - } - return true; - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return false; - } - return (*found).exists(path.mid(sepIndex + 1)); - } - } + /// Does the path-specified node exist in the tree? It does not have to be contained. + bool exists(QString path) const + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return false; + } + return true; + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return false; + } + return (*found).exists(path.mid(sepIndex + 1)); + } + } - /// find a node in the tree by name - const SeparatorPrefixTree * find(QString path) const - { - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - auto found = children.find(path); - if(found == children.end()) - { - return nullptr; - } - return &(*found); - } - else - { - auto prefix = path.left(sepIndex); - auto found = children.find(prefix); - if(found == children.end()) - { - return nullptr; - } - return (*found).find(path.mid(sepIndex + 1)); - } - } + /// find a node in the tree by name + const SeparatorPrefixTree * find(QString path) const + { + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + auto found = children.find(path); + if(found == children.end()) + { + return nullptr; + } + return &(*found); + } + else + { + auto prefix = path.left(sepIndex); + auto found = children.find(prefix); + if(found == children.end()) + { + return nullptr; + } + return (*found).find(path.mid(sepIndex + 1)); + } + } - /// is this a leaf node? - bool leaf() const - { - return children.isEmpty(); - } + /// is this a leaf node? + bool leaf() const + { + return children.isEmpty(); + } - /// is this node actually contained in the tree, or is it purely structural? - bool contained() const - { - return m_contained; - } + /// is this node actually contained in the tree, or is it purely structural? + bool contained() const + { + return m_contained; + } - /// Remove a path from the tree - bool remove(QString path) - { - return removeInternal(path) != Failed; - } + /// Remove a path from the tree + bool remove(QString path) + { + return removeInternal(path) != Failed; + } - /// Clear all children of this node tree node - void clear() - { - children.clear(); - } + /// Clear all children of this node tree node + void clear() + { + children.clear(); + } - QStringList toStringList() const - { - QStringList collected; - // collecting these is more expensive. - auto iter = children.begin(); - while(iter != children.end()) - { - QStringList list = iter.value().toStringList(); - for(int i = 0; i < list.size(); i++) - { - list[i] = iter.key() + Tseparator + list[i]; - } - collected.append(list); - if((*iter).m_contained) - { - collected.append(iter.key()); - } - iter++; - } - return collected; - } + QStringList toStringList() const + { + QStringList collected; + // collecting these is more expensive. + auto iter = children.begin(); + while(iter != children.end()) + { + QStringList list = iter.value().toStringList(); + for(int i = 0; i < list.size(); i++) + { + list[i] = iter.key() + Tseparator + list[i]; + } + collected.append(list); + if((*iter).m_contained) + { + collected.append(iter.key()); + } + iter++; + } + return collected; + } private: - enum Removal - { - Failed, - Succeeded, - HasChildren - }; - Removal removeInternal(QString path = QString()) - { - if(path.isEmpty()) - { - if(!m_contained) - { - // remove all children - we are removing a prefix - clear(); - return Succeeded; - } - m_contained = false; - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - Removal remStatus = Failed; - QString childToRemove; - auto sepIndex = path.indexOf(Tseparator); - if(sepIndex == -1) - { - childToRemove = path; - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(); - } - else - { - childToRemove = path.left(sepIndex); - auto found = children.find(childToRemove); - if(found == children.end()) - { - return Failed; - } - remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); - } - switch (remStatus) - { - case Failed: - case HasChildren: - { - return remStatus; - } - case Succeeded: - { - children.remove(childToRemove); - if(m_contained) - { - return HasChildren; - } - if(children.size()) - { - return HasChildren; - } - return Succeeded; - } - } - return Failed; - } + enum Removal + { + Failed, + Succeeded, + HasChildren + }; + Removal removeInternal(QString path = QString()) + { + if(path.isEmpty()) + { + if(!m_contained) + { + // remove all children - we are removing a prefix + clear(); + return Succeeded; + } + m_contained = false; + if(children.size()) + { + return HasChildren; + } + return Succeeded; + } + Removal remStatus = Failed; + QString childToRemove; + auto sepIndex = path.indexOf(Tseparator); + if(sepIndex == -1) + { + childToRemove = path; + auto found = children.find(childToRemove); + if(found == children.end()) + { + return Failed; + } + remStatus = (*found).removeInternal(); + } + else + { + childToRemove = path.left(sepIndex); + auto found = children.find(childToRemove); + if(found == children.end()) + { + return Failed; + } + remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); + } + switch (remStatus) + { + case Failed: + case HasChildren: + { + return remStatus; + } + case Succeeded: + { + children.remove(childToRemove); + if(m_contained) + { + return HasChildren; + } + if(children.size()) + { + return HasChildren; + } + return Succeeded; + } + } + return Failed; + } private: - QMap> children; - bool m_contained = false; + QMap> children; + bool m_contained = false; }; diff --git a/api/logic/Usable.h b/api/logic/Usable.h index 1168be4d..83dd083d 100644 --- a/api/logic/Usable.h +++ b/api/logic/Usable.h @@ -12,27 +12,27 @@ class Usable; */ class Usable { - friend class UseLock; + friend class UseLock; public: - std::size_t useCount() - { - return m_useCount; - } - bool isInUse() - { - return m_useCount > 0; - } + std::size_t useCount() + { + return m_useCount; + } + bool isInUse() + { + return m_useCount > 0; + } protected: - virtual void decrementUses() - { - m_useCount--; - } - virtual void incrementUses() - { - m_useCount++; - } + virtual void decrementUses() + { + m_useCount--; + } + virtual void incrementUses() + { + m_useCount++; + } private: - std::size_t m_useCount = 0; + std::size_t m_useCount = 0; }; /** @@ -43,16 +43,16 @@ private: class UseLock { public: - UseLock(std::shared_ptr usable) - : m_usable(usable) - { - // this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate. - m_usable->incrementUses(); - } - ~UseLock() - { - m_usable->decrementUses(); - } + UseLock(std::shared_ptr usable) + : m_usable(usable) + { + // this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate. + m_usable->incrementUses(); + } + ~UseLock() + { + m_usable->decrementUses(); + } private: - std::shared_ptr m_usable; + std::shared_ptr m_usable; }; diff --git a/api/logic/Version.cpp b/api/logic/Version.cpp index 2c83374f..42eac669 100644 --- a/api/logic/Version.cpp +++ b/api/logic/Version.cpp @@ -7,79 +7,79 @@ Version::Version(const QString &str) : m_string(str) { - parse(); + parse(); } bool Version::operator<(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 < sec2; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return sec1 < sec2; + } + } - return false; + return false; } bool Version::operator<=(const Version &other) const { - return *this < other || *this == other; + return *this < other || *this == other; } bool Version::operator>(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return sec1 > sec2; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return sec1 > sec2; + } + } - return false; + return false; } bool Version::operator>=(const Version &other) const { - return *this > other || *this == other; + return *this > other || *this == other; } bool Version::operator==(const Version &other) const { - const int size = qMax(m_sections.size(), other.m_sections.size()); - for (int i = 0; i < size; ++i) - { - const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); - const Section sec2 = - (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); - if (sec1 != sec2) - { - return false; - } - } + const int size = qMax(m_sections.size(), other.m_sections.size()); + for (int i = 0; i < size; ++i) + { + const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); + const Section sec2 = + (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); + if (sec1 != sec2) + { + return false; + } + } - return true; + return true; } bool Version::operator!=(const Version &other) const { - return !operator==(other); + return !operator==(other); } void Version::parse() { - m_sections.clear(); + m_sections.clear(); - // FIXME: this is bad. versions can contain a lot more separators... - QStringList parts = m_string.split('.'); + // FIXME: this is bad. versions can contain a lot more separators... + QStringList parts = m_string.split('.'); - for (const auto part : parts) - { - m_sections.append(Section(part)); - } + for (const auto part : parts) + { + m_sections.append(Section(part)); + } } diff --git a/api/logic/Version.h b/api/logic/Version.h index 08dfb6e9..c5d93081 100644 --- a/api/logic/Version.h +++ b/api/logic/Version.h @@ -7,100 +7,101 @@ class QUrl; -struct MULTIMC_LOGIC_EXPORT Version +class MULTIMC_LOGIC_EXPORT Version { - Version(const QString &str); - Version() {} +public: + Version(const QString &str); + Version() {} - bool operator<(const Version &other) const; - bool operator<=(const Version &other) const; - bool operator>(const Version &other) const; - bool operator>=(const Version &other) const; - bool operator==(const Version &other) const; - bool operator!=(const Version &other) const; + bool operator<(const Version &other) const; + bool operator<=(const Version &other) const; + bool operator>(const Version &other) const; + bool operator>=(const Version &other) const; + bool operator==(const Version &other) const; + bool operator!=(const Version &other) const; - QString toString() const - { - return m_string; - } + QString toString() const + { + return m_string; + } private: - QString m_string; - struct Section - { - explicit Section(const QString &fullString) - { - m_fullString = fullString; - int cutoff = m_fullString.size(); - for(int i = 0; i < m_fullString.size(); i++) - { - if(!m_fullString[i].isDigit()) - { - cutoff = i; - break; - } - } - auto numPart = m_fullString.leftRef(cutoff); - if(numPart.size()) - { - numValid = true; - m_numPart = numPart.toInt(); - } - auto stringPart = m_fullString.midRef(cutoff); - if(stringPart.size()) - { - m_stringPart = stringPart.toString(); - } - } - explicit Section() {} - bool numValid = false; - int m_numPart = 0; - QString m_stringPart; - QString m_fullString; + QString m_string; + struct Section + { + explicit Section(const QString &fullString) + { + m_fullString = fullString; + int cutoff = m_fullString.size(); + for(int i = 0; i < m_fullString.size(); i++) + { + if(!m_fullString[i].isDigit()) + { + cutoff = i; + break; + } + } + auto numPart = m_fullString.leftRef(cutoff); + if(numPart.size()) + { + numValid = true; + m_numPart = numPart.toInt(); + } + auto stringPart = m_fullString.midRef(cutoff); + if(stringPart.size()) + { + m_stringPart = stringPart.toString(); + } + } + explicit Section() {} + bool numValid = false; + int m_numPart = 0; + QString m_stringPart; + QString m_fullString; - inline bool operator!=(const Section &other) const - { - if(numValid && other.numValid) - { - return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; - } - else - { - return m_fullString != other.m_fullString; - } - } - inline bool operator<(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart < other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString < other.m_fullString; - } - } - inline bool operator>(const Section &other) const - { - if(numValid && other.numValid) - { - if(m_numPart > other.m_numPart) - return true; - if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) - return true; - return false; - } - else - { - return m_fullString > other.m_fullString; - } - } - }; - QList
m_sections; + inline bool operator!=(const Section &other) const + { + if(numValid && other.numValid) + { + return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; + } + else + { + return m_fullString != other.m_fullString; + } + } + inline bool operator<(const Section &other) const + { + if(numValid && other.numValid) + { + if(m_numPart < other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString < other.m_fullString; + } + } + inline bool operator>(const Section &other) const + { + if(numValid && other.numValid) + { + if(m_numPart > other.m_numPart) + return true; + if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) + return true; + return false; + } + else + { + return m_fullString > other.m_fullString; + } + } + }; + QList
m_sections; - void parse(); + void parse(); }; diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp index b8e05768..9c222a70 100644 --- a/api/logic/Version_test.cpp +++ b/api/logic/Version_test.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,64 +20,64 @@ class ModUtilsTest : public QObject { - Q_OBJECT - void setupVersions() - { - QTest::addColumn("first"); - QTest::addColumn("second"); - QTest::addColumn("lessThan"); - QTest::addColumn("equal"); + Q_OBJECT + void setupVersions() + { + QTest::addColumn("first"); + QTest::addColumn("second"); + QTest::addColumn("lessThan"); + QTest::addColumn("equal"); - QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; - QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; - QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; + QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; + QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; + QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; + QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; - QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; - QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; - QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; - QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; - QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; - QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; - QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; + QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; + QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; + QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; + QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; + QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; + QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; + QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; - QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; - QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; - QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; - QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; - } + QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; + QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; + QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; + QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; + QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; + QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; + } private slots: - void initTestCase() - { + void initTestCase() + { - } - void cleanupTestCase() - { + } + void cleanupTestCase() + { - } + } - void test_versionCompare_data() - { - setupVersions(); - } - void test_versionCompare() - { - QFETCH(QString, first); - QFETCH(QString, second); - QFETCH(bool, lessThan); - QFETCH(bool, equal); + void test_versionCompare_data() + { + setupVersions(); + } + void test_versionCompare() + { + QFETCH(QString, first); + QFETCH(QString, second); + QFETCH(bool, lessThan); + QFETCH(bool, equal); - const auto v1 = Version(first); - const auto v2 = Version(second); + const auto v1 = Version(first); + const auto v2 = Version(second); - QCOMPARE(v1 < v2, lessThan); - QCOMPARE(v1 > v2, !lessThan && !equal); - QCOMPARE(v1 == v2, equal); - } + QCOMPARE(v1 < v2, lessThan); + QCOMPARE(v1 > v2, !lessThan && !equal); + QCOMPARE(v1 == v2, equal); + } }; QTEST_GUILESS_MAIN(ModUtilsTest) diff --git a/api/logic/WatchLock.h b/api/logic/WatchLock.h new file mode 100644 index 00000000..3e08b413 --- /dev/null +++ b/api/logic/WatchLock.h @@ -0,0 +1,20 @@ + +#pragma once + +#include +#include + +struct WatchLock +{ + WatchLock(QFileSystemWatcher * watcher, const QString& directory) + : m_watcher(watcher), m_directory(directory) + { + m_watcher->removePath(m_directory); + } + ~WatchLock() + { + m_watcher->addPath(m_directory); + } + QFileSystemWatcher * m_watcher; + QString m_directory; +}; diff --git a/api/logic/icons/IIconList.h b/api/logic/icons/IIconList.h index e6c16d50..9a3fe022 100644 --- a/api/logic/icons/IIconList.h +++ b/api/logic/icons/IIconList.h @@ -6,21 +6,21 @@ enum IconType : unsigned { - Builtin, - Transient, - FileBased, - ICONS_TOTAL, - ToBeDeleted + Builtin, + Transient, + FileBased, + ICONS_TOTAL, + ToBeDeleted }; class MULTIMC_LOGIC_EXPORT IIconList { public: - virtual ~IIconList(); - virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0; - virtual bool deleteIcon(const QString &key) = 0; - virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0; - virtual bool iconFileExists(const QString &key) const = 0; - virtual void installIcons(const QStringList &iconFiles) = 0; - virtual void installIcon(const QString &file, const QString &name) = 0; + virtual ~IIconList(); + virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0; + virtual bool deleteIcon(const QString &key) = 0; + virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0; + virtual bool iconFileExists(const QString &key) const = 0; + virtual void installIcons(const QStringList &iconFiles) = 0; + virtual void installIcon(const QString &file, const QString &name) = 0; }; diff --git a/api/logic/icons/IconUtils.cpp b/api/logic/icons/IconUtils.cpp new file mode 100644 index 00000000..bf530c16 --- /dev/null +++ b/api/logic/icons/IconUtils.cpp @@ -0,0 +1,62 @@ +#include "IconUtils.h" + +#include "FileSystem.h" +#include + +#include + +namespace { +std::array validIconExtensions = {{ + "svg", + "png", + "ico", + "gif", + "jpg", + "jpeg" +}}; +} + +namespace IconUtils{ + +QString findBestIconIn(const QString &folder, const QString & iconKey) { + int best_found = validIconExtensions.size(); + QString best_filename; + + QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + it.next(); + auto fileInfo = it.fileInfo(); + + if(fileInfo.completeBaseName() != iconKey) + continue; + + auto extension = fileInfo.suffix(); + + for(int i = 0; i < best_found; i++) { + if(extension == validIconExtensions[i]) { + best_found = i; + qDebug() << i << " : " << fileInfo.fileName(); + best_filename = fileInfo.fileName(); + } + } + } + return FS::PathCombine(folder, best_filename); +} + +QString getIconFilter() { + QString out; + QTextStream stream(&out); + stream << '('; + for(size_t i = 0; i < validIconExtensions.size() - 1; i++) { + if(i > 0) { + stream << " "; + } + stream << "*." << validIconExtensions[i]; + } + stream << " *." << validIconExtensions[validIconExtensions.size() - 1]; + stream << ')'; + return out; +} + +} + diff --git a/api/logic/icons/IconUtils.h b/api/logic/icons/IconUtils.h new file mode 100644 index 00000000..ce236946 --- /dev/null +++ b/api/logic/icons/IconUtils.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "multimc_logic_export.h" + +namespace IconUtils { + +// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path +MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey); + +// Get icon file type filter for file browser dialogs +MULTIMC_LOGIC_EXPORT QString getIconFilter(); + +} diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp index f0b71e48..ca0f4bde 100644 --- a/api/logic/java/JavaChecker.cpp +++ b/api/logic/java/JavaChecker.cpp @@ -16,149 +16,149 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent) void JavaChecker::performCheck() { - QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); - - QStringList args; - - process.reset(new QProcess()); - if(m_args.size()) - { - auto extraArgs = Commandline::splitArgs(m_args); - args.append(extraArgs); - } - if(m_minMem != 0) - { - args << QString("-Xms%1m").arg(m_minMem); - } - if(m_maxMem != 0) - { - args << QString("-Xmx%1m").arg(m_maxMem); - } - if(m_permGen != 64) - { - args << QString("-XX:PermSize=%1m").arg(m_permGen); - } - - args.append({"-jar", checkerJar}); - process->setArguments(args); - process->setProgram(m_path); - process->setProcessChannelMode(QProcess::SeparateChannels); - process->setProcessEnvironment(CleanEnviroment()); - qDebug() << "Running java checker: " + m_path + 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(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); - connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); - connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); - killTimer.setSingleShot(true); - killTimer.start(15000); - process->start(); + QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar"); + + QStringList args; + + process.reset(new QProcess()); + if(m_args.size()) + { + auto extraArgs = Commandline::splitArgs(m_args); + args.append(extraArgs); + } + if(m_minMem != 0) + { + args << QString("-Xms%1m").arg(m_minMem); + } + if(m_maxMem != 0) + { + args << QString("-Xmx%1m").arg(m_maxMem); + } + if(m_permGen != 64) + { + args << QString("-XX:PermSize=%1m").arg(m_permGen); + } + + args.append({"-jar", checkerJar}); + process->setArguments(args); + process->setProgram(m_path); + process->setProcessChannelMode(QProcess::SeparateChannels); + process->setProcessEnvironment(CleanEnviroment()); + qDebug() << "Running java checker: " + m_path + 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(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); + connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); + connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); + killTimer.setSingleShot(true); + killTimer.start(15000); + process->start(); } void JavaChecker::stdoutReady() { - QByteArray data = process->readAllStandardOutput(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stdout += added; + QByteArray data = process->readAllStandardOutput(); + QString added = QString::fromLocal8Bit(data); + added.remove('\r'); + m_stdout += added; } void JavaChecker::stderrReady() { - QByteArray data = process->readAllStandardError(); - QString added = QString::fromLocal8Bit(data); - added.remove('\r'); - m_stderr += added; + QByteArray data = process->readAllStandardError(); + QString added = QString::fromLocal8Bit(data); + added.remove('\r'); + m_stderr += added; } void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) { - killTimer.stop(); - QProcessPtr _process; - _process.swap(process); - - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - result.errorLog = m_stderr; - result.outLog = m_stdout; - qDebug() << "STDOUT" << m_stdout; - qWarning() << "STDERR" << m_stderr; - qDebug() << "Java checker finished with status " << status << " exit code " << exitcode; - - if (status == QProcess::CrashExit || exitcode == 1) - { - result.validity = JavaCheckResult::Validity::Errored; - emit checkFinished(result); - return; - } - - bool success = true; - - QMap results; - QStringList lines = m_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) - { - result.validity = JavaCheckResult::Validity::ReturnedInvalidData; - 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.validity = JavaCheckResult::Validity::Valid; - result.is_64bit = is_64; - result.mojangPlatform = is_64 ? "64" : "32"; - result.realPlatform = os_arch; - result.javaVersion = java_version; - qDebug() << "Java checker succeeded."; - emit checkFinished(result); + killTimer.stop(); + QProcessPtr _process = process; + process.reset(); + + JavaCheckResult result; + { + result.path = m_path; + result.id = m_id; + } + result.errorLog = m_stderr; + result.outLog = m_stdout; + qDebug() << "STDOUT" << m_stdout; + qWarning() << "STDERR" << m_stderr; + qDebug() << "Java checker finished with status " << status << " exit code " << exitcode; + + if (status == QProcess::CrashExit || exitcode == 1) + { + result.validity = JavaCheckResult::Validity::Errored; + emit checkFinished(result); + return; + } + + bool success = true; + + QMap results; + QStringList lines = m_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) + { + result.validity = JavaCheckResult::Validity::ReturnedInvalidData; + 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.validity = JavaCheckResult::Validity::Valid; + result.is_64bit = is_64; + result.mojangPlatform = is_64 ? "64" : "32"; + result.realPlatform = os_arch; + result.javaVersion = java_version; + qDebug() << "Java checker succeeded."; + emit checkFinished(result); } void JavaChecker::error(QProcess::ProcessError err) { - if(err == QProcess::FailedToStart) - { - killTimer.stop(); - qDebug() << "Java checker has failed to start."; - JavaCheckResult result; - { - result.path = m_path; - result.id = m_id; - } - - emit checkFinished(result); - return; - } + if(err == QProcess::FailedToStart) + { + killTimer.stop(); + qDebug() << "Java checker has failed to start."; + JavaCheckResult result; + { + result.path = m_path; + result.id = m_id; + } + + emit checkFinished(result); + return; + } } void JavaChecker::timeout() { - // NO MERCY. NO ABUSE. - if(process) - { - qDebug() << "Java checker has been killed by timeout."; - process->kill(); - } + // NO MERCY. NO ABUSE. + if(process) + { + qDebug() << "Java checker has been killed by timeout."; + process->kill(); + } } diff --git a/api/logic/java/JavaChecker.h b/api/logic/java/JavaChecker.h index c6bd697c..af0dcb90 100644 --- a/api/logic/java/JavaChecker.h +++ b/api/logic/java/JavaChecker.h @@ -3,6 +3,8 @@ #include #include +#include "QObjectPtr.h" + #include "multimc_logic_export.h" #include "JavaVersion.h" @@ -11,50 +13,50 @@ class JavaChecker; struct MULTIMC_LOGIC_EXPORT JavaCheckResult { - QString path; - QString mojangPlatform; - QString realPlatform; - JavaVersion javaVersion; - QString outLog; - QString errorLog; - bool is_64bit = false; - int id; - enum class Validity - { - Errored, - ReturnedInvalidData, - Valid - } validity = Validity::Errored; + QString path; + QString mojangPlatform; + QString realPlatform; + JavaVersion javaVersion; + QString outLog; + QString errorLog; + bool is_64bit = false; + int id; + enum class Validity + { + Errored, + ReturnedInvalidData, + Valid + } validity = Validity::Errored; }; -typedef std::shared_ptr QProcessPtr; -typedef std::shared_ptr JavaCheckerPtr; +typedef shared_qobject_ptr QProcessPtr; +typedef shared_qobject_ptr JavaCheckerPtr; class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit JavaChecker(QObject *parent = 0); - void performCheck(); + explicit JavaChecker(QObject *parent = 0); + void performCheck(); - QString m_path; - QString m_args; - int m_id = 0; - int m_minMem = 0; - int m_maxMem = 0; - int m_permGen = 64; + QString m_path; + QString m_args; + int m_id = 0; + int m_minMem = 0; + int m_maxMem = 0; + int m_permGen = 64; signals: - void checkFinished(JavaCheckResult result); + void checkFinished(JavaCheckResult result); private: - QProcessPtr process; - QTimer killTimer; - QString m_stdout; - QString m_stderr; + QProcessPtr process; + QTimer killTimer; + QString m_stdout; + QString m_stderr; public slots: - void timeout(); - void finished(int exitcode, QProcess::ExitStatus); - void error(QProcess::ProcessError); - void stdoutReady(); - void stderrReady(); + void timeout(); + void finished(int exitcode, QProcess::ExitStatus); + void error(QProcess::ProcessError); + void stdoutReady(); + void stderrReady(); }; diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp index fabb5aaa..223d8f55 100644 --- a/api/logic/java/JavaCheckerJob.cpp +++ b/api/logic/java/JavaCheckerJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,26 +19,26 @@ void JavaCheckerJob::partFinished(JavaCheckResult result) { - num_finished++; - qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" - << javacheckers.size(); - setProgress(num_finished, javacheckers.size()); + num_finished++; + qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" + << javacheckers.size(); + setProgress(num_finished, javacheckers.size()); - javaresults.replace(result.id, result); + javaresults.replace(result.id, result); - if (num_finished == javacheckers.size()) - { - emitSucceeded(); - } + if (num_finished == javacheckers.size()) + { + emitSucceeded(); + } } void JavaCheckerJob::executeTask() { - qDebug() << m_job_name.toLocal8Bit() << " started."; - for (auto iter : javacheckers) - { - javaresults.append(JavaCheckResult()); - connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); - iter->performCheck(); - } + qDebug() << m_job_name.toLocal8Bit() << " started."; + for (auto iter : javacheckers) + { + javaresults.append(JavaCheckResult()); + connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); + iter->performCheck(); + } } diff --git a/api/logic/java/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h index cac2b638..24d0d1b8 100644 --- a/api/logic/java/JavaCheckerJob.h +++ b/api/logic/java/JavaCheckerJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,41 +20,42 @@ #include "tasks/Task.h" class JavaCheckerJob; -typedef std::shared_ptr JavaCheckerJobPtr; +typedef shared_qobject_ptr JavaCheckerJobPtr; // FIXME: this just seems horribly redundant class JavaCheckerJob : public Task { - Q_OBJECT + Q_OBJECT public: - explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; - - bool addJavaCheckerAction(JavaCheckerPtr base) - { - javacheckers.append(base); - // if this is already running, the action needs to be started right away! - if (isRunning()) - { - setProgress(num_finished, javacheckers.size()); - connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); - base->performCheck(); - } - return true; - } - QList getResults() - { - return javaresults; - } + explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; + virtual ~JavaCheckerJob() {}; + + bool addJavaCheckerAction(JavaCheckerPtr base) + { + javacheckers.append(base); + // if this is already running, the action needs to be started right away! + if (isRunning()) + { + setProgress(num_finished, javacheckers.size()); + connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished); + base->performCheck(); + } + return true; + } + QList getResults() + { + return javaresults; + } private slots: - void partFinished(JavaCheckResult result); + void partFinished(JavaCheckResult result); protected: - virtual void executeTask() override; + virtual void executeTask() override; private: - QString m_job_name; - QList javacheckers; - QList javaresults; - int num_finished = 0; + QString m_job_name; + QList javacheckers; + QList javaresults; + int num_finished = 0; }; diff --git a/api/logic/java/JavaInstall.cpp b/api/logic/java/JavaInstall.cpp index bb262b6e..5bcf7bcb 100644 --- a/api/logic/java/JavaInstall.cpp +++ b/api/logic/java/JavaInstall.cpp @@ -3,26 +3,26 @@ bool JavaInstall::operator<(const JavaInstall &rhs) { - auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); - if(archCompare != 0) - return archCompare < 0; - if(id < rhs.id) - { - return true; - } - if(id > rhs.id) - { - return false; - } - return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; + auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); + if(archCompare != 0) + return archCompare < 0; + if(id < rhs.id) + { + return true; + } + if(id > rhs.id) + { + return false; + } + return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; } bool JavaInstall::operator==(const JavaInstall &rhs) { - return arch == rhs.arch && id == rhs.id && path == rhs.path; + return arch == rhs.arch && id == rhs.id && path == rhs.path; } bool JavaInstall::operator>(const JavaInstall &rhs) { - return (!operator<(rhs)) && (!operator==(rhs)); + return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/api/logic/java/JavaInstall.h b/api/logic/java/JavaInstall.h index 882c7386..64be40d1 100644 --- a/api/logic/java/JavaInstall.h +++ b/api/logic/java/JavaInstall.h @@ -5,34 +5,34 @@ struct JavaInstall : public BaseVersion { - JavaInstall(){} - JavaInstall(QString id, QString arch, QString path) - : id(id), arch(arch), path(path) - { - } - virtual QString descriptor() - { - return id.toString(); - } + JavaInstall(){} + JavaInstall(QString id, QString arch, QString path) + : id(id), arch(arch), path(path) + { + } + virtual QString descriptor() + { + return id.toString(); + } - virtual QString name() - { - return id.toString(); - } + virtual QString name() + { + return id.toString(); + } - virtual QString typeString() const - { - return arch; - } + virtual QString typeString() const + { + return arch; + } - bool operator<(const JavaInstall & rhs); - bool operator==(const JavaInstall & rhs); - bool operator>(const JavaInstall & rhs); + bool operator<(const JavaInstall & rhs); + bool operator==(const JavaInstall & rhs); + bool operator>(const JavaInstall & rhs); - JavaVersion id; - QString arch; - QString path; - bool recommended = false; + JavaVersion id; + QString arch; + QString path; + bool recommended = false; }; typedef std::shared_ptr JavaInstallPtr; diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp index 9d2e2f8b..a71a7dbe 100644 --- a/api/logic/java/JavaInstallList.cpp +++ b/api/logic/java/JavaInstallList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,111 +31,111 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent) shared_qobject_ptr JavaInstallList::getLoadTask() { - load(); - return getCurrentTask(); + load(); + return getCurrentTask(); } shared_qobject_ptr JavaInstallList::getCurrentTask() { - if(m_status == Status::InProgress) - { - return m_loadTask; - } - return nullptr; + if(m_status == Status::InProgress) + { + return m_loadTask; + } + return nullptr; } void JavaInstallList::load() { - if(m_status != Status::InProgress) - { - m_status = Status::InProgress; - m_loadTask = new JavaListLoadTask(this); - m_loadTask->start(); - } + if(m_status != Status::InProgress) + { + m_status = Status::InProgress; + m_loadTask = new JavaListLoadTask(this); + m_loadTask->start(); + } } const BaseVersionPtr JavaInstallList::at(int i) const { - return m_vlist.at(i); + return m_vlist.at(i); } bool JavaInstallList::isLoaded() { - return m_status == JavaInstallList::Status::Done; + return m_status == JavaInstallList::Status::Done; } int JavaInstallList::count() const { - return m_vlist.count(); + return m_vlist.count(); } QVariant JavaInstallList::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 VersionPointerRole: - return qVariantFromValue(m_vlist[index.row()]); - case VersionIdRole: - return version->descriptor(); - case VersionRole: - return version->id.toString(); - case RecommendedRole: - return version->recommended; - case PathRole: - return version->path; - case ArchitectureRole: - return version->arch; - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + auto version = std::dynamic_pointer_cast(m_vlist[index.row()]); + switch (role) + { + case VersionPointerRole: + return qVariantFromValue(m_vlist[index.row()]); + case VersionIdRole: + return version->descriptor(); + case VersionRole: + return version->id.toString(); + case RecommendedRole: + return version->recommended; + case PathRole: + return version->path; + case ArchitectureRole: + return version->arch; + default: + return QVariant(); + } } BaseVersionList::RoleList JavaInstallList::providesRoles() const { - return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; + return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; } void JavaInstallList::updateListData(QList versions) { - beginResetModel(); - m_vlist = versions; - sortVersions(); - if(m_vlist.size()) - { - auto best = std::dynamic_pointer_cast(m_vlist[0]); - best->recommended = true; - } - endResetModel(); - m_status = Status::Done; - m_loadTask.reset(); + beginResetModel(); + m_vlist = versions; + sortVersions(); + if(m_vlist.size()) + { + auto best = std::dynamic_pointer_cast(m_vlist[0]); + best->recommended = true; + } + endResetModel(); + m_status = Status::Done; + m_loadTask.reset(); } bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) { - auto rleft = std::dynamic_pointer_cast(left); - auto rright = std::dynamic_pointer_cast(right); - return (*rleft) > (*rright); + auto rleft = std::dynamic_pointer_cast(left); + auto rright = std::dynamic_pointer_cast(right); + return (*rleft) > (*rright); } void JavaInstallList::sortVersions() { - beginResetModel(); - std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); - endResetModel(); + beginResetModel(); + std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); + endResetModel(); } JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task() { - m_list = vlist; - m_currentRecommended = NULL; + m_list = vlist; + m_currentRecommended = NULL; } JavaListLoadTask::~JavaListLoadTask() @@ -144,65 +144,65 @@ JavaListLoadTask::~JavaListLoadTask() void JavaListLoadTask::executeTask() { - setStatus(tr("Detecting Java installations...")); + setStatus(tr("Detecting Java installations...")); - JavaUtils ju; - QList candidate_paths = ju.FindJavaPaths(); + JavaUtils ju; + QList candidate_paths = ju.FindJavaPaths(); - m_job = std::shared_ptr(new JavaCheckerJob("Java detection")); - connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); - connect(m_job.get(), &Task::progress, this, &Task::setProgress); + m_job = new JavaCheckerJob("Java detection"); + connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished); + connect(m_job.get(), &Task::progress, this, &Task::setProgress); - qDebug() << "Probing the following Java paths: "; - int id = 0; - for(QString candidate : candidate_paths) - { - qDebug() << " " << candidate; + qDebug() << "Probing the following Java paths: "; + int id = 0; + for(QString candidate : candidate_paths) + { + qDebug() << " " << candidate; - auto candidate_checker = new JavaChecker(); - candidate_checker->m_path = candidate; - candidate_checker->m_id = id; - m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); + auto candidate_checker = new JavaChecker(); + candidate_checker->m_path = candidate; + candidate_checker->m_id = id; + m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); - id++; - } + id++; + } - m_job->start(); + m_job->start(); } void JavaListLoadTask::javaCheckerFinished() { - QList candidates; - auto results = m_job->getResults(); - - qDebug() << "Found the following valid Java installations:"; - for(JavaCheckResult result : results) - { - if(result.validity == JavaCheckResult::Validity::Valid) - { - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = result.javaVersion; - javaVersion->arch = result.mojangPlatform; - javaVersion->path = result.path; - candidates.append(javaVersion); - - qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; - } - } - - QList javas_bvp; - for (auto java : candidates) - { - //qDebug() << 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(); + QList candidates; + auto results = m_job->getResults(); + + qDebug() << "Found the following valid Java installations:"; + for(JavaCheckResult result : results) + { + if(result.validity == JavaCheckResult::Validity::Valid) + { + JavaInstallPtr javaVersion(new JavaInstall()); + + javaVersion->id = result.javaVersion; + javaVersion->arch = result.mojangPlatform; + javaVersion->path = result.path; + candidates.append(javaVersion); + + qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; + } + } + + QList javas_bvp; + for (auto java : candidates) + { + //qDebug() << 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/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h index 39f37b80..b98908f3 100644 --- a/api/logic/java/JavaInstallList.h +++ b/api/logic/java/JavaInstallList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,58 +24,60 @@ #include "JavaCheckerJob.h" #include "JavaInstall.h" +#include "QObjectPtr.h" + #include "multimc_logic_export.h" class JavaListLoadTask; class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList { - Q_OBJECT - enum class Status - { - NotDone, - InProgress, - Done - }; + Q_OBJECT + enum class Status + { + NotDone, + InProgress, + Done + }; public: - explicit JavaInstallList(QObject *parent = 0); + explicit JavaInstallList(QObject *parent = 0); - shared_qobject_ptr getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; + shared_qobject_ptr getLoadTask() override; + bool isLoaded() override; + const BaseVersionPtr at(int i) const override; + int count() const override; + void sortVersions() override; - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; + QVariant data(const QModelIndex &index, int role) const override; + RoleList providesRoles() const override; public slots: - void updateListData(QList versions) override; + void updateListData(QList versions) override; protected: - void load(); - shared_qobject_ptr getCurrentTask(); + void load(); + shared_qobject_ptr getCurrentTask(); protected: - Status m_status = Status::NotDone; - shared_qobject_ptr m_loadTask; - QList m_vlist; + Status m_status = Status::NotDone; + shared_qobject_ptr m_loadTask; + QList m_vlist; }; class JavaListLoadTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit JavaListLoadTask(JavaInstallList *vlist); - ~JavaListLoadTask(); + explicit JavaListLoadTask(JavaInstallList *vlist); + virtual ~JavaListLoadTask(); - void executeTask() override; + void executeTask() override; public slots: - void javaCheckerFinished(); + void javaCheckerFinished(); protected: - std::shared_ptr m_job; - JavaInstallList *m_list; - JavaInstall *m_currentRecommended; + shared_qobject_ptr m_job; + JavaInstallList *m_list; + JavaInstall *m_currentRecommended; }; diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp index 4a77bc7e..1f9719a8 100644 --- a/api/logic/java/JavaUtils.cpp +++ b/api/logic/java/JavaUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,313 +31,315 @@ JavaUtils::JavaUtils() { } +#ifdef Q_OS_LINUX static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH) { - QDir mmcBin(QCoreApplication::applicationDirPath()); - auto items = LD_LIBRARY_PATH.split(':'); - QStringList final; - for(auto & item: items) - { - QDir test(item); - if(test == mmcBin) - { - qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; - continue; - } - final.append(item); - } - return final.join(':'); + QDir mmcBin(QCoreApplication::applicationDirPath()); + auto items = LD_LIBRARY_PATH.split(':'); + QStringList final; + for(auto & item: items) + { + QDir test(item); + if(test == mmcBin) + { + qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item; + continue; + } + final.append(item); + } + return final.join(':'); } +#endif QProcessEnvironment CleanEnviroment() { - // prepare the process environment - QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); - QProcessEnvironment env; - - QStringList ignored = - { - "JAVA_ARGS", - "CLASSPATH", - "CONFIGPATH", - "JAVA_HOME", - "JRE_HOME", - "_JAVA_OPTIONS", - "JAVA_OPTIONS", - "JAVA_TOOL_OPTIONS" - }; - for(auto key: rawenv.keys()) - { - auto value = rawenv.value(key); - // filter out dangerous java crap - if(ignored.contains(key)) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // filter MultiMC-related things - if(key.startsWith("QT_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } + // prepare the process environment + QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment env; + + QStringList ignored = + { + "JAVA_ARGS", + "CLASSPATH", + "CONFIGPATH", + "JAVA_HOME", + "JRE_HOME", + "_JAVA_OPTIONS", + "JAVA_OPTIONS", + "JAVA_TOOL_OPTIONS" + }; + for(auto key: rawenv.keys()) + { + auto value = rawenv.value(key); + // filter out dangerous java crap + if(ignored.contains(key)) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // filter MultiMC-related things + if(key.startsWith("QT_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } #ifdef Q_OS_LINUX - // Do not pass LD_* variables to java. They were intended for MultiMC - if(key.startsWith("LD_")) - { - qDebug() << "Env: ignoring" << key << value; - continue; - } - // Strip IBus - // IBus is a Linux IME framework. For some reason, it breaks MC? - if (key == "XMODIFIERS" && value.contains(IBUS)) - { - QString save = value; - value.replace(IBUS, ""); - qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; - } - if(key == "GAME_PRELOAD") - { - env.insert("LD_PRELOAD", value); - continue; - } - if(key == "GAME_LIBRARY_PATH") - { - env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); - continue; - } + // Do not pass LD_* variables to java. They were intended for MultiMC + if(key.startsWith("LD_")) + { + qDebug() << "Env: ignoring" << key << value; + continue; + } + // Strip IBus + // IBus is a Linux IME framework. For some reason, it breaks MC? + if (key == "XMODIFIERS" && value.contains(IBUS)) + { + QString save = value; + value.replace(IBUS, ""); + qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value; + } + if(key == "GAME_PRELOAD") + { + env.insert("LD_PRELOAD", value); + continue; + } + if(key == "GAME_LIBRARY_PATH") + { + env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value)); + continue; + } #endif - // qDebug() << "Env: " << key << value; - env.insert(key, value); - } + // qDebug() << "Env: " << key << value; + env.insert(key, value); + } #ifdef Q_OS_LINUX - // HACK: Workaround for QTBUG42500 - if(!env.contains("LD_LIBRARY_PATH")) - { - env.insert("LD_LIBRARY_PATH", ""); - } + // HACK: Workaround for QTBUG42500 + if(!env.contains("LD_LIBRARY_PATH")) + { + env.insert("LD_LIBRARY_PATH", ""); + } #endif - return env; + return env; } JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) { - JavaInstallPtr javaVersion(new JavaInstall()); + JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = id; - javaVersion->arch = arch; - javaVersion->path = path; + javaVersion->id = id; + javaVersion->arch = arch; + javaVersion->path = path; - return javaVersion; + return javaVersion; } JavaInstallPtr JavaUtils::GetDefaultJava() { - JavaInstallPtr javaVersion(new JavaInstall()); + JavaInstallPtr javaVersion(new JavaInstall()); - javaVersion->id = "java"; - javaVersion->arch = "unknown"; + javaVersion->id = "java"; + javaVersion->arch = "unknown"; #if defined(Q_OS_WIN32) - javaVersion->path = "javaw"; + javaVersion->path = "javaw"; #else - javaVersion->path = "java"; + javaVersion->path = "java"; #endif - return javaVersion; + return javaVersion; } #if defined(Q_OS_WIN32) 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 (DWORD 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. - JavaInstallPtr javaVersion(new JavaInstall()); - - javaVersion->id = subKeyName; - javaVersion->arch = archType; - javaVersion->path = - QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); - javas.append(javaVersion); - } - - RegCloseKey(newKey); - } - } - } - } - - RegCloseKey(jreKey); - } - - return javas; + 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 (DWORD 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. + JavaInstallPtr javaVersion(new JavaInstall()); + + javaVersion->id = subKeyName; + javaVersion->arch = archType; + javaVersion->path = + QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.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/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK64s); - java_candidates.append(JRE32s); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); - java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); - java_candidates.append(JDK32s); - java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); - - QList candidates; - for(JavaInstallPtr java_candidate : java_candidates) - { - if(!candidates.contains(java_candidate->path)) - { - candidates.append(java_candidate->path); - } - } - - return candidates; + 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/jre8/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); + java_candidates.append(JDK64s); + java_candidates.append(JRE32s); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); + java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); + java_candidates.append(JDK32s); + java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); + + QList candidates; + for(JavaInstallPtr java_candidate : java_candidates) + { + if(!candidates.contains(java_candidate->path)) + { + candidates.append(java_candidate->path); + } + } + + return candidates; } #elif defined(Q_OS_MAC) 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; + 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 defined(Q_OS_LINUX) QList JavaUtils::FindJavaPaths() { - qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; - - QList javas; - javas.append(this->GetDefaultJava()->path); - auto scanJavaDir = [&](const QString & dirPath) - { - QDir dir(dirPath); - if(!dir.exists()) - return; - auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); - for(auto & entry: entries) - { - - QString prefix; - if(entry.isAbsolute()) - { - prefix = entry.absoluteFilePath(); - } - else - { - prefix = entry.filePath(); - } - - javas.append(FS::PathCombine(prefix, "jre/bin/java")); - javas.append(FS::PathCombine(prefix, "bin/java")); - } - }; - // oracle RPMs - scanJavaDir("/usr/java"); - // general locations used by distro packaging - scanJavaDir("/usr/lib/jvm"); - scanJavaDir("/usr/lib32/jvm"); - // javas stored in MultiMC's folder - scanJavaDir("java"); - return javas; + qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; + + QList javas; + javas.append(this->GetDefaultJava()->path); + auto scanJavaDir = [&](const QString & dirPath) + { + QDir dir(dirPath); + if(!dir.exists()) + return; + auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); + for(auto & entry: entries) + { + + QString prefix; + if(entry.isAbsolute()) + { + prefix = entry.absoluteFilePath(); + } + else + { + prefix = entry.filePath(); + } + + javas.append(FS::PathCombine(prefix, "jre/bin/java")); + javas.append(FS::PathCombine(prefix, "bin/java")); + } + }; + // oracle RPMs + scanJavaDir("/usr/java"); + // general locations used by distro packaging + scanJavaDir("/usr/lib/jvm"); + scanJavaDir("/usr/lib32/jvm"); + // javas stored in MultiMC's folder + scanJavaDir("java"); + return javas; } #else QList JavaUtils::FindJavaPaths() { - qDebug() << "Unknown operating system build - defaulting to \"java\""; + qDebug() << "Unknown operating system build - defaulting to \"java\""; - QList javas; - javas.append(this->GetDefaultJava()->path); + QList javas; + javas.append(this->GetDefaultJava()->path); - return javas; + return javas; } #endif diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h index b43e93cf..100d942f 100644 --- a/api/logic/java/JavaUtils.h +++ b/api/logic/java/JavaUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,15 +30,15 @@ QProcessEnvironment CleanEnviroment(); class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject { - Q_OBJECT + Q_OBJECT public: - JavaUtils(); + JavaUtils(); - JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); - QList FindJavaPaths(); - JavaInstallPtr GetDefaultJava(); + JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); + QList FindJavaPaths(); + JavaInstallPtr GetDefaultJava(); #ifdef Q_OS_WIN - QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); + QList FindJavaFromRegistryKey(DWORD keyType, QString keyName); #endif }; diff --git a/api/logic/java/JavaVersion.cpp b/api/logic/java/JavaVersion.cpp index 27050da3..179ccd8d 100644 --- a/api/logic/java/JavaVersion.cpp +++ b/api/logic/java/JavaVersion.cpp @@ -6,116 +6,116 @@ JavaVersion & JavaVersion::operator=(const QString & javaVersionString) { - m_string = javaVersionString; + m_string = javaVersionString; - auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int - { - auto str = match.captured(what); - if(str.isEmpty()) - { - return 0; - } - return str.toInt(); - }; + auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int + { + auto str = match.captured(what); + if(str.isEmpty()) + { + return 0; + } + return str.toInt(); + }; - QRegularExpression pattern; - if(javaVersionString.startsWith("1.")) - { - pattern = QRegularExpression ("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); - } - else - { - pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); - } + QRegularExpression pattern; + if(javaVersionString.startsWith("1.")) + { + pattern = QRegularExpression ("1[.](?[0-9]+)([.](?[0-9]+))?(_(?[0-9]+)?)?(-(?[a-zA-Z0-9]+))?"); + } + else + { + pattern = QRegularExpression("(?[0-9]+)([.](?[0-9]+))?([.](?[0-9]+))?(-(?[a-zA-Z0-9]+))?"); + } - auto match = pattern.match(m_string); - m_parseable = match.hasMatch(); - m_major = getCapturedInteger(match, "major"); - m_minor = getCapturedInteger(match, "minor"); - m_security = getCapturedInteger(match, "security"); - m_prerelease = match.captured("prerelease"); - return *this; + auto match = pattern.match(m_string); + m_parseable = match.hasMatch(); + m_major = getCapturedInteger(match, "major"); + m_minor = getCapturedInteger(match, "minor"); + m_security = getCapturedInteger(match, "security"); + m_prerelease = match.captured("prerelease"); + return *this; } JavaVersion::JavaVersion(const QString &rhs) { - operator=(rhs); + operator=(rhs); } QString JavaVersion::toString() { - return m_string; + return m_string; } bool JavaVersion::requiresPermGen() { - if(m_parseable) - { - return m_major < 8; - } - return true; + if(m_parseable) + { + return m_major < 8; + } + return true; } bool JavaVersion::operator<(const JavaVersion &rhs) { - if(m_parseable && rhs.m_parseable) - { - auto major = m_major; - auto rmajor = rhs.m_major; + if(m_parseable && rhs.m_parseable) + { + auto major = m_major; + auto rmajor = rhs.m_major; - // HACK: discourage using java 9 - if(major > 8) - major = -major; - if(rmajor > 8) - rmajor = -rmajor; + // HACK: discourage using java 9 + if(major > 8) + major = -major; + if(rmajor > 8) + rmajor = -rmajor; - if(major < rmajor) - return true; - if(major > rmajor) - return false; - if(m_minor < rhs.m_minor) - return true; - if(m_minor > rhs.m_minor) - return false; - if(m_security < rhs.m_security) - return true; - if(m_security > rhs.m_security) - return false; + if(major < rmajor) + return true; + if(major > rmajor) + return false; + if(m_minor < rhs.m_minor) + return true; + if(m_minor > rhs.m_minor) + return false; + if(m_security < rhs.m_security) + return true; + if(m_security > rhs.m_security) + return false; - // everything else being equal, consider prerelease status - bool thisPre = !m_prerelease.isEmpty(); - bool rhsPre = !rhs.m_prerelease.isEmpty(); - if(thisPre && !rhsPre) - { - // this is a prerelease and the other one isn't -> lesser - return true; - } - else if(!thisPre && rhsPre) - { - // this isn't a prerelease and the other one is -> greater - return false; - } - else if(thisPre && rhsPre) - { - // both are prereleases - use natural compare... - return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; - } - // neither is prerelease, so they are the same -> this cannot be less than rhs - return false; - } - else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; + // everything else being equal, consider prerelease status + bool thisPre = !m_prerelease.isEmpty(); + bool rhsPre = !rhs.m_prerelease.isEmpty(); + if(thisPre && !rhsPre) + { + // this is a prerelease and the other one isn't -> lesser + return true; + } + else if(!thisPre && rhsPre) + { + // this isn't a prerelease and the other one is -> greater + return false; + } + else if(thisPre && rhsPre) + { + // both are prereleases - use natural compare... + return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; + } + // neither is prerelease, so they are the same -> this cannot be less than rhs + return false; + } + else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; } bool JavaVersion::operator==(const JavaVersion &rhs) { - if(m_parseable && rhs.m_parseable) - { - return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; - } - return m_string == rhs.m_string; + if(m_parseable && rhs.m_parseable) + { + return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; + } + return m_string == rhs.m_string; } bool JavaVersion::operator>(const JavaVersion &rhs) { - return (!operator<(rhs)) && (!operator==(rhs)); + return (!operator<(rhs)) && (!operator==(rhs)); } diff --git a/api/logic/java/JavaVersion.h b/api/logic/java/JavaVersion.h index de13998c..8589c21a 100644 --- a/api/logic/java/JavaVersion.h +++ b/api/logic/java/JavaVersion.h @@ -5,46 +5,46 @@ // NOTE: apparently the GNU C library pollutes the global namespace with these... undef them. #ifdef major - #undef major + #undef major #endif #ifdef minor - #undef minor + #undef minor #endif class MULTIMC_LOGIC_EXPORT JavaVersion { - friend class JavaVersionTest; + friend class JavaVersionTest; public: - JavaVersion() {}; - JavaVersion(const QString & rhs); - - JavaVersion & operator=(const QString & rhs); - - bool operator<(const JavaVersion & rhs); - bool operator==(const JavaVersion & rhs); - bool operator>(const JavaVersion & rhs); - - bool requiresPermGen(); - - QString toString(); - - int major() - { - return m_major; - } - int minor() - { - return m_minor; - } - int security() - { - return m_security; - } + JavaVersion() {}; + JavaVersion(const QString & rhs); + + JavaVersion & operator=(const QString & rhs); + + bool operator<(const JavaVersion & rhs); + bool operator==(const JavaVersion & rhs); + bool operator>(const JavaVersion & rhs); + + bool requiresPermGen(); + + QString toString(); + + int major() + { + return m_major; + } + int minor() + { + return m_minor; + } + int security() + { + return m_security; + } private: - QString m_string; - int m_major = 0; - int m_minor = 0; - int m_security = 0; - bool m_parseable = false; - QString m_prerelease; + QString m_string; + int m_major = 0; + int m_minor = 0; + int m_security = 0; + bool m_parseable = false; + QString m_prerelease; }; diff --git a/api/logic/java/JavaVersion_test.cpp b/api/logic/java/JavaVersion_test.cpp index e719ffc8..10ae13a7 100644 --- a/api/logic/java/JavaVersion_test.cpp +++ b/api/logic/java/JavaVersion_test.cpp @@ -5,110 +5,110 @@ class JavaVersionTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Parse_data() - { - QTest::addColumn("string"); - QTest::addColumn("major"); - QTest::addColumn("minor"); - QTest::addColumn("security"); - QTest::addColumn("prerelease"); + void test_Parse_data() + { + QTest::addColumn("string"); + QTest::addColumn("major"); + QTest::addColumn("minor"); + QTest::addColumn("security"); + QTest::addColumn("prerelease"); - QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); - QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; + QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); + QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; - QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); - QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); - QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); - QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; - QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; - } - void test_Parse() - { - QFETCH(QString, string); - QFETCH(int, major); - QFETCH(int, minor); - QFETCH(int, security); - QFETCH(QString, prerelease); + QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); + QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); + QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); + QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; + QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; + } + void test_Parse() + { + QFETCH(QString, string); + QFETCH(int, major); + QFETCH(int, minor); + QFETCH(int, security); + QFETCH(QString, prerelease); - JavaVersion test(string); - QCOMPARE(test.m_string, string); - QCOMPARE(test.toString(), string); - QCOMPARE(test.m_major, major); - QCOMPARE(test.m_minor, minor); - QCOMPARE(test.m_security, security); - QCOMPARE(test.m_prerelease, prerelease); - } + JavaVersion test(string); + QCOMPARE(test.m_string, string); + QCOMPARE(test.toString(), string); + QCOMPARE(test.m_major, major); + QCOMPARE(test.m_minor, minor); + QCOMPARE(test.m_security, security); + QCOMPARE(test.m_prerelease, prerelease); + } - void test_Sort_data() - { - QTest::addColumn("lhs"); - QTest::addColumn("rhs"); - QTest::addColumn("smaller"); - QTest::addColumn("equal"); - QTest::addColumn("bigger"); + void test_Sort_data() + { + QTest::addColumn("lhs"); + QTest::addColumn("rhs"); + QTest::addColumn("smaller"); + QTest::addColumn("equal"); + QTest::addColumn("bigger"); - // old format and new format equivalence - QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; - // old format major version - QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; - // new format - first release vs first security patch - QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; - QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; - // new format - first minor vs first release/security patch - QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; - QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; - QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; - QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; - // new format - omitted numbers - QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; - QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; - QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; - // early access and prereleases compared to final release - QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; - QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; - QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; - // prerelease difference only testing - QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; - QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; - QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; - QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; - QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; - QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; - QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; - } - void test_Sort() - { - QFETCH(QString, lhs); - QFETCH(QString, rhs); - QFETCH(bool, smaller); - QFETCH(bool, equal); - QFETCH(bool, bigger); - JavaVersion lver(lhs); - JavaVersion rver(rhs); - QCOMPARE(lver < rver, smaller); - QCOMPARE(lver == rver, equal); - QCOMPARE(lver > rver, bigger); - } - void test_PermGen_data() - { - QTest::addColumn("version"); - QTest::addColumn("needs_permgen"); - QTest::newRow("1.6.0_33") << "1.6.0_33" << true; - QTest::newRow("1.7.0_60") << "1.7.0_60" << true; - QTest::newRow("1.8.0_22") << "1.8.0_22" << false; - QTest::newRow("9-ea") << "9-ea" << false; - QTest::newRow("9.2.4") << "9.2.4" << false; - } - void test_PermGen() - { - QFETCH(QString, version); - QFETCH(bool, needs_permgen); - JavaVersion v(version); - QCOMPARE(needs_permgen, v.requiresPermGen()); - } + // old format and new format equivalence + QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; + // old format major version + QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; + // new format - first release vs first security patch + QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; + QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; + // new format - first minor vs first release/security patch + QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; + QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; + QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; + QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; + // new format - omitted numbers + QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; + QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; + QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; + // early access and prereleases compared to final release + QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; + QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; + QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; + // prerelease difference only testing + QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; + QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; + QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; + QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; + QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; + QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; + QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; + } + void test_Sort() + { + QFETCH(QString, lhs); + QFETCH(QString, rhs); + QFETCH(bool, smaller); + QFETCH(bool, equal); + QFETCH(bool, bigger); + JavaVersion lver(lhs); + JavaVersion rver(rhs); + QCOMPARE(lver < rver, smaller); + QCOMPARE(lver == rver, equal); + QCOMPARE(lver > rver, bigger); + } + void test_PermGen_data() + { + QTest::addColumn("version"); + QTest::addColumn("needs_permgen"); + QTest::newRow("1.6.0_33") << "1.6.0_33" << true; + QTest::newRow("1.7.0_60") << "1.7.0_60" << true; + QTest::newRow("1.8.0_22") << "1.8.0_22" << false; + QTest::newRow("9-ea") << "9-ea" << false; + QTest::newRow("9.2.4") << "9.2.4" << false; + } + void test_PermGen() + { + QFETCH(QString, version); + QFETCH(bool, needs_permgen); + JavaVersion v(version); + QCOMPARE(needs_permgen, v.requiresPermGen()); + } }; QTEST_GUILESS_MAIN(JavaVersionTest) diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp index 24f26682..b75c6dc6 100644 --- a/api/logic/java/launch/CheckJava.cpp +++ b/api/logic/java/launch/CheckJava.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,115 +22,115 @@ void CheckJava::executeTask() { - auto instance = m_parent->instance(); - auto settings = instance->settings(); - m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString()); - bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); + auto instance = m_parent->instance(); + auto settings = instance->settings(); + m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString()); + bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); - auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); - if (realJavaPath.isEmpty()) - { - if (perInstance) - { - emit logLine( - tr("The java binary \"%1\" couldn't be found. Please fix the java path " - "override in the instance's settings or disable it.").arg(m_javaPath), - MessageLevel::Warning); - } - else - { - emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in " - "the settings.").arg(m_javaPath), - MessageLevel::Warning); - } - emitFailed(tr("Java path is not valid.")); - return; - } - else - { - emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC); - } + auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); + if (realJavaPath.isEmpty()) + { + if (perInstance) + { + emit logLine( + tr("The java binary \"%1\" couldn't be found. Please fix the java path " + "override in the instance's settings or disable it.").arg(m_javaPath), + MessageLevel::Warning); + } + else + { + emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in " + "the settings.").arg(m_javaPath), + MessageLevel::Warning); + } + emitFailed(tr("Java path is not valid.")); + return; + } + else + { + emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC); + } - QFileInfo javaInfo(realJavaPath); - qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); - auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); - auto storedArchitecture = settings->get("JavaArchitecture").toString(); - auto storedVersion = settings->get("JavaVersion").toString(); - m_javaUnixTime = javaUnixTime; - // if timestamps are not the same, or something is missing, check! - if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0) - { - m_JavaChecker = std::make_shared(); - emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC); - connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); - m_JavaChecker->m_path = realJavaPath; - m_JavaChecker->performCheck(); - return; - } - else - { - auto verString = instance->settings()->get("JavaVersion").toString(); - auto archString = instance->settings()->get("JavaArchitecture").toString(); - printJavaInfo(verString, archString); - } - emitSucceeded(); + QFileInfo javaInfo(realJavaPath); + qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); + auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); + auto storedArchitecture = settings->get("JavaArchitecture").toString(); + auto storedVersion = settings->get("JavaVersion").toString(); + m_javaUnixTime = javaUnixTime; + // if timestamps are not the same, or something is missing, check! + if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0) + { + m_JavaChecker = new JavaChecker(); + emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC); + connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); + m_JavaChecker->m_path = realJavaPath; + m_JavaChecker->performCheck(); + return; + } + else + { + auto verString = instance->settings()->get("JavaVersion").toString(); + auto archString = instance->settings()->get("JavaArchitecture").toString(); + printJavaInfo(verString, archString); + } + emitSucceeded(); } void CheckJava::checkJavaFinished(JavaCheckResult result) { - switch (result.validity) - { - case JavaCheckResult::Validity::Errored: - { - // Error message displayed if java can't start - emit logLine(tr("Could not start java:"), MessageLevel::Error); - emit logLines(result.errorLog.split('\n'), MessageLevel::Error); - emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); - printSystemInfo(false, false); - emitFailed(tr("Could not start java!")); - return; - } - case JavaCheckResult::Validity::ReturnedInvalidData: - { - emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error); - emit logLines(result.outLog.split('\n'), MessageLevel::Warning); - emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC); - printSystemInfo(false, false); - emitSucceeded(); - return; - } - case JavaCheckResult::Validity::Valid: - { - auto instance = m_parent->instance(); - printJavaInfo(result.javaVersion.toString(), result.mojangPlatform); - instance->settings()->set("JavaVersion", result.javaVersion.toString()); - instance->settings()->set("JavaArchitecture", result.mojangPlatform); - instance->settings()->set("JavaTimestamp", m_javaUnixTime); - emitSucceeded(); - return; - } - } + switch (result.validity) + { + case JavaCheckResult::Validity::Errored: + { + // Error message displayed if java can't start + emit logLine(tr("Could not start java:"), MessageLevel::Error); + emit logLines(result.errorLog.split('\n'), MessageLevel::Error); + emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); + printSystemInfo(false, false); + emitFailed(tr("Could not start java!")); + return; + } + case JavaCheckResult::Validity::ReturnedInvalidData: + { + emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error); + emit logLines(result.outLog.split('\n'), MessageLevel::Warning); + emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC); + printSystemInfo(false, false); + emitSucceeded(); + return; + } + case JavaCheckResult::Validity::Valid: + { + auto instance = m_parent->instance(); + printJavaInfo(result.javaVersion.toString(), result.mojangPlatform); + instance->settings()->set("JavaVersion", result.javaVersion.toString()); + instance->settings()->set("JavaArchitecture", result.mojangPlatform); + instance->settings()->set("JavaTimestamp", m_javaUnixTime); + emitSucceeded(); + return; + } + } } void CheckJava::printJavaInfo(const QString& version, const QString& architecture) { - emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC); - printSystemInfo(true, architecture == "64"); + emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC); + printSystemInfo(true, architecture == "64"); } void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) { - auto cpu64 = Sys::isCPU64bit(); - auto system64 = Sys::isSystem64bit(); - if(cpu64 != system64) - { - emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error); - } - if(javaIsKnown) - { - if(javaIs64bit != system64) - { - emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error); - } - } + auto cpu64 = Sys::isCPU64bit(); + auto system64 = Sys::isSystem64bit(); + if(cpu64 != system64) + { + emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error); + } + if(javaIsKnown) + { + if(javaIs64bit != system64) + { + emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error); + } + } } diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h index 82508cd4..f0dd2308 100644 --- a/api/logic/java/launch/CheckJava.h +++ b/api/logic/java/launch/CheckJava.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,25 +21,25 @@ class CheckJava: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; - virtual ~CheckJava() {}; + explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; + virtual ~CheckJava() {}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } private slots: - void checkJavaFinished(JavaCheckResult result); + void checkJavaFinished(JavaCheckResult result); private: - void printJavaInfo(const QString & version, const QString & architecture); - void printSystemInfo(bool javaIsKnown, bool javaIs64bit); + void printJavaInfo(const QString & version, const QString & architecture); + void printSystemInfo(bool javaIsKnown, bool javaIs64bit); private: - QString m_javaPath; - qlonglong m_javaUnixTime; - JavaCheckerPtr m_JavaChecker; + QString m_javaPath; + qlonglong m_javaUnixTime; + JavaCheckerPtr m_JavaChecker; }; diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp index 01f72a0a..bcaca406 100644 --- a/api/logic/launch/LaunchStep.cpp +++ b/api/logic/launch/LaunchStep.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ void LaunchStep::bind(LaunchTask *parent) { - m_parent = parent; - connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); - connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); - connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); - connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); - connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); + m_parent = parent; + connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); + connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); + connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); + connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); + connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); } diff --git a/api/logic/launch/LaunchStep.h b/api/logic/launch/LaunchStep.h index 80778e34..dd380e43 100644 --- a/api/logic/launch/LaunchStep.h +++ b/api/logic/launch/LaunchStep.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,28 +23,28 @@ class LaunchTask; class LaunchStep: public Task { - Q_OBJECT + Q_OBJECT public: /* methods */ - explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) - { - bind(parent); - }; - virtual ~LaunchStep() {}; + explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) + { + bind(parent); + }; + virtual ~LaunchStep() {}; -protected: /* methods */ - virtual void bind(LaunchTask *parent); +private: /* methods */ + void bind(LaunchTask *parent); signals: - void logLines(QStringList lines, MessageLevel::Enum level); - void logLine(QString line, MessageLevel::Enum level); - void readyForLaunch(); - void progressReportingRequest(); + void logLines(QStringList lines, MessageLevel::Enum level); + void logLine(QString line, MessageLevel::Enum level); + void readyForLaunch(); + void progressReportingRequest(); public slots: - virtual void proceed() {}; - // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends - virtual void finalize() {}; + virtual void proceed() {}; + // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends + virtual void finalize() {}; protected: /* data */ - LaunchTask *m_parent; -}; \ No newline at end of file + LaunchTask *m_parent; +}; diff --git a/api/logic/launch/LaunchTask.cpp b/api/logic/launch/LaunchTask.cpp index 99c16721..841b8363 100644 --- a/api/logic/launch/LaunchTask.cpp +++ b/api/logic/launch/LaunchTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -30,251 +30,251 @@ void LaunchTask::init() { - m_instance->setRunning(true); + m_instance->setRunning(true); } -std::shared_ptr LaunchTask::create(InstancePtr inst) +shared_qobject_ptr LaunchTask::create(InstancePtr inst) { - std::shared_ptr proc(new LaunchTask(inst)); - proc->init(); - return proc; + shared_qobject_ptr proc(new LaunchTask(inst)); + proc->init(); + return proc; } LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance) { } -void LaunchTask::appendStep(std::shared_ptr step) +void LaunchTask::appendStep(shared_qobject_ptr step) { - m_steps.append(step); + m_steps.append(step); } -void LaunchTask::prependStep(std::shared_ptr step) +void LaunchTask::prependStep(shared_qobject_ptr step) { - m_steps.prepend(step); + m_steps.prepend(step); } void LaunchTask::executeTask() { - m_instance->setCrashed(false); - if(!m_steps.size()) - { - state = LaunchTask::Finished; - emitSucceeded(); - } - state = LaunchTask::Running; - onStepFinished(); + m_instance->setCrashed(false); + if(!m_steps.size()) + { + state = LaunchTask::Finished; + emitSucceeded(); + } + state = LaunchTask::Running; + onStepFinished(); } void LaunchTask::onReadyForLaunch() { - state = LaunchTask::Waiting; - emit readyForLaunch(); + state = LaunchTask::Waiting; + emit readyForLaunch(); } void LaunchTask::onStepFinished() { - // initial -> just start the first step - if(currentStep == -1) - { - currentStep ++; - m_steps[currentStep]->start(); - return; - } + // initial -> just start the first step + if(currentStep == -1) + { + currentStep ++; + m_steps[currentStep]->start(); + return; + } - auto step = m_steps[currentStep]; - if(step->wasSuccessful()) - { - // end? - if(currentStep == m_steps.size() - 1) - { - finalizeSteps(true, QString()); - } - else - { - currentStep ++; - step = m_steps[currentStep]; - step->start(); - } - } - else - { - finalizeSteps(false, step->failReason()); - } + auto step = m_steps[currentStep]; + if(step->wasSuccessful()) + { + // end? + if(currentStep == m_steps.size() - 1) + { + finalizeSteps(true, QString()); + } + else + { + currentStep ++; + step = m_steps[currentStep]; + step->start(); + } + } + else + { + finalizeSteps(false, step->failReason()); + } } void LaunchTask::finalizeSteps(bool successful, const QString& error) { - for(auto step = currentStep; step >= 0; step--) - { - m_steps[step]->finalize(); - } - if(successful) - { - emitSucceeded(); - } - else - { - emitFailed(error); - } + for(auto step = currentStep; step >= 0; step--) + { + m_steps[step]->finalize(); + } + if(successful) + { + emitSucceeded(); + } + else + { + emitFailed(error); + } } void LaunchTask::onProgressReportingRequested() { - state = LaunchTask::Waiting; - emit requestProgress(m_steps[currentStep].get()); + state = LaunchTask::Waiting; + emit requestProgress(m_steps[currentStep].get()); } void LaunchTask::setCensorFilter(QMap filter) { - m_censorFilter = filter; + m_censorFilter = filter; } QString LaunchTask::censorPrivateInfo(QString in) { - auto iter = m_censorFilter.begin(); - while (iter != m_censorFilter.end()) - { - in.replace(iter.key(), iter.value()); - iter++; - } - return in; + auto iter = m_censorFilter.begin(); + while (iter != m_censorFilter.end()) + { + in.replace(iter.key(), iter.value()); + iter++; + } + return in; } void LaunchTask::proceed() { - if(state != LaunchTask::Waiting) - { - return; - } - m_steps[currentStep]->proceed(); + if(state != LaunchTask::Waiting) + { + return; + } + m_steps[currentStep]->proceed(); } bool LaunchTask::canAbort() const { - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return false; - case LaunchTask::NotStarted: - return true; - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - return step->canAbort(); - } - } - return false; + switch(state) + { + case LaunchTask::Aborted: + case LaunchTask::Failed: + case LaunchTask::Finished: + return false; + case LaunchTask::NotStarted: + return true; + case LaunchTask::Running: + case LaunchTask::Waiting: + { + auto step = m_steps[currentStep]; + return step->canAbort(); + } + } + return false; } bool LaunchTask::abort() { - switch(state) - { - case LaunchTask::Aborted: - case LaunchTask::Failed: - case LaunchTask::Finished: - return true; - case LaunchTask::NotStarted: - { - state = LaunchTask::Aborted; - emitFailed("Aborted"); - return true; - } - case LaunchTask::Running: - case LaunchTask::Waiting: - { - auto step = m_steps[currentStep]; - if(!step->canAbort()) - { - return false; - } - if(step->abort()) - { - state = LaunchTask::Aborted; - return true; - } - } - default: - break; - } - return false; + switch(state) + { + case LaunchTask::Aborted: + case LaunchTask::Failed: + case LaunchTask::Finished: + return true; + case LaunchTask::NotStarted: + { + state = LaunchTask::Aborted; + emitFailed("Aborted"); + return true; + } + case LaunchTask::Running: + case LaunchTask::Waiting: + { + auto step = m_steps[currentStep]; + if(!step->canAbort()) + { + return false; + } + if(step->abort()) + { + state = LaunchTask::Aborted; + return true; + } + } + default: + break; + } + return false; } shared_qobject_ptr LaunchTask::getLogModel() { - if(!m_logModel) - { - m_logModel.reset(new LogModel()); - m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); - m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); - // FIXME: should this really be here? - m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" - "You may have to fix your mods because the game is still logging to files and" - " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); - } - return m_logModel; + if(!m_logModel) + { + m_logModel.reset(new LogModel()); + m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); + m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); + // FIXME: should this really be here? + m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" + "You may have to fix your mods because the game is still logging to files and" + " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); + } + return m_logModel; } void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel) { - for (auto & line: lines) - { - onLogLine(line, defaultLevel); - } + for (auto & line: lines) + { + onLogLine(line, defaultLevel); + } } void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) { - // if the launcher part set a log level, use it - auto innerLevel = MessageLevel::fromLine(line); - if(innerLevel != MessageLevel::Unknown) - { - level = innerLevel; - } + // if the launcher part set a log level, use it + auto innerLevel = MessageLevel::fromLine(line); + if(innerLevel != MessageLevel::Unknown) + { + level = innerLevel; + } - // If the level is still undetermined, guess level - if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) - { - level = m_instance->guessLevel(line, level); - } + // If the level is still undetermined, guess level + if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) + { + level = m_instance->guessLevel(line, level); + } - // censor private user info - line = censorPrivateInfo(line); + // censor private user info + line = censorPrivateInfo(line); - auto &model = *getLogModel(); - model.append(level, line); + auto &model = *getLogModel(); + model.append(level, line); } void LaunchTask::emitSucceeded() { - m_instance->setRunning(false); - Task::emitSucceeded(); + m_instance->setRunning(false); + Task::emitSucceeded(); } void LaunchTask::emitFailed(QString reason) { - m_instance->setRunning(false); - m_instance->setCrashed(true); - Task::emitFailed(reason); + m_instance->setRunning(false); + m_instance->setCrashed(true); + Task::emitFailed(reason); } QString LaunchTask::substituteVariables(const QString &cmd) const { - QString out = cmd; - auto variables = m_instance->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - out.replace("$" + it.key(), it.value()); - } - auto env = QProcessEnvironment::systemEnvironment(); - for (auto var : env.keys()) - { - out.replace("$" + var, env.value(var)); - } - return out; + QString out = cmd; + auto variables = m_instance->getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + out.replace("$" + it.key(), it.value()); + } + auto env = QProcessEnvironment::systemEnvironment(); + for (auto var : env.keys()) + { + out.replace("$" + var, env.value(var)); + } + return out; } diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h index 746d6d19..ee04bd9a 100644 --- a/api/logic/launch/LaunchTask.h +++ b/api/logic/launch/LaunchTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -6,7 +6,7 @@ * 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 + * 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, @@ -28,98 +28,98 @@ class MULTIMC_LOGIC_EXPORT LaunchTask: public Task { - Q_OBJECT + Q_OBJECT protected: - explicit LaunchTask(InstancePtr instance); - void init(); + explicit LaunchTask(InstancePtr instance); + void init(); public: - enum State - { - NotStarted, - Running, - Waiting, - Failed, - Aborted, - Finished - }; + enum State + { + NotStarted, + Running, + Waiting, + Failed, + Aborted, + Finished + }; public: /* methods */ - static std::shared_ptr create(InstancePtr inst); - virtual ~LaunchTask() {}; + static shared_qobject_ptr create(InstancePtr inst); + virtual ~LaunchTask() {}; - void appendStep(std::shared_ptr step); - void prependStep(std::shared_ptr step); - void setCensorFilter(QMap filter); + void appendStep(shared_qobject_ptr step); + void prependStep(shared_qobject_ptr step); + void setCensorFilter(QMap filter); - InstancePtr instance() - { - return m_instance; - } + InstancePtr instance() + { + return m_instance; + } - void setPid(qint64 pid) - { - m_pid = pid; - } + void setPid(qint64 pid) + { + m_pid = pid; + } - qint64 pid() - { - return m_pid; - } + qint64 pid() + { + return m_pid; + } - /** - * @brief prepare the process for launch (for multi-stage launch) - */ - virtual void executeTask() override; + /** + * @brief prepare the process for launch (for multi-stage launch) + */ + virtual void executeTask() override; - /** - * @brief launch the armed instance - */ - void proceed(); + /** + * @brief launch the armed instance + */ + void proceed(); - /** - * @brief abort launch - */ - bool abort() override; + /** + * @brief abort launch + */ + bool abort() override; - bool canAbort() const override; + bool canAbort() const override; - shared_qobject_ptr getLogModel(); + shared_qobject_ptr getLogModel(); public: - QString substituteVariables(const QString &cmd) const; - QString censorPrivateInfo(QString in); + QString substituteVariables(const QString &cmd) const; + QString censorPrivateInfo(QString in); protected: /* methods */ - virtual void emitFailed(QString reason) override; - virtual void emitSucceeded() override; + virtual void emitFailed(QString reason) override; + virtual void emitSucceeded() override; signals: - /** - * @brief emitted when the launch preparations are done - */ - void readyForLaunch(); + /** + * @brief emitted when the launch preparations are done + */ + void readyForLaunch(); - void requestProgress(Task *task); + void requestProgress(Task *task); - void requestLogging(); + void requestLogging(); public slots: - void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); - void onReadyForLaunch(); - void onStepFinished(); - void onProgressReportingRequested(); + void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC); + void onReadyForLaunch(); + void onStepFinished(); + void onProgressReportingRequested(); private: /*methods */ - void finalizeSteps(bool successful, const QString & error); + void finalizeSteps(bool successful, const QString & error); protected: /* data */ - InstancePtr m_instance; - shared_qobject_ptr m_logModel; - QList > m_steps; - QMap m_censorFilter; - int currentStep = -1; - State state = NotStarted; - qint64 m_pid = -1; + InstancePtr m_instance; + shared_qobject_ptr m_logModel; + QList > m_steps; + QMap m_censorFilter; + int currentStep = -1; + State state = NotStarted; + qint64 m_pid = -1; }; diff --git a/api/logic/launch/LogModel.cpp b/api/logic/launch/LogModel.cpp index 72b076e9..92f9487a 100644 --- a/api/logic/launch/LogModel.cpp +++ b/api/logic/launch/LogModel.cpp @@ -2,166 +2,166 @@ LogModel::LogModel(QObject *parent):QAbstractListModel(parent) { - m_content.resize(m_maxLines); + m_content.resize(m_maxLines); } int LogModel::rowCount(const QModelIndex &parent) const { - if (parent.isValid()) - return 0; + if (parent.isValid()) + return 0; - return m_numLines; + return m_numLines; } QVariant LogModel::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= m_numLines) - return QVariant(); - - auto row = index.row(); - auto realRow = (row + m_firstLine) % m_maxLines; - if (role == Qt::DisplayRole || role == Qt::EditRole) - { - return m_content[realRow].line; - } - if(role == LevelRole) - { - return m_content[realRow].level; - } - - return QVariant(); + if (index.row() < 0 || index.row() >= m_numLines) + return QVariant(); + + auto row = index.row(); + auto realRow = (row + m_firstLine) % m_maxLines; + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + return m_content[realRow].line; + } + if(role == LevelRole) + { + return m_content[realRow].level; + } + + return QVariant(); } void LogModel::append(MessageLevel::Enum level, QString line) { - if(m_suspended) - { - return; - } - int lineNum = (m_firstLine + m_numLines) % m_maxLines; - // overflow - if(m_numLines == m_maxLines) - { - if(m_stopOnOverflow) - { - // nothing more to do, the buffer is full - return; - } - beginRemoveRows(QModelIndex(), 0, 0); - m_firstLine = (m_firstLine + 1) % m_maxLines; - m_numLines --; - endRemoveRows(); - } - else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow) - { - level = MessageLevel::Fatal; - line = m_overflowMessage; - } - beginInsertRows(QModelIndex(), m_numLines, m_numLines); - m_numLines ++; - m_content[lineNum].level = level; - m_content[lineNum].line = line; - endInsertRows(); + if(m_suspended) + { + return; + } + int lineNum = (m_firstLine + m_numLines) % m_maxLines; + // overflow + if(m_numLines == m_maxLines) + { + if(m_stopOnOverflow) + { + // nothing more to do, the buffer is full + return; + } + beginRemoveRows(QModelIndex(), 0, 0); + m_firstLine = (m_firstLine + 1) % m_maxLines; + m_numLines --; + endRemoveRows(); + } + else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow) + { + level = MessageLevel::Fatal; + line = m_overflowMessage; + } + beginInsertRows(QModelIndex(), m_numLines, m_numLines); + m_numLines ++; + m_content[lineNum].level = level; + m_content[lineNum].line = line; + endInsertRows(); } void LogModel::suspend(bool suspend) { - m_suspended = suspend; + m_suspended = suspend; } bool LogModel::suspended() { - return m_suspended; + return m_suspended; } void LogModel::clear() { - beginResetModel(); - m_firstLine = 0; - m_numLines = 0; - endResetModel(); + beginResetModel(); + m_firstLine = 0; + m_numLines = 0; + endResetModel(); } QString LogModel::toPlainText() { - QString out; - out.reserve(m_numLines * 80); - for(int i = 0; i < m_numLines; i++) - { - QString & line = m_content[(m_firstLine + i) % m_maxLines].line; - out.append(line + '\n'); - } - out.squeeze(); - return out; + QString out; + out.reserve(m_numLines * 80); + for(int i = 0; i < m_numLines; i++) + { + QString & line = m_content[(m_firstLine + i) % m_maxLines].line; + out.append(line + '\n'); + } + out.squeeze(); + return out; } void LogModel::setMaxLines(int maxLines) { - // no-op - if(maxLines == m_maxLines) - { - return; - } - // if it all still fits in the buffer, just resize it - if(m_firstLine + m_numLines < m_maxLines) - { - m_maxLines = maxLines; - m_content.resize(maxLines); - return; - } - // otherwise, we need to reorganize the data because it crosses the wrap boundary - QVector newContent; - newContent.resize(maxLines); - if(m_numLines <= maxLines) - { - // if it all fits in the new buffer, just copy it over - for(int i = 0; i < m_numLines; i++) - { - newContent[i] = m_content[(m_firstLine + i) % m_maxLines]; - } - m_content.swap(newContent); - } - else - { - // if it doesn't fit, part of the data needs to be thrown away (the oldest log messages) - int lead = m_numLines - maxLines; - beginRemoveRows(QModelIndex(), 0, lead - 1); - for(int i = 0; i < maxLines; i++) - { - newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines]; - } - m_numLines = m_maxLines; - m_content.swap(newContent); - endRemoveRows(); - } - m_firstLine = 0; - m_maxLines = maxLines; + // no-op + if(maxLines == m_maxLines) + { + return; + } + // if it all still fits in the buffer, just resize it + if(m_firstLine + m_numLines < m_maxLines) + { + m_maxLines = maxLines; + m_content.resize(maxLines); + return; + } + // otherwise, we need to reorganize the data because it crosses the wrap boundary + QVector newContent; + newContent.resize(maxLines); + if(m_numLines <= maxLines) + { + // if it all fits in the new buffer, just copy it over + for(int i = 0; i < m_numLines; i++) + { + newContent[i] = m_content[(m_firstLine + i) % m_maxLines]; + } + m_content.swap(newContent); + } + else + { + // if it doesn't fit, part of the data needs to be thrown away (the oldest log messages) + int lead = m_numLines - maxLines; + beginRemoveRows(QModelIndex(), 0, lead - 1); + for(int i = 0; i < maxLines; i++) + { + newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines]; + } + m_numLines = m_maxLines; + m_content.swap(newContent); + endRemoveRows(); + } + m_firstLine = 0; + m_maxLines = maxLines; } int LogModel::getMaxLines() { - return m_maxLines; + return m_maxLines; } void LogModel::setStopOnOverflow(bool stop) { - m_stopOnOverflow = stop; + m_stopOnOverflow = stop; } void LogModel::setOverflowMessage(const QString& overflowMessage) { - m_overflowMessage = overflowMessage; + m_overflowMessage = overflowMessage; } void LogModel::setLineWrap(bool state) { - if(m_lineWrap != state) - { - m_lineWrap = state; - } + if(m_lineWrap != state) + { + m_lineWrap = state; + } } bool LogModel::wrapLines() const { - return m_lineWrap; + return m_lineWrap; } diff --git a/api/logic/launch/LogModel.h b/api/logic/launch/LogModel.h index e6deac89..bccceaef 100644 --- a/api/logic/launch/LogModel.h +++ b/api/logic/launch/LogModel.h @@ -8,53 +8,53 @@ class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit LogModel(QObject *parent = 0); + explicit LogModel(QObject *parent = 0); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; - void append(MessageLevel::Enum, QString line); - void clear(); + void append(MessageLevel::Enum, QString line); + void clear(); - void suspend(bool suspend); - bool suspended(); + void suspend(bool suspend); + bool suspended(); - QString toPlainText(); + QString toPlainText(); - int getMaxLines(); - void setMaxLines(int maxLines); - void setStopOnOverflow(bool stop); - void setOverflowMessage(const QString & overflowMessage); + int getMaxLines(); + void setMaxLines(int maxLines); + void setStopOnOverflow(bool stop); + void setOverflowMessage(const QString & overflowMessage); - void setLineWrap(bool state); - bool wrapLines() const; + void setLineWrap(bool state); + bool wrapLines() const; - enum Roles - { - LevelRole = Qt::UserRole - }; + enum Roles + { + LevelRole = Qt::UserRole + }; private /* types */: - struct entry - { - MessageLevel::Enum level; - QString line; - }; + struct entry + { + MessageLevel::Enum level; + QString line; + }; private: /* data */ - QVector m_content; - int m_maxLines = 1000; - // first line in the circular buffer - int m_firstLine = 0; - // number of lines occupied in the circular buffer - int m_numLines = 0; - bool m_stopOnOverflow = false; - QString m_overflowMessage = "OVERFLOW"; - bool m_suspended = false; - bool m_lineWrap = true; + QVector m_content; + int m_maxLines = 1000; + // first line in the circular buffer + int m_firstLine = 0; + // number of lines occupied in the circular buffer + int m_numLines = 0; + bool m_stopOnOverflow = false; + QString m_overflowMessage = "OVERFLOW"; + bool m_suspended = false; + bool m_lineWrap = true; private: - Q_DISABLE_COPY(LogModel) + Q_DISABLE_COPY(LogModel) }; diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp index bc3b3ffe..e0b2b6fc 100644 --- a/api/logic/launch/steps/PostLaunchCommand.cpp +++ b/api/logic/launch/steps/PostLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,67 +18,67 @@ PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent) { - auto instance = m_parent->instance(); - m_command = instance->getPostExitCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state); + auto instance = m_parent->instance(); + m_command = instance->getPostExitCommand(); + m_process.setProcessEnvironment(instance->createEnvironment()); + connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state); } void PostLaunchCommand::executeTask() { - QString postlaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC); - m_process.start(postlaunch_cmd); + QString postlaunch_cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC); + m_process.start(postlaunch_cmd); } void PostLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() - { - return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } + auto getError = [&]() + { + return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + }; + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + return; + } + case LoggedProcess::Finished: + { + if(m_process.exitCode() != 0) + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + } + else + { + emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + emitSucceeded(); + } + } + default: + break; + } } void PostLaunchCommand::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } bool PostLaunchCommand::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/launch/steps/PostLaunchCommand.h b/api/logic/launch/steps/PostLaunchCommand.h index 36b3b96f..da200534 100644 --- a/api/logic/launch/steps/PostLaunchCommand.h +++ b/api/logic/launch/steps/PostLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,22 @@ class PostLaunchCommand: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PostLaunchCommand(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); + explicit PostLaunchCommand(LaunchTask *parent); + virtual ~PostLaunchCommand() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; + LoggedProcess m_process; + QString m_command; }; diff --git a/api/logic/launch/steps/PreLaunchCommand.cpp b/api/logic/launch/steps/PreLaunchCommand.cpp index 7c37d5cb..6f1f280d 100644 --- a/api/logic/launch/steps/PreLaunchCommand.cpp +++ b/api/logic/launch/steps/PreLaunchCommand.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,68 +18,68 @@ PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent) { - auto instance = m_parent->instance(); - m_command = instance->getPreLaunchCommand(); - m_process.setProcessEnvironment(instance->createEnvironment()); - connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state); + auto instance = m_parent->instance(); + m_command = instance->getPreLaunchCommand(); + m_process.setProcessEnvironment(instance->createEnvironment()); + connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state); } void PreLaunchCommand::executeTask() { - //FIXME: where to put this? - QString prelaunch_cmd = m_parent->substituteVariables(m_command); - emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC); - m_process.start(prelaunch_cmd); + //FIXME: where to put this? + QString prelaunch_cmd = m_parent->substituteVariables(m_command); + emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC); + m_process.start(prelaunch_cmd); } void PreLaunchCommand::on_state(LoggedProcess::State state) { - auto getError = [&]() - { - return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); - }; - switch(state) - { - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - case LoggedProcess::FailedToStart: - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - return; - } - case LoggedProcess::Finished: - { - if(m_process.exitCode() != 0) - { - auto error = getError(); - emit logLine(error, MessageLevel::Fatal); - emitFailed(error); - } - else - { - emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); - emitSucceeded(); - } - } - default: - break; - } + auto getError = [&]() + { + return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); + }; + switch(state) + { + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + case LoggedProcess::FailedToStart: + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + return; + } + case LoggedProcess::Finished: + { + if(m_process.exitCode() != 0) + { + auto error = getError(); + emit logLine(error, MessageLevel::Fatal); + emitFailed(error); + } + else + { + emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC); + emitSucceeded(); + } + } + default: + break; + } } void PreLaunchCommand::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } bool PreLaunchCommand::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/launch/steps/PreLaunchCommand.h b/api/logic/launch/steps/PreLaunchCommand.h index 880cc076..f1e9db5d 100644 --- a/api/logic/launch/steps/PreLaunchCommand.h +++ b/api/logic/launch/steps/PreLaunchCommand.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,20 +20,22 @@ class PreLaunchCommand: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PreLaunchCommand(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); + explicit PreLaunchCommand(LaunchTask *parent); + virtual ~PreLaunchCommand() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; + LoggedProcess m_process; + QString m_command; }; diff --git a/api/logic/launch/steps/TextPrint.cpp b/api/logic/launch/steps/TextPrint.cpp index f307b1fd..0c1f320c 100644 --- a/api/logic/launch/steps/TextPrint.cpp +++ b/api/logic/launch/steps/TextPrint.cpp @@ -2,28 +2,28 @@ TextPrint::TextPrint(LaunchTask * parent, const QStringList &lines, MessageLevel::Enum level) : LaunchStep(parent) { - m_lines = lines; - m_level = level; + m_lines = lines; + m_level = level; } TextPrint::TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level) : LaunchStep(parent) { - m_lines.append(line); - m_level = level; + m_lines.append(line); + m_level = level; } void TextPrint::executeTask() { - emit logLines(m_lines, m_level); - emitSucceeded(); + emit logLines(m_lines, m_level); + emitSucceeded(); } bool TextPrint::canAbort() const { - return true; + return true; } bool TextPrint::abort() { - emitFailed("Aborted."); - return true; + emitFailed("Aborted."); + return true; } diff --git a/api/logic/launch/steps/TextPrint.h b/api/logic/launch/steps/TextPrint.h index 3e3e06cb..099fdc13 100644 --- a/api/logic/launch/steps/TextPrint.h +++ b/api/logic/launch/steps/TextPrint.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,17 +27,17 @@ class MULTIMC_LOGIC_EXPORT TextPrint: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level); - explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level); - virtual ~TextPrint(){}; + explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level); + explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level); + virtual ~TextPrint(){}; - virtual void executeTask(); - virtual bool canAbort() const; - virtual bool abort(); + virtual void executeTask(); + virtual bool canAbort() const; + virtual bool abort(); private: - QStringList m_lines; - MessageLevel::Enum m_level; + QStringList m_lines; + MessageLevel::Enum m_level; }; diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp index d057febb..6eb012f7 100644 --- a/api/logic/launch/steps/Update.cpp +++ b/api/logic/launch/steps/Update.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,63 +18,63 @@ void Update::executeTask() { - if(m_aborted) - { - emitFailed(tr("Task aborted.")); - return; - } - m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); - if(m_updateTask) - { - connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); - connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); - connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); - emit progressReportingRequest(); - return; - } - emitSucceeded(); + if(m_aborted) + { + emitFailed(tr("Task aborted.")); + return; + } + m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); + if(m_updateTask) + { + connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished())); + connect(m_updateTask.get(), &Task::progress, this, &Task::setProgress); + connect(m_updateTask.get(), &Task::status, this, &Task::setStatus); + emit progressReportingRequest(); + return; + } + emitSucceeded(); } void Update::proceed() { - m_updateTask->start(); + m_updateTask->start(); } void Update::updateFinished() { - if(m_updateTask->wasSuccessful()) - { - m_updateTask.reset(); - emitSucceeded(); - } - else - { - QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason()); - m_updateTask.reset(); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - } + if(m_updateTask->wasSuccessful()) + { + m_updateTask.reset(); + emitSucceeded(); + } + else + { + QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason()); + m_updateTask.reset(); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } } bool Update::canAbort() const { - if(m_updateTask) - { - return m_updateTask->canAbort(); - } - return true; + if(m_updateTask) + { + return m_updateTask->canAbort(); + } + return true; } bool Update::abort() { - m_aborted = true; - if(m_updateTask) - { - if(m_updateTask->canAbort()) - { - return m_updateTask->abort(); - } - } - return true; + m_aborted = true; + if(m_updateTask) + { + if(m_updateTask->canAbort()) + { + return m_updateTask->abort(); + } + } + return true; } diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h index 7a14011a..331698bd 100644 --- a/api/logic/launch/steps/Update.h +++ b/api/logic/launch/steps/Update.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,22 +24,22 @@ // FIXME: stupid. should be defined by the instance type? or even completely abstracted away... class Update: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; - virtual ~Update() {}; + explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {}; + virtual ~Update() {}; - void executeTask() override; - bool canAbort() const override; - void proceed() override; + void executeTask() override; + bool canAbort() const override; + void proceed() override; public slots: - bool abort() override; + bool abort() override; private slots: - void updateFinished(); + void updateFinished(); private: - shared_qobject_ptr m_updateTask; - bool m_aborted = false; - Net::Mode m_mode = Net::Mode::Offline; + shared_qobject_ptr m_updateTask; + bool m_aborted = false; + Net::Mode m_mode = Net::Mode::Offline; }; diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp index 5c2339cb..ce0be859 100644 --- a/api/logic/meta/BaseEntity.cpp +++ b/api/logic/meta/BaseEntity.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,45 +27,45 @@ class ParsingValidator : public Net::Validator { public: /* con/des */ - ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity) - { - }; - virtual ~ParsingValidator() - { - }; + ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity) + { + }; + virtual ~ParsingValidator() + { + }; public: /* methods */ - bool init(QNetworkRequest &) override - { - return true; - } - bool write(QByteArray & data) override - { - this->data.append(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - auto fname = m_entity->localFilename(); - try - { - m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname)); - return true; - } - catch (Exception &e) - { - qWarning() << "Unable to parse response:" << e.cause(); - return false; - } - } + bool init(QNetworkRequest &) override + { + return true; + } + bool write(QByteArray & data) override + { + this->data.append(data); + return true; + } + bool abort() override + { + return true; + } + bool validate(QNetworkReply &) override + { + auto fname = m_entity->localFilename(); + try + { + m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname)); + return true; + } + catch (const Exception &e) + { + qWarning() << "Unable to parse response:" << e.cause(); + return false; + } + } private: /* data */ - QByteArray data; - Meta::BaseEntity *m_entity; + QByteArray data; + Meta::BaseEntity *m_entity; }; Meta::BaseEntity::~BaseEntity() @@ -74,89 +74,89 @@ Meta::BaseEntity::~BaseEntity() QUrl Meta::BaseEntity::url() const { - return QUrl("https://v1.meta.multimc.org").resolved(localFilename()); + return QUrl("https://meta.multimc.org/v1/").resolved(localFilename()); } bool Meta::BaseEntity::loadLocalFile() { - const QString fname = QDir("meta").absoluteFilePath(localFilename()); - if (!QFile::exists(fname)) - { - return false; - } - // TODO: check if the file has the expected checksum - try - { - parse(Json::requireObject(Json::requireDocument(fname, fname), fname)); - return true; - } - catch (Exception &e) - { - qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); - // just make sure it's gone and we never consider it again. - QFile::remove(fname); - return false; - } + const QString fname = QDir("meta").absoluteFilePath(localFilename()); + if (!QFile::exists(fname)) + { + return false; + } + // TODO: check if the file has the expected checksum + try + { + parse(Json::requireObject(Json::requireDocument(fname, fname), fname)); + return true; + } + catch (const Exception &e) + { + qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause()); + // just make sure it's gone and we never consider it again. + QFile::remove(fname); + return false; + } } void Meta::BaseEntity::load(Net::Mode loadType) { - // load local file if nothing is loaded yet - if(!isLoaded()) - { - if(loadLocalFile()) - { - m_loadStatus = LoadStatus::Local; - } - } - // if we need remote update, run the update task - if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) - { - return; - } - NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename())); - auto url = this->url(); - auto entry = ENV.metacache()->resolveEntry("meta", localFilename()); - entry->setStale(true); - auto dl = Net::Download::makeCached(url, entry); - /* - * The validator parses the file and loads it into the object. - * If that fails, the file is not written to storage. - */ - dl->addValidator(new ParsingValidator(this)); - job->addNetAction(dl); - m_updateStatus = UpdateStatus::InProgress; - m_updateTask.reset(job); - QObject::connect(job, &NetJob::succeeded, [&]() - { - m_loadStatus = LoadStatus::Remote; - m_updateStatus = UpdateStatus::Succeeded; - m_updateTask.reset(); - }); - QObject::connect(job, &NetJob::failed, [&]() - { - m_updateStatus = UpdateStatus::Failed; - m_updateTask.reset(); - }); - m_updateTask->start(); + // load local file if nothing is loaded yet + if(!isLoaded()) + { + if(loadLocalFile()) + { + m_loadStatus = LoadStatus::Local; + } + } + // if we need remote update, run the update task + if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate()) + { + return; + } + NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename())); + auto url = this->url(); + auto entry = ENV.metacache()->resolveEntry("meta", localFilename()); + entry->setStale(true); + auto dl = Net::Download::makeCached(url, entry); + /* + * The validator parses the file and loads it into the object. + * If that fails, the file is not written to storage. + */ + dl->addValidator(new ParsingValidator(this)); + job->addNetAction(dl); + m_updateStatus = UpdateStatus::InProgress; + m_updateTask.reset(job); + QObject::connect(job, &NetJob::succeeded, [&]() + { + m_loadStatus = LoadStatus::Remote; + m_updateStatus = UpdateStatus::Succeeded; + m_updateTask.reset(); + }); + QObject::connect(job, &NetJob::failed, [&]() + { + m_updateStatus = UpdateStatus::Failed; + m_updateTask.reset(); + }); + m_updateTask->start(); } bool Meta::BaseEntity::isLoaded() const { - return m_loadStatus > LoadStatus::NotLoaded; + return m_loadStatus > LoadStatus::NotLoaded; } bool Meta::BaseEntity::shouldStartRemoteUpdate() const { - // TODO: version-locks and offline mode? - return m_updateStatus != UpdateStatus::InProgress; + // TODO: version-locks and offline mode? + return m_updateStatus != UpdateStatus::InProgress; } shared_qobject_ptr Meta::BaseEntity::getCurrentTask() { - if(m_updateStatus == UpdateStatus::InProgress) - { - return m_updateTask; - } - return nullptr; + if(m_updateStatus == UpdateStatus::InProgress) + { + return m_updateTask; + } + return nullptr; } diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h index 418c979f..5fc5981a 100644 --- a/api/logic/meta/BaseEntity.h +++ b/api/logic/meta/BaseEntity.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,41 +28,41 @@ namespace Meta class MULTIMC_LOGIC_EXPORT BaseEntity { public: /* types */ - using Ptr = std::shared_ptr; - enum class LoadStatus - { - NotLoaded, - Local, - Remote - }; - enum class UpdateStatus - { - NotDone, - InProgress, - Failed, - Succeeded - }; + using Ptr = std::shared_ptr; + enum class LoadStatus + { + NotLoaded, + Local, + Remote + }; + enum class UpdateStatus + { + NotDone, + InProgress, + Failed, + Succeeded + }; public: - virtual ~BaseEntity(); + virtual ~BaseEntity(); - virtual void parse(const QJsonObject &obj) = 0; + virtual void parse(const QJsonObject &obj) = 0; - virtual QString localFilename() const = 0; - virtual QUrl url() const; + virtual QString localFilename() const = 0; + virtual QUrl url() const; - bool isLoaded() const; - bool shouldStartRemoteUpdate() const; + bool isLoaded() const; + bool shouldStartRemoteUpdate() const; - void load(Net::Mode loadType); - shared_qobject_ptr getCurrentTask(); + void load(Net::Mode loadType); + shared_qobject_ptr getCurrentTask(); protected: /* methods */ - bool loadLocalFile(); + bool loadLocalFile(); private: - LoadStatus m_loadStatus = LoadStatus::NotLoaded; - UpdateStatus m_updateStatus = UpdateStatus::NotDone; - shared_qobject_ptr m_updateTask; + LoadStatus m_loadStatus = LoadStatus::NotLoaded; + UpdateStatus m_updateStatus = UpdateStatus::NotDone; + shared_qobject_ptr m_updateTask; }; } diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp index 6e1e34cd..4e0026b3 100644 --- a/api/logic/meta/Index.cpp +++ b/api/logic/meta/Index.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,128 +21,128 @@ namespace Meta { Index::Index(QObject *parent) - : QAbstractListModel(parent) + : QAbstractListModel(parent) { } Index::Index(const QVector &lists, QObject *parent) - : QAbstractListModel(parent), m_lists(lists) + : QAbstractListModel(parent), m_lists(lists) { - for (int i = 0; i < m_lists.size(); ++i) - { - m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); - connectVersionList(i, m_lists.at(i)); - } + for (int i = 0; i < m_lists.size(); ++i) + { + m_uids.insert(m_lists.at(i)->uid(), m_lists.at(i)); + connectVersionList(i, m_lists.at(i)); + } } QVariant Index::data(const QModelIndex &index, int role) const { - if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size()) - { - return QVariant(); - } + if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size()) + { + return QVariant(); + } - VersionListPtr list = m_lists.at(index.row()); - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case 0: return list->humanReadable(); - default: break; - } - case UidRole: return list->uid(); - case NameRole: return list->name(); - case ListPtrRole: return QVariant::fromValue(list); - } - return QVariant(); + VersionListPtr list = m_lists.at(index.row()); + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case 0: return list->humanReadable(); + default: break; + } + case UidRole: return list->uid(); + case NameRole: return list->name(); + case ListPtrRole: return QVariant::fromValue(list); + } + return QVariant(); } int Index::rowCount(const QModelIndex &parent) const { - return m_lists.size(); + return m_lists.size(); } int Index::columnCount(const QModelIndex &parent) const { - return 1; + return 1; } QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) - { - return tr("Name"); - } - else - { - return QVariant(); - } + if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) + { + return tr("Name"); + } + else + { + return QVariant(); + } } bool Index::hasUid(const QString &uid) const { - return m_uids.contains(uid); + return m_uids.contains(uid); } VersionListPtr Index::get(const QString &uid) { - VersionListPtr out = m_uids.value(uid, nullptr); - if(!out) - { - out = std::make_shared(uid); - m_uids[uid] = out; - } - return out; + VersionListPtr out = m_uids.value(uid, nullptr); + if(!out) + { + out = std::make_shared(uid); + m_uids[uid] = out; + } + return out; } VersionPtr Index::get(const QString &uid, const QString &version) { - auto list = get(uid); - return list->getVersion(version); + auto list = get(uid); + return list->getVersion(version); } void Index::parse(const QJsonObject& obj) { - parseIndex(obj, this); + parseIndex(obj, this); } void Index::merge(const std::shared_ptr &other) { - const QVector lists = std::dynamic_pointer_cast(other)->m_lists; - // initial load, no need to merge - if (m_lists.isEmpty()) - { - beginResetModel(); - m_lists = lists; - for (int i = 0; i < lists.size(); ++i) - { - m_uids.insert(lists.at(i)->uid(), lists.at(i)); - connectVersionList(i, lists.at(i)); - } - endResetModel(); - } - else - { - for (const VersionListPtr &list : lists) - { - if (m_uids.contains(list->uid())) - { - m_uids[list->uid()]->mergeFromIndex(list); - } - else - { - beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size()); - connectVersionList(m_lists.size(), list); - m_lists.append(list); - m_uids.insert(list->uid(), list); - endInsertRows(); - } - } - } + const QVector lists = std::dynamic_pointer_cast(other)->m_lists; + // initial load, no need to merge + if (m_lists.isEmpty()) + { + beginResetModel(); + m_lists = lists; + for (int i = 0; i < lists.size(); ++i) + { + m_uids.insert(lists.at(i)->uid(), lists.at(i)); + connectVersionList(i, lists.at(i)); + } + endResetModel(); + } + else + { + for (const VersionListPtr &list : lists) + { + if (m_uids.contains(list->uid())) + { + m_uids[list->uid()]->mergeFromIndex(list); + } + else + { + beginInsertRows(QModelIndex(), m_lists.size(), m_lists.size()); + connectVersionList(m_lists.size(), list); + m_lists.append(list); + m_uids.insert(list->uid(), list); + endInsertRows(); + } + } + } } void Index::connectVersionList(const int row, const VersionListPtr &list) { - connect(list.get(), &VersionList::nameChanged, this, [this, row]() - { - emit dataChanged(index(row), index(row), QVector() << Qt::DisplayRole); - }); + connect(list.get(), &VersionList::nameChanged, this, [this, row]() + { + emit dataChanged(index(row), index(row), QVector() << Qt::DisplayRole); + }); } } diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h index 0ec43f96..7a84ff38 100644 --- a/api/logic/meta/Index.h +++ b/api/logic/meta/Index.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,41 +31,41 @@ using VersionPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity { - Q_OBJECT + Q_OBJECT public: - explicit Index(QObject *parent = nullptr); - explicit Index(const QVector &lists, QObject *parent = nullptr); + explicit Index(QObject *parent = nullptr); + explicit Index(const QVector &lists, QObject *parent = nullptr); - enum - { - UidRole = Qt::UserRole, - NameRole, - ListPtrRole - }; + enum + { + UidRole = Qt::UserRole, + NameRole, + ListPtrRole + }; - QVariant data(const QModelIndex &index, int role) const override; - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QString localFilename() const override { return "index.json"; } + QString localFilename() const override { return "index.json"; } - // queries - VersionListPtr get(const QString &uid); - VersionPtr get(const QString &uid, const QString &version); - bool hasUid(const QString &uid) const; + // queries + VersionListPtr get(const QString &uid); + VersionPtr get(const QString &uid, const QString &version); + bool hasUid(const QString &uid) const; - QVector lists() const { return m_lists; } + QVector lists() const { return m_lists; } public: // for usage by parsers only - void merge(const std::shared_ptr &other); - void parse(const QJsonObject &obj) override; + void merge(const std::shared_ptr &other); + void parse(const QJsonObject &obj) override; private: - QVector m_lists; - QHash m_uids; + QVector m_lists; + QHash m_uids; - void connectVersionList(const int row, const VersionListPtr &list); + void connectVersionList(const int row, const VersionListPtr &list); }; } diff --git a/api/logic/meta/Index_test.cpp b/api/logic/meta/Index_test.cpp index 1c5face2..b0892070 100644 --- a/api/logic/meta/Index_test.cpp +++ b/api/logic/meta/Index_test.cpp @@ -7,36 +7,36 @@ class IndexTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_isProvidedByEnv() - { - QVERIFY(ENV.metadataIndex()); - QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex()); - } + void test_isProvidedByEnv() + { + QVERIFY(ENV.metadataIndex()); + QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex()); + } - void test_hasUid_and_getList() - { - Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); - QVERIFY(windex.hasUid("list1")); - QVERIFY(!windex.hasUid("asdf")); - QVERIFY(windex.get("list2") != nullptr); - QCOMPARE(windex.get("list2")->uid(), QString("list2")); - QVERIFY(windex.get("adsf") != nullptr); - } + void test_hasUid_and_getList() + { + Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); + QVERIFY(windex.hasUid("list1")); + QVERIFY(!windex.hasUid("asdf")); + QVERIFY(windex.get("list2") != nullptr); + QCOMPARE(windex.get("list2")->uid(), QString("list2")); + QVERIFY(windex.get("adsf") != nullptr); + } - void test_merge() - { - Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}))); - QCOMPARE(windex.lists().size(), 3); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list4"), std::make_shared("list2"), std::make_shared("list5")}))); - QCOMPARE(windex.lists().size(), 5); - windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list6")}))); - QCOMPARE(windex.lists().size(), 6); - } + void test_merge() + { + Meta::Index windex({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}); + QCOMPARE(windex.lists().size(), 3); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list1"), std::make_shared("list2"), std::make_shared("list3")}))); + QCOMPARE(windex.lists().size(), 3); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list4"), std::make_shared("list2"), std::make_shared("list5")}))); + QCOMPARE(windex.lists().size(), 5); + windex.merge(std::shared_ptr(new Meta::Index({std::make_shared("list6")}))); + QCOMPARE(windex.lists().size(), 6); + } }; QTEST_GUILESS_MAIN(IndexTest) diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp index 2183e579..41abb8e9 100644 --- a/api/logic/meta/JsonFormat.cpp +++ b/api/logic/meta/JsonFormat.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,141 +30,141 @@ namespace Meta MetadataVersion currentFormatVersion() { - return MetadataVersion::InitialRelease; + return MetadataVersion::InitialRelease; } // Index static std::shared_ptr parseIndexInternal(const QJsonObject &obj) { - const QVector objects = requireIsArrayOf(obj, "packages"); - QVector lists; - lists.reserve(objects.size()); - std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) - { - VersionListPtr list = std::make_shared(requireString(obj, "uid")); - list->setName(ensureString(obj, "name", QString())); - return list; - }); - return std::make_shared(lists); + const QVector objects = requireIsArrayOf(obj, "packages"); + QVector lists; + lists.reserve(objects.size()); + std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj) + { + VersionListPtr list = std::make_shared(requireString(obj, "uid")); + list->setName(ensureString(obj, "name", QString())); + return list; + }); + return std::make_shared(lists); } // Version static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj) { - VersionPtr version = std::make_shared(uid, requireString(obj, "version")); - version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); - version->setType(ensureString(obj, "type", QString())); - version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); - version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); - RequireSet requires, conflicts; - parseRequires(obj, &requires, "requires"); - parseRequires(obj, &conflicts, "conflicts"); - version->setRequires(requires, conflicts); - return version; + VersionPtr version = std::make_shared(uid, requireString(obj, "version")); + version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000); + version->setType(ensureString(obj, "type", QString())); + version->setRecommended(ensureBoolean(obj, QString("recommended"), false)); + version->setVolatile(ensureBoolean(obj, QString("volatile"), false)); + RequireSet requires, conflicts; + parseRequires(obj, &requires, "requires"); + parseRequires(obj, &conflicts, "conflicts"); + version->setRequires(requires, conflicts); + return version; } static std::shared_ptr parseVersionInternal(const QJsonObject &obj) { - VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); + VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj); - version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), - QString("%1/%2.json").arg(version->uid(), version->version()), - obj.contains("order"))); - return version; + version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj), + QString("%1/%2.json").arg(version->uid(), version->version()), + obj.contains("order"))); + return version; } // Version list / package static std::shared_ptr parseVersionListInternal(const QJsonObject &obj) { - const QString uid = requireString(obj, "uid"); - - const QVector versionsRaw = requireIsArrayOf(obj, "versions"); - QVector versions; - versions.reserve(versionsRaw.size()); - std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) - { - auto version = parseCommonVersion(uid, vObj); - version->setProvidesRecommendations(); - return version; - }); - - VersionListPtr list = std::make_shared(uid); - list->setName(ensureString(obj, "name", QString())); - list->setVersions(versions); - return list; + const QString uid = requireString(obj, "uid"); + + const QVector versionsRaw = requireIsArrayOf(obj, "versions"); + QVector versions; + versions.reserve(versionsRaw.size()); + std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj) + { + auto version = parseCommonVersion(uid, vObj); + version->setProvidesRecommendations(); + return version; + }); + + VersionListPtr list = std::make_shared(uid); + list->setName(ensureString(obj, "name", QString())); + list->setVersions(versions); + return list; } MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required) { - if (!obj.contains("formatVersion")) - { - if(required) - { - return MetadataVersion::Invalid; - } - return MetadataVersion::InitialRelease; - } - if (!obj.value("formatVersion").isDouble()) - { - return MetadataVersion::Invalid; - } - switch(obj.value("formatVersion").toInt()) - { - case 0: - case 1: - return MetadataVersion::InitialRelease; - default: - return MetadataVersion::Invalid; - } + if (!obj.contains("formatVersion")) + { + if(required) + { + return MetadataVersion::Invalid; + } + return MetadataVersion::InitialRelease; + } + if (!obj.value("formatVersion").isDouble()) + { + return MetadataVersion::Invalid; + } + switch(obj.value("formatVersion").toInt()) + { + case 0: + case 1: + return MetadataVersion::InitialRelease; + default: + return MetadataVersion::Invalid; + } } void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version) { - if(version == MetadataVersion::Invalid) - { - return; - } - obj.insert("formatVersion", int(version)); + if(version == MetadataVersion::Invalid) + { + return; + } + obj.insert("formatVersion", int(version)); } void parseIndex(const QJsonObject &obj, Index *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseIndexInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseIndexInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } void parseVersionList(const QJsonObject &obj, VersionList *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionListInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionListInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } void parseVersion(const QJsonObject &obj, Version *ptr) { - const MetadataVersion version = parseFormatVersion(obj); - switch (version) - { - case MetadataVersion::InitialRelease: - ptr->merge(parseVersionInternal(obj)); - break; - case MetadataVersion::Invalid: - throw ParseException(QObject::tr("Unknown format version!")); - } + const MetadataVersion version = parseFormatVersion(obj); + switch (version) + { + case MetadataVersion::InitialRelease: + ptr->merge(parseVersionInternal(obj)); + break; + case MetadataVersion::Invalid: + throw ParseException(QObject::tr("Unknown format version!")); + } } /* @@ -174,44 +174,44 @@ void parseVersion(const QJsonObject &obj, Version *ptr) */ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName) { - if(obj.contains(keyName)) - { - QSet requires; - auto reqArray = requireArray(obj, keyName); - auto iter = reqArray.begin(); - while(iter != reqArray.end()) - { - auto reqObject = requireObject(*iter); - auto uid = requireString(reqObject, "uid"); - auto equals = ensureString(reqObject, "equals", QString()); - auto suggests = ensureString(reqObject, "suggests", QString()); - ptr->insert({uid, equals, suggests}); - iter++; - } - } + if(obj.contains(keyName)) + { + QSet requires; + auto reqArray = requireArray(obj, keyName); + auto iter = reqArray.begin(); + while(iter != reqArray.end()) + { + auto reqObject = requireObject(*iter); + auto uid = requireString(reqObject, "uid"); + auto equals = ensureString(reqObject, "equals", QString()); + auto suggests = ensureString(reqObject, "suggests", QString()); + ptr->insert({uid, equals, suggests}); + iter++; + } + } } void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName) { - if(!ptr || ptr->empty()) - { - return; - } - QJsonArray arrOut; - for(auto &iter: *ptr) - { - QJsonObject reqOut; - reqOut.insert("uid", iter.uid); - if(!iter.equalsVersion.isEmpty()) - { - reqOut.insert("equals", iter.equalsVersion); - } - if(!iter.suggests.isEmpty()) - { - reqOut.insert("suggests", iter.suggests); - } - arrOut.append(reqOut); - } - obj.insert(keyName, arrOut); + if(!ptr || ptr->empty()) + { + return; + } + QJsonArray arrOut; + for(auto &iter: *ptr) + { + QJsonObject reqOut; + reqOut.insert("uid", iter.uid); + if(!iter.equalsVersion.isEmpty()) + { + reqOut.insert("equals", iter.equalsVersion); + } + if(!iter.suggests.isEmpty()) + { + reqOut.insert("suggests", iter.suggests); + } + arrOut.append(reqOut); + } + obj.insert(keyName, arrOut); } } diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h index 762a36f6..06732ffd 100644 --- a/api/logic/meta/JsonFormat.h +++ b/api/logic/meta/JsonFormat.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,39 +30,39 @@ class VersionList; enum class MetadataVersion { - Invalid = -1, - InitialRelease = 1 + Invalid = -1, + InitialRelease = 1 }; class ParseException : public Exception { public: - using Exception::Exception; + using Exception::Exception; }; struct Require { - bool operator==(const Require & rhs) const - { - return uid == rhs.uid; - } - bool operator<(const Require & rhs) const - { - return uid < rhs.uid; - } - bool deepEquals(const Require & rhs) const - { - return uid == rhs.uid - && equalsVersion == rhs.equalsVersion - && suggests == rhs.suggests; - } - QString uid; - QString equalsVersion; - QString suggests; + bool operator==(const Require & rhs) const + { + return uid == rhs.uid; + } + bool operator<(const Require & rhs) const + { + return uid < rhs.uid; + } + bool deepEquals(const Require & rhs) const + { + return uid == rhs.uid + && equalsVersion == rhs.equalsVersion + && suggests == rhs.suggests; + } + QString uid; + QString equalsVersion; + QString suggests; }; inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW { - return qHash(key.uid, seed); + return qHash(key.uid, seed); } using RequireSet = std::set; @@ -80,4 +80,4 @@ void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyNa MetadataVersion currentFormatVersion(); } -Q_DECLARE_METATYPE(std::set); \ No newline at end of file +Q_DECLARE_METATYPE(std::set) diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp index edc70f33..ca889550 100644 --- a/api/logic/meta/Version.cpp +++ b/api/logic/meta/Version.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ #include "minecraft/ComponentList.h" Meta::Version::Version(const QString &uid, const QString &version) - : BaseVersion(), m_uid(uid), m_version(version) + : BaseVersion(), m_uid(uid), m_version(version) { } @@ -31,110 +31,110 @@ Meta::Version::~Version() QString Meta::Version::descriptor() { - return m_version; + return m_version; } QString Meta::Version::name() { - if(m_data) - return m_data->name; - return m_uid; + if(m_data) + return m_data->name; + return m_uid; } QString Meta::Version::typeString() const { - return m_type; + return m_type; } QDateTime Meta::Version::time() const { - return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC); + return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC); } void Meta::Version::parse(const QJsonObject& obj) { - parseVersion(obj, this); + parseVersion(obj, this); } void Meta::Version::mergeFromList(const Meta::VersionPtr& other) { - if(other->m_providesRecommendations) - { - if(m_recommended != other->m_recommended) - { - setRecommended(other->m_recommended); - } - } - if (m_type != other->m_type) - { - setType(other->m_type); - } - if (m_time != other->m_time) - { - setTime(other->m_time); - } - if (m_requires != other->m_requires) - { - m_requires = other->m_requires; - } - if (m_conflicts != other->m_conflicts) - { - m_conflicts = other->m_conflicts; - } - if(m_volatile != other->m_volatile) - { - setVolatile(other->m_volatile); - } + if(other->m_providesRecommendations) + { + if(m_recommended != other->m_recommended) + { + setRecommended(other->m_recommended); + } + } + if (m_type != other->m_type) + { + setType(other->m_type); + } + if (m_time != other->m_time) + { + setTime(other->m_time); + } + if (m_requires != other->m_requires) + { + m_requires = other->m_requires; + } + if (m_conflicts != other->m_conflicts) + { + m_conflicts = other->m_conflicts; + } + if(m_volatile != other->m_volatile) + { + setVolatile(other->m_volatile); + } } void Meta::Version::merge(const VersionPtr &other) { - mergeFromList(other); - if(other->m_data) - { - setData(other->m_data); - } + mergeFromList(other); + if(other->m_data) + { + setData(other->m_data); + } } QString Meta::Version::localFilename() const { - return m_uid + '/' + m_version + ".json"; + return m_uid + '/' + m_version + ".json"; } void Meta::Version::setType(const QString &type) { - m_type = type; - emit typeChanged(); + m_type = type; + emit typeChanged(); } void Meta::Version::setTime(const qint64 time) { - m_time = time; - emit timeChanged(); + m_time = time; + emit timeChanged(); } void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts) { - m_requires = requires; - m_conflicts = conflicts; - emit requiresChanged(); + m_requires = requires; + m_conflicts = conflicts; + emit requiresChanged(); } void Meta::Version::setVolatile(bool volatile_) { - m_volatile = volatile_; + m_volatile = volatile_; } void Meta::Version::setData(const VersionFilePtr &data) { - m_data = data; + m_data = data; } void Meta::Version::setProvidesRecommendations() { - m_providesRecommendations = true; + m_providesRecommendations = true; } void Meta::Version::setRecommended(bool recommended) { - m_recommended = recommended; + m_recommended = recommended; } diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h index 33bd5b35..d902049a 100644 --- a/api/logic/meta/Version.h +++ b/api/logic/meta/Version.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,82 +36,82 @@ using VersionPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity { - Q_OBJECT + Q_OBJECT public: /* con/des */ - explicit Version(const QString &uid, const QString &version); - virtual ~Version(); - - QString descriptor() override; - QString name() override; - QString typeString() const override; - - QString uid() const - { - return m_uid; - } - QString version() const - { - return m_version; - } - QString type() const - { - return m_type; - } - QDateTime time() const; - qint64 rawTime() const - { - return m_time; - } - const Meta::RequireSet &requires() const - { - return m_requires; - } - VersionFilePtr data() const - { - return m_data; - } - bool isRecommended() const - { - return m_recommended; - } - bool isLoaded() const - { - return m_data != nullptr; - } - - void merge(const VersionPtr &other); - void mergeFromList(const VersionPtr &other); - void parse(const QJsonObject &obj) override; - - QString localFilename() const override; + explicit Version(const QString &uid, const QString &version); + virtual ~Version(); + + QString descriptor() override; + QString name() override; + QString typeString() const override; + + QString uid() const + { + return m_uid; + } + QString version() const + { + return m_version; + } + QString type() const + { + return m_type; + } + QDateTime time() const; + qint64 rawTime() const + { + return m_time; + } + const Meta::RequireSet &requires() const + { + return m_requires; + } + VersionFilePtr data() const + { + return m_data; + } + bool isRecommended() const + { + return m_recommended; + } + bool isLoaded() const + { + return m_data != nullptr; + } + + void merge(const VersionPtr &other); + void mergeFromList(const VersionPtr &other); + void parse(const QJsonObject &obj) override; + + QString localFilename() const override; public: // for usage by format parsers only - void setType(const QString &type); - void setTime(const qint64 time); - void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); - void setVolatile(bool volatile_); - void setRecommended(bool recommended); - void setProvidesRecommendations(); - void setData(const VersionFilePtr &data); + void setType(const QString &type); + void setTime(const qint64 time); + void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts); + void setVolatile(bool volatile_); + void setRecommended(bool recommended); + void setProvidesRecommendations(); + void setData(const VersionFilePtr &data); signals: - void typeChanged(); - void timeChanged(); - void requiresChanged(); + void typeChanged(); + void timeChanged(); + void requiresChanged(); private: - bool m_providesRecommendations = false; - bool m_recommended = false; - QString m_name; - QString m_uid; - QString m_version; - QString m_type; - qint64 m_time = 0; - Meta::RequireSet m_requires; - Meta::RequireSet m_conflicts; - bool m_volatile = false; - VersionFilePtr m_data; + bool m_providesRecommendations = false; + bool m_recommended = false; + QString m_name; + QString m_uid; + QString m_version; + QString m_type; + qint64 m_time = 0; + Meta::RequireSet m_requires; + Meta::RequireSet m_conflicts; + bool m_volatile = false; + VersionFilePtr m_data; }; } diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp index 9ae02301..cc15d0e4 100644 --- a/api/logic/meta/VersionList.cpp +++ b/api/logic/meta/VersionList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,222 +24,222 @@ namespace Meta { VersionList::VersionList(const QString &uid, QObject *parent) - : BaseVersionList(parent), m_uid(uid) + : BaseVersionList(parent), m_uid(uid) { - setObjectName("Version list: " + uid); + setObjectName("Version list: " + uid); } shared_qobject_ptr VersionList::getLoadTask() { - load(Net::Mode::Online); - return getCurrentTask(); + load(Net::Mode::Online); + return getCurrentTask(); } bool VersionList::isLoaded() { - return BaseEntity::isLoaded(); + return BaseEntity::isLoaded(); } const BaseVersionPtr VersionList::at(int i) const { - return m_versions.at(i); + return m_versions.at(i); } int VersionList::count() const { - return m_versions.size(); + return m_versions.size(); } void VersionList::sortVersions() { - beginResetModel(); - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return *a.get() < *b.get(); - }); - endResetModel(); + beginResetModel(); + std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + { + return *a.get() < *b.get(); + }); + endResetModel(); } QVariant VersionList::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid()) - { - return QVariant(); - } - - VersionPtr version = m_versions.at(index.row()); - - switch (role) - { - case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast(version)); - case VersionRole: - case VersionIdRole: - return version->version(); - case ParentVersionRole: - { - // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. - auto & reqs = version->requires(); - auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) - { - return req.uid == "net.minecraft"; - }); - if (iter != reqs.end()) - { - return (*iter).equalsVersion; - } - return QVariant(); - } - case TypeRole: return version->type(); - - case UidRole: return version->uid(); - case TimeRole: return version->time(); - case RequiresRole: return QVariant::fromValue(version->requires()); - case SortRole: return version->rawTime(); - case VersionPtrRole: return QVariant::fromValue(version); - case RecommendedRole: return version->isRecommended(); - // FIXME: this should be determined in whatever view/proxy is used... - // case LatestRole: return version == getLatestStable(); - default: return QVariant(); - } + if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid()) + { + return QVariant(); + } + + VersionPtr version = m_versions.at(index.row()); + + switch (role) + { + case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast(version)); + case VersionRole: + case VersionIdRole: + return version->version(); + case ParentVersionRole: + { + // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'. + auto & reqs = version->requires(); + auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req) + { + return req.uid == "net.minecraft"; + }); + if (iter != reqs.end()) + { + return (*iter).equalsVersion; + } + return QVariant(); + } + case TypeRole: return version->type(); + + case UidRole: return version->uid(); + case TimeRole: return version->time(); + case RequiresRole: return QVariant::fromValue(version->requires()); + case SortRole: return version->rawTime(); + case VersionPtrRole: return QVariant::fromValue(version); + case RecommendedRole: return version->isRecommended(); + // FIXME: this should be determined in whatever view/proxy is used... + // case LatestRole: return version == getLatestStable(); + default: return QVariant(); + } } BaseVersionList::RoleList VersionList::providesRoles() const { - return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, - TypeRole, UidRole, TimeRole, RequiresRole, SortRole, - RecommendedRole, LatestRole, VersionPtrRole}; + return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole, + TypeRole, UidRole, TimeRole, RequiresRole, SortRole, + RecommendedRole, LatestRole, VersionPtrRole}; } QHash VersionList::roleNames() const { - QHash roles = BaseVersionList::roleNames(); - roles.insert(UidRole, "uid"); - roles.insert(TimeRole, "time"); - roles.insert(SortRole, "sort"); - roles.insert(RequiresRole, "requires"); - return roles; + QHash roles = BaseVersionList::roleNames(); + roles.insert(UidRole, "uid"); + roles.insert(TimeRole, "time"); + roles.insert(SortRole, "sort"); + roles.insert(RequiresRole, "requires"); + return roles; } QString VersionList::localFilename() const { - return m_uid + "/index.json"; + return m_uid + "/index.json"; } QString VersionList::humanReadable() const { - return m_name.isEmpty() ? m_uid : m_name; + return m_name.isEmpty() ? m_uid : m_name; } VersionPtr VersionList::getVersion(const QString &version) { - VersionPtr out = m_lookup.value(version, nullptr); - if(!out) - { - out = std::make_shared(m_uid, version); - m_lookup[version] = out; - } - return out; + VersionPtr out = m_lookup.value(version, nullptr); + if(!out) + { + out = std::make_shared(m_uid, version); + m_lookup[version] = out; + } + return out; } void VersionList::setName(const QString &name) { - m_name = name; - emit nameChanged(name); + m_name = name; + emit nameChanged(name); } void VersionList::setVersions(const QVector &versions) { - beginResetModel(); - m_versions = versions; - std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) - { - return a->rawTime() > b->rawTime(); - }); - for (int i = 0; i < m_versions.size(); ++i) - { - m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i)); - setupAddedVersion(i, m_versions.at(i)); - } + beginResetModel(); + m_versions = versions; + std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b) + { + return a->rawTime() > b->rawTime(); + }); + for (int i = 0; i < m_versions.size(); ++i) + { + m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i)); + setupAddedVersion(i, m_versions.at(i)); + } - // FIXME: this is dumb, we have 'recommended' as part of the metadata already... - auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); - m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; - endResetModel(); + // FIXME: this is dumb, we have 'recommended' as part of the metadata already... + auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; }); + m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt; + endResetModel(); } void VersionList::parse(const QJsonObject& obj) { - parseVersionList(obj, this); + parseVersionList(obj, this); } // FIXME: this is dumb, we have 'recommended' as part of the metadata already... static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b) { - if(!a) - return b; - if(!b) - return a; - if(a->type() == b->type()) - { - // newer of same type wins - return (a->rawTime() > b->rawTime() ? a : b); - } - // 'release' type wins - return (a->type() == "release" ? a : b); + if(!a) + return b; + if(!b) + return a; + if(a->type() == b->type()) + { + // newer of same type wins + return (a->rawTime() > b->rawTime() ? a : b); + } + // 'release' type wins + return (a->type() == "release" ? a : b); } void VersionList::mergeFromIndex(const VersionListPtr &other) { - if (m_name != other->m_name) - { - setName(other->m_name); - } + if (m_name != other->m_name) + { + setName(other->m_name); + } } void VersionList::merge(const VersionListPtr &other) { - if (m_name != other->m_name) - { - setName(other->m_name); - } - - // TODO: do not reset the whole model. maybe? - beginResetModel(); - m_versions.clear(); - if(other->m_versions.isEmpty()) - { - qWarning() << "Empty list loaded ..."; - } - for (const VersionPtr &version : other->m_versions) - { - // we already have the version. merge the contents - if (m_lookup.contains(version->version())) - { - m_lookup.value(version->version())->mergeFromList(version); - } - else - { - m_lookup.insert(version->uid(), version); - } - // connect it. - setupAddedVersion(m_versions.size(), version); - m_versions.append(version); - m_recommended = getBetterVersion(m_recommended, version); - } - endResetModel(); + if (m_name != other->m_name) + { + setName(other->m_name); + } + + // TODO: do not reset the whole model. maybe? + beginResetModel(); + m_versions.clear(); + if(other->m_versions.isEmpty()) + { + qWarning() << "Empty list loaded ..."; + } + for (const VersionPtr &version : other->m_versions) + { + // we already have the version. merge the contents + if (m_lookup.contains(version->version())) + { + m_lookup.value(version->version())->mergeFromList(version); + } + else + { + m_lookup.insert(version->uid(), version); + } + // connect it. + setupAddedVersion(m_versions.size(), version); + m_versions.append(version); + m_recommended = getBetterVersion(m_recommended, version); + } + endResetModel(); } void VersionList::setupAddedVersion(const int row, const VersionPtr &version) { - // FIXME: do not disconnect from everythin, disconnect only the lambdas here - version->disconnect(); - connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); - connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TimeRole << SortRole); }); - connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TypeRole); }); + // FIXME: do not disconnect from everythin, disconnect only the lambdas here + version->disconnect(); + connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << RequiresRole); }); + connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TimeRole << SortRole); }); + connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector() << TypeRole); }); } BaseVersionPtr VersionList::getRecommended() const { - return m_recommended; + return m_recommended; } } diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h index ad8733cc..24b01d08 100644 --- a/api/logic/meta/VersionList.h +++ b/api/logic/meta/VersionList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,75 +27,75 @@ using VersionListPtr = std::shared_ptr; class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity { - Q_OBJECT - Q_PROPERTY(QString uid READ uid CONSTANT) - Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_OBJECT + Q_PROPERTY(QString uid READ uid CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) public: - explicit VersionList(const QString &uid, QObject *parent = nullptr); - - enum Roles - { - UidRole = Qt::UserRole + 100, - TimeRole, - RequiresRole, - VersionPtrRole - }; - - shared_qobject_ptr getLoadTask() override; - bool isLoaded() override; - const BaseVersionPtr at(int i) const override; - int count() const override; - void sortVersions() override; - - BaseVersionPtr getRecommended() const override; - - QVariant data(const QModelIndex &index, int role) const override; - RoleList providesRoles() const override; - QHash roleNames() const override; - - QString localFilename() const override; - - QString uid() const - { - return m_uid; - } - QString name() const - { - return m_name; - } - QString humanReadable() const; - - VersionPtr getVersion(const QString &version); - - QVector versions() const - { - return m_versions; - } + explicit VersionList(const QString &uid, QObject *parent = nullptr); + + enum Roles + { + UidRole = Qt::UserRole + 100, + TimeRole, + RequiresRole, + VersionPtrRole + }; + + shared_qobject_ptr getLoadTask() override; + bool isLoaded() override; + const BaseVersionPtr at(int i) const override; + int count() const override; + void sortVersions() override; + + BaseVersionPtr getRecommended() const override; + + QVariant data(const QModelIndex &index, int role) const override; + RoleList providesRoles() const override; + QHash roleNames() const override; + + QString localFilename() const override; + + QString uid() const + { + return m_uid; + } + QString name() const + { + return m_name; + } + QString humanReadable() const; + + VersionPtr getVersion(const QString &version); + + QVector versions() const + { + return m_versions; + } public: // for usage only by parsers - void setName(const QString &name); - void setVersions(const QVector &versions); - void merge(const VersionListPtr &other); - void mergeFromIndex(const VersionListPtr &other); - void parse(const QJsonObject &obj) override; + void setName(const QString &name); + void setVersions(const QVector &versions); + void merge(const VersionListPtr &other); + void mergeFromIndex(const VersionListPtr &other); + void parse(const QJsonObject &obj) override; signals: - void nameChanged(const QString &name); + void nameChanged(const QString &name); protected slots: - void updateListData(QList) override - { - } + void updateListData(QList) override + { + } private: - QVector m_versions; - QHash m_lookup; - QString m_uid; - QString m_name; + QVector m_versions; + QHash m_lookup; + QString m_uid; + QString m_name; - VersionPtr m_recommended; + VersionPtr m_recommended; - void setupAddedVersion(const int row, const VersionPtr &version); + void setupAddedVersion(const int row, const VersionPtr &version); }; } Q_DECLARE_METATYPE(Meta::VersionListPtr) diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp index 5b25bede..f94f68b1 100644 --- a/api/logic/minecraft/AssetsUtils.cpp +++ b/api/logic/minecraft/AssetsUtils.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,34 @@ #include "FileSystem.h" #include "net/Download.h" #include "net/ChecksumValidator.h" +#include "net/URLConstants.h" + +namespace { +QSet collectPathsFromDir(QString dirPath) +{ + QFileInfo dirInfo(dirPath); + + if (!dirInfo.exists()) + { + return {}; + } + + QSet out; + + QDirIterator iter(dirPath, QDirIterator::Subdirectories); + while (iter.hasNext()) + { + QString value = iter.next(); + QFileInfo info(value); + if(info.isFile()) + { + out.insert(value); + qDebug() << value; + } + } + return out; +} +} namespace AssetsUtils @@ -36,202 +64,270 @@ namespace AssetsUtils * Returns true on success, with index populated * index is undefined otherwise */ -bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index) +bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index) +{ + /* + { + "objects": { + "icons/icon_16x16.png": { + "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", + "size": 3665 + }, + ... + } + } + } + */ + + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << "Failed to read assets index file" << path; + return false; + } + index.id = assetsId; + + // Read the file and close it. + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); + + // Fail if the JSON is invalid. + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << "Failed to parse assets index file:" << parseError.errorString() + << "at offset " << QString::number(parseError.offset); + return false; + } + + // Make sure the root is an object. + if (!jsonDoc.isObject()) + { + qCritical() << "Invalid assets index JSON: Root should be an array."; + return false; + } + + QJsonObject root = jsonDoc.object(); + + QJsonValue isVirtual = root.value("virtual"); + if (!isVirtual.isUndefined()) + { + index.isVirtual = isVirtual.toBool(false); + } + + QJsonValue mapToResources = root.value("map_to_resources"); + if (!mapToResources.isUndefined()) + { + index.mapToResources = mapToResources.toBool(false); + } + + QJsonValue objects = root.value("objects"); + QVariantMap map = objects.toVariant().toMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + // qDebug() << iter.key(); + + QVariant variant = iter.value(); + QVariantMap nested_objects = variant.toMap(); + + AssetObject object; + + for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); + nested_iter != nested_objects.end(); ++nested_iter) + { + // qDebug() << nested_iter.key() << nested_iter.value().toString(); + QString key = nested_iter.key(); + QVariant value = nested_iter.value(); + + if (key == "hash") + { + object.hash = value.toString(); + } + else if (key == "size") + { + object.size = value.toDouble(); + } + } + + index.objects.insert(iter.key(), object); + } + + return true; +} + +// FIXME: ugly code duplication +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder) { - /* - { - "objects": { - "icons/icon_16x16.png": { - "hash": "bdf48ef6b5d0d23bbb02e17d04865216179f510a", - "size": 3665 - }, - ... - } - } - } - */ - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << "Failed to read assets index file" << path; - return false; - } - index->id = assetsId; - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << "Failed to parse assets index file:" << parseError.errorString() - << "at offset " << QString::number(parseError.offset); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid assets index JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - QJsonValue isVirtual = root.value("virtual"); - if (!isVirtual.isUndefined()) - { - index->isVirtual = isVirtual.toBool(false); - } - - QJsonValue objects = root.value("objects"); - QVariantMap map = objects.toVariant().toMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - // qDebug() << iter.key(); - - QVariant variant = iter.value(); - QVariantMap nested_objects = variant.toMap(); - - AssetObject object; - - for (QVariantMap::const_iterator nested_iter = nested_objects.begin(); - nested_iter != nested_objects.end(); ++nested_iter) - { - // qDebug() << nested_iter.key() << nested_iter.value().toString(); - QString key = nested_iter.key(); - QVariant value = nested_iter.value(); - - if (key == "hash") - { - object.hash = value.toString(); - } - else if (key == "size") - { - object.size = value.toDouble(); - } - } - - index->objects.insert(iter.key(), object); - } - - return true; + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + qCritical() << "No assets index file" << indexPath << "; can't determine assets path!"; + return virtualRoot; + } + + AssetsIndex index; + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) + { + qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!"; + return virtualRoot; + } + + QString targetPath; + if(index.isVirtual) + { + return virtualRoot; + } + else if(index.mapToResources) + { + return QDir(resourcesFolder); + } + return virtualRoot; } -QDir reconstructAssets(QString assetsId) +// FIXME: ugly code duplication +bool reconstructAssets(QString assetsId, QString resourcesFolder) { - QDir assetsDir = QDir("assets/"); - QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); - QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); - QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); - - QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); - QFile indexFile(indexPath); - QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); - - if (!indexFile.exists()) - { - qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets"; - return virtualRoot; - } - - qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() - << objectDir.path() << virtualDir.path() << virtualRoot.path(); - - AssetsIndex index; - bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index); - - if (loadAssetsIndex && index.isVirtual) - { - qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path(); - - for (QString map : index.objects.keys()) - { - AssetObject asset_object = index.objects.value(map); - QString target_path = FS::PathCombine(virtualRoot.path(), map); - QFile target(target_path); - - QString tlk = asset_object.hash.left(2); - - QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); - QFile original(original_path); - if (!original.exists()) - continue; - if (!target.exists()) - { - QFileInfo info(target_path); - QDir target_dir = info.dir(); - // qDebug() << target_dir; - if (!target_dir.exists()) - QDir("").mkpath(target_dir.path()); - - bool couldCopy = original.copy(target_path); - qDebug() << " Copying" << original_path << "to" << target_path - << QString::number(couldCopy); // << original.errorString(); - } - } - - // TODO: Write last used time to virtualRoot/.lastused - } - - return virtualRoot; + QDir assetsDir = QDir("assets/"); + QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes")); + QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects")); + QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual")); + + QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json"); + QFile indexFile(indexPath); + QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId)); + + if (!indexFile.exists()) + { + qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + + qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path(); + + AssetsIndex index; + if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index)) + { + qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!"; + return false; + } + + QString targetPath; + bool removeLeftovers = false; + if(index.isVirtual) + { + targetPath = virtualRoot.path(); + removeLeftovers = true; + qDebug() << "Reconstructing virtual assets folder at" << targetPath; + } + else if(index.mapToResources) + { + targetPath = resourcesFolder; + qDebug() << "Reconstructing resources folder at" << targetPath; + } + + if (!targetPath.isNull()) + { + auto presentFiles = collectPathsFromDir(targetPath); + for (QString map : index.objects.keys()) + { + AssetObject asset_object = index.objects.value(map); + QString target_path = FS::PathCombine(targetPath, map); + QFile target(target_path); + + QString tlk = asset_object.hash.left(2); + + QString original_path = FS::PathCombine(objectDir.path(), tlk, asset_object.hash); + QFile original(original_path); + if (!original.exists()) + continue; + + presentFiles.remove(target_path); + + if (!target.exists()) + { + QFileInfo info(target_path); + QDir target_dir = info.dir(); + + qDebug() << target_dir.path(); + FS::ensureFolderPathExists(target_dir.path()); + + bool couldCopy = original.copy(target_path); + qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy); + } + } + + // TODO: Write last used time to virtualRoot/.lastused + if(removeLeftovers) + { + for(auto & file: presentFiles) + { + qDebug() << "Would remove" << file; + } + } + } + return true; } } NetActionPtr AssetObject::getDownloadAction() { - QFileInfo objectFile(getLocalPath()); - if ((!objectFile.isFile()) || (objectFile.size() != size)) - { - auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath()); - if(hash.size()) - { - auto rawHash = QByteArray::fromHex(hash.toLatin1()); - objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); - } - objectDL->m_total_progress = size; - return objectDL; - } - return nullptr; + QFileInfo objectFile(getLocalPath()); + if ((!objectFile.isFile()) || (objectFile.size() != size)) + { + auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath()); + if(hash.size()) + { + auto rawHash = QByteArray::fromHex(hash.toLatin1()); + objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); + } + objectDL->m_total_progress = size; + return objectDL; + } + return nullptr; } QString AssetObject::getLocalPath() { - return "assets/objects/" + getRelPath(); + return "assets/objects/" + getRelPath(); } QUrl AssetObject::getUrl() { - return QUrl("http://resources.download.minecraft.net/" + getRelPath()); + return URLConstants::RESOURCE_BASE + getRelPath(); } QString AssetObject::getRelPath() { - return hash.left(2) + "/" + hash; + return hash.left(2) + "/" + hash; } NetJobPtr AssetsIndex::getDownloadJob() { - auto job = new NetJob(QObject::tr("Assets for %1").arg(id)); - for (auto &object : objects.values()) - { - auto dl = object.getDownloadAction(); - if(dl) - { - job->addNetAction(dl); - } - } - if(job->size()) - return job; - return nullptr; + auto job = new NetJob(QObject::tr("Assets for %1").arg(id)); + for (auto &object : objects.values()) + { + auto dl = object.getDownloadAction(); + if(dl) + { + job->addNetAction(dl); + } + } + if(job->size()) + return job; + return nullptr; } diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h index b7ea9cc1..356b8c8a 100644 --- a/api/logic/minecraft/AssetsUtils.h +++ b/api/logic/minecraft/AssetsUtils.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,27 +22,32 @@ struct AssetObject { - QString getRelPath(); - QUrl getUrl(); - QString getLocalPath(); - NetActionPtr getDownloadAction(); + QString getRelPath(); + QUrl getUrl(); + QString getLocalPath(); + NetActionPtr getDownloadAction(); - QString hash; - qint64 size; + QString hash; + qint64 size; }; struct AssetsIndex { - NetJobPtr getDownloadJob(); + NetJobPtr getDownloadJob(); - QString id; - QMap objects; - bool isVirtual = false; + QString id; + QMap objects; + bool isVirtual = false; + bool mapToResources = false; }; +/// FIXME: this is absolutely horrendous. REDO!!!! namespace AssetsUtils { -bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index); +bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index); + +QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder); + /// Reconstruct a virtual assets folder for the given assets ID and return the folder -QDir reconstructAssets(QString assetsId); +bool reconstructAssets(QString assetsId, QString resourcesFolder); } diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp index 50a2ae16..51957d17 100644 --- a/api/logic/minecraft/Component.cpp +++ b/api/logic/minecraft/Component.cpp @@ -13,356 +13,356 @@ Component::Component(ComponentList * parent, const QString& uid) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_uid = uid; + m_uid = uid; } Component::Component(ComponentList * parent, std::shared_ptr version) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_metaVersion = version; - m_uid = version->uid(); - m_version = m_cachedVersion = version->version(); - m_cachedName = version->name(); - m_loaded = version->isLoaded(); + m_metaVersion = version; + m_uid = version->uid(); + m_version = m_cachedVersion = version->version(); + m_cachedName = version->name(); + m_loaded = version->isLoaded(); } Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr file) { - assert(parent); - m_parent = parent; + assert(parent); + m_parent = parent; - m_file = file; - m_uid = uid; - m_cachedVersion = m_file->version; - m_cachedName = m_file->name; - m_loaded = true; + m_file = file; + m_uid = uid; + m_cachedVersion = m_file->version; + m_cachedName = m_file->name; + m_loaded = true; } std::shared_ptr Component::getMeta() { - return m_metaVersion; + return m_metaVersion; } void Component::applyTo(LaunchProfile* profile) { - // do not apply disabled components - if(!isEnabled()) - { - return; - } - auto vfile = getVersionFile(); - if(vfile) - { - vfile->applyTo(profile); - } - else - { - profile->applyProblemSeverity(getProblemSeverity()); - } + // do not apply disabled components + if(!isEnabled()) + { + return; + } + auto vfile = getVersionFile(); + if(vfile) + { + vfile->applyTo(profile); + } + else + { + profile->applyProblemSeverity(getProblemSeverity()); + } } std::shared_ptr Component::getVersionFile() const { - if(m_metaVersion) - { - if(!m_metaVersion->isLoaded()) - { - m_metaVersion->load(Net::Mode::Online); - } - return m_metaVersion->data(); - } - else - { - return m_file; - } + if(m_metaVersion) + { + if(!m_metaVersion->isLoaded()) + { + m_metaVersion->load(Net::Mode::Online); + } + return m_metaVersion->data(); + } + else + { + return m_file; + } } std::shared_ptr Component::getVersionList() const { - // FIXME: what if the metadata index isn't loaded yet? - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return ENV.metadataIndex()->get(m_uid); - } - return nullptr; + // FIXME: what if the metadata index isn't loaded yet? + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return ENV.metadataIndex()->get(m_uid); + } + return nullptr; } int Component::getOrder() { - if(m_orderOverride) - return m_order; + if(m_orderOverride) + return m_order; - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->order; - } - return 0; + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->order; + } + return 0; } void Component::setOrder(int order) { - m_orderOverride = true; - m_order = order; + m_orderOverride = true; + m_order = order; } QString Component::getID() { - return m_uid; + return m_uid; } QString Component::getName() { - if (!m_cachedName.isEmpty()) - return m_cachedName; - return m_uid; + if (!m_cachedName.isEmpty()) + return m_cachedName; + return m_uid; } QString Component::getVersion() { - return m_cachedVersion; + return m_cachedVersion; } QString Component::getFilename() { - return m_parent->patchFilePathForUid(m_uid); + return m_parent->patchFilePathForUid(m_uid); } QDateTime Component::getReleaseDateTime() { - if(m_metaVersion) - { - return m_metaVersion->time(); - } - auto vfile = getVersionFile(); - if(vfile) - { - return vfile->releaseTime; - } - // FIXME: fake - return QDateTime::currentDateTime(); + if(m_metaVersion) + { + return m_metaVersion->time(); + } + auto vfile = getVersionFile(); + if(vfile) + { + return vfile->releaseTime; + } + // FIXME: fake + return QDateTime::currentDateTime(); } bool Component::isEnabled() { - return !canBeDisabled() || !m_disabled; -}; + return !canBeDisabled() || !m_disabled; +} bool Component::canBeDisabled() { - return isRemovable() && !m_dependencyOnly; + return isRemovable() && !m_dependencyOnly; } bool Component::setEnabled(bool state) { - bool intendedDisabled = !state; - if (!canBeDisabled()) - { - intendedDisabled = false; - } - if(intendedDisabled != m_disabled) - { - m_disabled = intendedDisabled; - emit dataChanged(); - return true; - } - return false; + bool intendedDisabled = !state; + if (!canBeDisabled()) + { + intendedDisabled = false; + } + if(intendedDisabled != m_disabled) + { + m_disabled = intendedDisabled; + emit dataChanged(); + return true; + } + return false; } bool Component::isCustom() { - return m_file != nullptr; -}; + return m_file != nullptr; +} bool Component::isCustomizable() { - if(m_metaVersion) - { - if(getVersionFile()) - { - return true; - } - } - return false; + if(m_metaVersion) + { + if(getVersionFile()) + { + return true; + } + } + return false; } bool Component::isRemovable() { - return !m_important; + return !m_important; } bool Component::isRevertible() { - if (isCustom()) - { - if(ENV.metadataIndex()->hasUid(m_uid)) - { - return true; - } - } - return false; + if (isCustom()) + { + if(ENV.metadataIndex()->hasUid(m_uid)) + { + return true; + } + } + return false; } bool Component::isMoveable() { - // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. - return true; + // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'. + return true; } bool Component::isVersionChangeable() { - auto list = getVersionList(); - if(list) - { - if(!list->isLoaded()) - { - list->load(Net::Mode::Online); - } - return list->count() != 0; - } - return false; + auto list = getVersionList(); + if(list) + { + if(!list->isLoaded()) + { + list->load(Net::Mode::Online); + } + return list->count() != 0; + } + return false; } void Component::setImportant(bool state) { - if(m_important != state) - { - m_important = state; - emit dataChanged(); - } + if(m_important != state) + { + m_important = state; + emit dataChanged(); + } } ProblemSeverity Component::getProblemSeverity() const { - auto file = getVersionFile(); - if(file) - { - return file->getProblemSeverity(); - } - return ProblemSeverity::Error; + auto file = getVersionFile(); + if(file) + { + return file->getProblemSeverity(); + } + return ProblemSeverity::Error; } const QList Component::getProblems() const { - auto file = getVersionFile(); - if(file) - { - return file->getProblems(); - } - return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; + auto file = getVersionFile(); + if(file) + { + return file->getProblems(); + } + return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}}; } void Component::setVersion(const QString& version) { - if(version == m_version) - { - return; - } - m_version = version; - if(m_loaded) - { - // we are loaded and potentially have state to invalidate - if(m_file) - { - // we have a file... explicit version has been changed and there is nothing else to do. - } - else - { - // we don't have a file, therefore we are loaded with metadata - m_cachedVersion = version; - // see if the meta version is loaded - auto metaVersion = ENV.metadataIndex()->get(m_uid, version); - if(metaVersion->isLoaded()) - { - // if yes, we can continue with that. - m_metaVersion = metaVersion; - } - else - { - // if not, we need loading - m_metaVersion.reset(); - m_loaded = false; - } - updateCachedData(); - } - } - else - { - // not loaded... assume it will be sorted out later by the update task - } - emit dataChanged(); + if(version == m_version) + { + return; + } + m_version = version; + if(m_loaded) + { + // we are loaded and potentially have state to invalidate + if(m_file) + { + // we have a file... explicit version has been changed and there is nothing else to do. + } + else + { + // we don't have a file, therefore we are loaded with metadata + m_cachedVersion = version; + // see if the meta version is loaded + auto metaVersion = ENV.metadataIndex()->get(m_uid, version); + if(metaVersion->isLoaded()) + { + // if yes, we can continue with that. + m_metaVersion = metaVersion; + } + else + { + // if not, we need loading + m_metaVersion.reset(); + m_loaded = false; + } + updateCachedData(); + } + } + else + { + // not loaded... assume it will be sorted out later by the update task + } + emit dataChanged(); } bool Component::customize() { - if(isCustom()) - { - return false; - } + if(isCustom()) + { + return false; + } - auto filename = getFilename(); - if(!FS::ensureFilePathExists(filename)) - { - return false; - } - // FIXME: get rid of this try-catch. - try - { - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - return false; - } - auto vfile = getVersionFile(); - if(!vfile) - { - return false; - } - auto document = OneSixVersionFormat::versionFileToJson(vfile); - jsonFile.write(document.toJson()); - if(!jsonFile.commit()) - { - return false; - } - m_file = vfile; - m_metaVersion.reset(); - emit dataChanged(); - } - catch (Exception &error) - { - qWarning() << "Version could not be loaded:" << error.cause(); - } - return true; + auto filename = getFilename(); + if(!FS::ensureFilePathExists(filename)) + { + return false; + } + // FIXME: get rid of this try-catch. + try + { + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + return false; + } + auto vfile = getVersionFile(); + if(!vfile) + { + return false; + } + auto document = OneSixVersionFormat::versionFileToJson(vfile); + jsonFile.write(document.toJson()); + if(!jsonFile.commit()) + { + return false; + } + m_file = vfile; + m_metaVersion.reset(); + emit dataChanged(); + } + catch (const Exception &error) + { + qWarning() << "Version could not be loaded:" << error.cause(); + } + return true; } bool Component::revert() { - if(!isCustom()) - { - // already not custom - return true; - } - auto filename = getFilename(); - bool result = true; - // just kill the file and reload - if(QFile::exists(filename)) - { - result = QFile::remove(filename); - } - if(result) - { - // file gone... - m_file.reset(); + if(!isCustom()) + { + // already not custom + return true; + } + auto filename = getFilename(); + bool result = true; + // just kill the file and reload + if(QFile::exists(filename)) + { + result = QFile::remove(filename); + } + if(result) + { + // file gone... + m_file.reset(); - // check local cache for metadata... - auto version = ENV.metadataIndex()->get(m_uid, m_version); - if(version->isLoaded()) - { - m_metaVersion = version; - } - else - { - m_metaVersion.reset(); - m_loaded = false; - } - emit dataChanged(); - } - return result; + // check local cache for metadata... + auto version = ENV.metadataIndex()->get(m_uid, m_version); + if(version->isLoaded()) + { + m_metaVersion = version; + } + else + { + m_metaVersion.reset(); + m_loaded = false; + } + emit dataChanged(); + } + return result; } /** @@ -372,68 +372,68 @@ bool Component::revert() */ static bool deepCompare(const std::set & a, const std::set & b) { - // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes - if(a.size() != b.size()) - { - return false; - } - for(const auto & reqA :a) - { - const auto &iter2 = b.find(reqA); - if(iter2 == b.cend()) - { - return false; - } - const auto & reqB = *iter2; - if(!reqA.deepEquals(reqB)) - { - return false; - } - } - return true; + // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes + if(a.size() != b.size()) + { + return false; + } + for(const auto & reqA :a) + { + const auto &iter2 = b.find(reqA); + if(iter2 == b.cend()) + { + return false; + } + const auto & reqB = *iter2; + if(!reqA.deepEquals(reqB)) + { + return false; + } + } + return true; } void Component::updateCachedData() { - auto file = getVersionFile(); - if(file) - { - bool changed = false; - if(m_cachedName != file->name) - { - m_cachedName = file->name; - changed = true; - } - if(m_cachedVersion != file->version) - { - m_cachedVersion = file->version; - changed = true; - } - if(m_cachedVolatile != file->m_volatile) - { - m_cachedVolatile = file->m_volatile; - changed = true; - } - if(!deepCompare(m_cachedRequires, file->requires)) - { - m_cachedRequires = file->requires; - changed = true; - } - if(!deepCompare(m_cachedConflicts, file->conflicts)) - { - m_cachedConflicts = file->conflicts; - changed = true; - } - if(changed) - { - emit dataChanged(); - } - } - else - { - // in case we removed all the metadata - m_cachedRequires.clear(); - m_cachedConflicts.clear(); - emit dataChanged(); - } + auto file = getVersionFile(); + if(file) + { + bool changed = false; + if(m_cachedName != file->name) + { + m_cachedName = file->name; + changed = true; + } + if(m_cachedVersion != file->version) + { + m_cachedVersion = file->version; + changed = true; + } + if(m_cachedVolatile != file->m_volatile) + { + m_cachedVolatile = file->m_volatile; + changed = true; + } + if(!deepCompare(m_cachedRequires, file->requires)) + { + m_cachedRequires = file->requires; + changed = true; + } + if(!deepCompare(m_cachedConflicts, file->conflicts)) + { + m_cachedConflicts = file->conflicts; + changed = true; + } + if(changed) + { + emit dataChanged(); + } + } + else + { + // in case we removed all the metadata + m_cachedRequires.clear(); + m_cachedConflicts.clear(); + emit dataChanged(); + } } diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h index 778fbb18..6a0f86c8 100644 --- a/api/logic/minecraft/Component.h +++ b/api/logic/minecraft/Component.h @@ -13,8 +13,8 @@ class ComponentList; class LaunchProfile; namespace Meta { - class Version; - class VersionList; + class Version; + class VersionList; } class VersionFile; @@ -22,90 +22,90 @@ class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider { Q_OBJECT public: - Component(ComponentList * parent, const QString &uid); + Component(ComponentList * parent, const QString &uid); - // DEPRECATED: remove these constructors? - Component(ComponentList * parent, std::shared_ptr version); - Component(ComponentList * parent, const QString & uid, std::shared_ptr file); + // DEPRECATED: remove these constructors? + Component(ComponentList * parent, std::shared_ptr version); + Component(ComponentList * parent, const QString & uid, std::shared_ptr file); - virtual ~Component(){}; - void applyTo(LaunchProfile *profile); + virtual ~Component(){}; + void applyTo(LaunchProfile *profile); - bool isEnabled(); - bool setEnabled (bool state); - bool canBeDisabled(); + bool isEnabled(); + bool setEnabled (bool state); + bool canBeDisabled(); - bool isMoveable(); - bool isCustomizable(); - bool isRevertible(); - bool isRemovable(); - bool isCustom(); - bool isVersionChangeable(); + bool isMoveable(); + bool isCustomizable(); + bool isRevertible(); + bool isRemovable(); + bool isCustom(); + bool isVersionChangeable(); - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - void setOrder(int order); - int getOrder(); + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + void setOrder(int order); + int getOrder(); - QString getID(); - QString getName(); - QString getVersion(); - std::shared_ptr getMeta(); - QDateTime getReleaseDateTime(); + QString getID(); + QString getName(); + QString getVersion(); + std::shared_ptr getMeta(); + QDateTime getReleaseDateTime(); - QString getFilename(); + QString getFilename(); - std::shared_ptr getVersionFile() const; - std::shared_ptr getVersionList() const; + std::shared_ptr getVersionFile() const; + std::shared_ptr getVersionList() const; - void setImportant (bool state); + void setImportant (bool state); - const QList getProblems() const override; - ProblemSeverity getProblemSeverity() const override; + const QList getProblems() const override; + ProblemSeverity getProblemSeverity() const override; - void setVersion(const QString & version); - bool customize(); - bool revert(); + void setVersion(const QString & version); + bool customize(); + bool revert(); - void updateCachedData(); + void updateCachedData(); signals: - void dataChanged(); + void dataChanged(); public: /* data */ - ComponentList * m_parent; - - // BEGIN: persistent component list properties - /// ID of the component - QString m_uid; - /// version of the component - when there's a custom json override, this is also the version the component reverts to - QString m_version; - /// if true, this has been added automatically to satisfy dependencies and may be automatically removed - bool m_dependencyOnly = false; - /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. - bool m_important = false; - /// if true, the component is disabled - bool m_disabled = false; - - /// cached name for display purposes, taken from the version file (meta or local override) - QString m_cachedName; - /// cached version for display AND other purposes, taken from the version file (meta or local override) - QString m_cachedVersion; - /// cached set of requirements, taken from the version file (meta or local override) - Meta::RequireSet m_cachedRequires; - Meta::RequireSet m_cachedConflicts; - /// if true, the component is volatile and may be automatically removed when no longer needed - bool m_cachedVolatile = false; - // END: persistent component list properties - - // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code - bool m_orderOverride = false; - int m_order = 0; - - // load state - std::shared_ptr m_metaVersion; - std::shared_ptr m_file; - bool m_loaded = false; + ComponentList * m_parent; + + // BEGIN: persistent component list properties + /// ID of the component + QString m_uid; + /// version of the component - when there's a custom json override, this is also the version the component reverts to + QString m_version; + /// if true, this has been added automatically to satisfy dependencies and may be automatically removed + bool m_dependencyOnly = false; + /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed. + bool m_important = false; + /// if true, the component is disabled + bool m_disabled = false; + + /// cached name for display purposes, taken from the version file (meta or local override) + QString m_cachedName; + /// cached version for display AND other purposes, taken from the version file (meta or local override) + QString m_cachedVersion; + /// cached set of requirements, taken from the version file (meta or local override) + Meta::RequireSet m_cachedRequires; + Meta::RequireSet m_cachedConflicts; + /// if true, the component is volatile and may be automatically removed when no longer needed + bool m_cachedVolatile = false; + // END: persistent component list properties + + // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code + bool m_orderOverride = false; + int m_order = 0; + + // load state + std::shared_ptr m_metaVersion; + std::shared_ptr m_file; + bool m_loaded = false; }; typedef shared_qobject_ptr ComponentPtr; diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp index a207e987..51fe214d 100644 --- a/api/logic/minecraft/ComponentList.cpp +++ b/api/logic/minecraft/ComponentList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,18 +37,20 @@ #include "ComponentUpdateTask.h" ComponentList::ComponentList(MinecraftInstance * instance) - : QAbstractListModel() + : QAbstractListModel() { - d.reset(new ComponentListData); - d->m_instance = instance; - d->m_saveTimer.setSingleShot(true); - d->m_saveTimer.setInterval(5000); - connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); + d.reset(new ComponentListData); + d->m_instance = instance; + d->m_saveTimer.setSingleShot(true); + d->m_saveTimer.setInterval(5000); + d->interactionDisabled = instance->isRunning(); + connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &ComponentList::disableInteraction); + connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal); } ComponentList::~ComponentList() { - saveNow(); + saveNow(); } // BEGIN: component file format @@ -57,151 +59,151 @@ static const int currentComponentsFileVersion = 1; static QJsonObject componentToJsonV1(ComponentPtr component) { - QJsonObject obj; - // critical - obj.insert("uid", component->m_uid); - if(!component->m_version.isEmpty()) - { - obj.insert("version", component->m_version); - } - if(component->m_dependencyOnly) - { - obj.insert("dependencyOnly", true); - } - if(component->m_important) - { - obj.insert("important", true); - } - if(component->m_disabled) - { - obj.insert("disabled", true); - } - - // cached - if(!component->m_cachedVersion.isEmpty()) - { - obj.insert("cachedVersion", component->m_cachedVersion); - } - if(!component->m_cachedName.isEmpty()) - { - obj.insert("cachedName", component->m_cachedName); - } - Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - if(component->m_cachedVolatile) - { - obj.insert("cachedVolatile", true); - } - return obj; + QJsonObject obj; + // critical + obj.insert("uid", component->m_uid); + if(!component->m_version.isEmpty()) + { + obj.insert("version", component->m_version); + } + if(component->m_dependencyOnly) + { + obj.insert("dependencyOnly", true); + } + if(component->m_important) + { + obj.insert("important", true); + } + if(component->m_disabled) + { + obj.insert("disabled", true); + } + + // cached + if(!component->m_cachedVersion.isEmpty()) + { + obj.insert("cachedVersion", component->m_cachedVersion); + } + if(!component->m_cachedName.isEmpty()) + { + obj.insert("cachedName", component->m_cachedName); + } + Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + if(component->m_cachedVolatile) + { + obj.insert("cachedVolatile", true); + } + return obj; } static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj) { - // critical - auto uid = Json::requireString(obj.value("uid")); - auto filePath = componentJsonPattern.arg(uid); - auto component = new Component(parent, uid); - component->m_version = Json::ensureString(obj.value("version")); - component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); - component->m_important = Json::ensureBoolean(obj.value("important"), false); - - // cached - // TODO @RESILIENCE: ignore invalid values/structure here? - component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); - component->m_cachedName = Json::ensureString(obj.value("cachedName")); - Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); - Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); - component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); - bool disabled = Json::ensureBoolean(obj.value("disabled"), false); - component->setEnabled(!disabled); - return component; + // critical + auto uid = Json::requireString(obj.value("uid")); + auto filePath = componentJsonPattern.arg(uid); + auto component = new Component(parent, uid); + component->m_version = Json::ensureString(obj.value("version")); + component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false); + component->m_important = Json::ensureBoolean(obj.value("important"), false); + + // cached + // TODO @RESILIENCE: ignore invalid values/structure here? + component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion")); + component->m_cachedName = Json::ensureString(obj.value("cachedName")); + Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires"); + Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts"); + component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false); + bool disabled = Json::ensureBoolean(obj.value("disabled"), false); + component->setEnabled(!disabled); + return component; } // Save the given component container data to a file static bool saveComponentList(const QString & filename, const ComponentContainer & container) { - QJsonObject obj; - obj.insert("formatVersion", currentComponentsFileVersion); - QJsonArray orderArray; - for(auto component: container) - { - orderArray.append(componentToJsonV1(component)); - } - obj.insert("components", orderArray); - QSaveFile outFile(filename); - if (!outFile.open(QFile::WriteOnly)) - { - qCritical() << "Couldn't open" << outFile.fileName() - << "for writing:" << outFile.errorString(); - return false; - } - auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); - if(outFile.write(data) != data.size()) - { - qCritical() << "Couldn't write all the data into" << outFile.fileName() - << "because:" << outFile.errorString(); - return false; - } - if(!outFile.commit()) - { - qCritical() << "Couldn't save" << outFile.fileName() - << "because:" << outFile.errorString(); - } - return true; + QJsonObject obj; + obj.insert("formatVersion", currentComponentsFileVersion); + QJsonArray orderArray; + for(auto component: container) + { + orderArray.append(componentToJsonV1(component)); + } + obj.insert("components", orderArray); + QSaveFile outFile(filename); + if (!outFile.open(QFile::WriteOnly)) + { + qCritical() << "Couldn't open" << outFile.fileName() + << "for writing:" << outFile.errorString(); + return false; + } + auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); + if(outFile.write(data) != data.size()) + { + qCritical() << "Couldn't write all the data into" << outFile.fileName() + << "because:" << outFile.errorString(); + return false; + } + if(!outFile.commit()) + { + qCritical() << "Couldn't save" << outFile.fileName() + << "because:" << outFile.errorString(); + } + return true; } // Read the given file into component containers static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container) { - QFile componentsFile(filename); - if (!componentsFile.exists()) - { - qWarning() << "Components file doesn't exist. This should never happen."; - return false; - } - if (!componentsFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << componentsFile.fileName() - << " for reading:" << componentsFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } - - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("formatVersion")); - if (version != currentComponentsFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") - .arg(currentComponentsFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("components")); - for(auto item: orderArray) - { - auto obj = Json::requireObject(item, "Component must be an object."); - container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); - } - } - catch (JSONValidationError &err) - { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; - container.clear(); - return false; - } - return true; + QFile componentsFile(filename); + if (!componentsFile.exists()) + { + qWarning() << "Components file doesn't exist. This should never happen."; + return false; + } + if (!componentsFile.open(QFile::ReadOnly)) + { + qCritical() << "Couldn't open" << componentsFile.fileName() + << " for reading:" << componentsFile.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } + + // and then read it and process it if all above is true. + try + { + auto obj = Json::requireObject(doc); + // check order file version. + auto version = Json::requireInteger(obj.value("formatVersion")); + if (version != currentComponentsFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid component file version, expected %1") + .arg(currentComponentsFileVersion)); + } + auto orderArray = Json::requireArray(obj.value("components")); + for(auto item: orderArray) + { + auto obj = Json::requireObject(item, "Component must be an object."); + container.append(componentFromJsonV1(parent, componentJsonPattern, obj)); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + container.clear(); + return false; + } + return true; } // END: component file format @@ -210,217 +212,217 @@ static bool loadComponentList(ComponentList * parent, const QString & filename, void ComponentList::saveNow() { - if(saveIsScheduled()) - { - d->m_saveTimer.stop(); - save_internal(); - } + if(saveIsScheduled()) + { + d->m_saveTimer.stop(); + save_internal(); + } } bool ComponentList::saveIsScheduled() const { - return d->dirty; + return d->dirty; } void ComponentList::buildingFromScratch() { - d->loaded = true; - d->dirty = true; + d->loaded = true; + d->dirty = true; } void ComponentList::scheduleSave() { - if(!d->loaded) - { - qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); - return; - } - if(!d->dirty) - { - d->dirty = true; - qDebug() << "Component list save is scheduled for" << d->m_instance->name(); - } - d->m_saveTimer.start(); + if(!d->loaded) + { + qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); + return; + } + if(!d->dirty) + { + d->dirty = true; + qDebug() << "Component list save is scheduled for" << d->m_instance->name(); + } + d->m_saveTimer.start(); } QString ComponentList::componentsFilePath() const { - return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); + return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); } QString ComponentList::patchesPattern() const { - return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); + return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json"); } QString ComponentList::patchFilePathForUid(const QString& uid) const { - return patchesPattern().arg(uid); + return patchesPattern().arg(uid); } void ComponentList::save_internal() { - qDebug() << "Component list save performed now for" << d->m_instance->name(); - auto filename = componentsFilePath(); - saveComponentList(filename, d->components); - d->dirty = false; + qDebug() << "Component list save performed now for" << d->m_instance->name(); + auto filename = componentsFilePath(); + saveComponentList(filename, d->components); + d->dirty = false; } bool ComponentList::load() { - auto filename = componentsFilePath(); - QFile componentsFile(filename); - - // migrate old config to new one, if needed - if(!componentsFile.exists()) - { - if(!migratePreComponentConfig()) - { - // FIXME: the user should be notified... - qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); - return false; - } - } - - // load the new component list and swap it with the current one... - ComponentContainer newComponents; - if(!loadComponentList(this, filename, patchesPattern(), newComponents)) - { - qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); - return false; - } - else - { - // FIXME: actually use fine-grained updates, not this... - beginResetModel(); - // disconnect all the old components - for(auto component: d->components) - { - disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - } - d->components.clear(); - d->componentIndex.clear(); - for(auto component: newComponents) - { - if(d->componentIndex.contains(component->m_uid)) - { - qWarning() << "Ignoring duplicate component entry" << component->m_uid; - continue; - } - connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - d->components.append(component); - d->componentIndex[component->m_uid] = component; - } - endResetModel(); - d->loaded = true; - return true; - } + auto filename = componentsFilePath(); + QFile componentsFile(filename); + + // migrate old config to new one, if needed + if(!componentsFile.exists()) + { + if(!migratePreComponentConfig()) + { + // FIXME: the user should be notified... + qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name(); + return false; + } + } + + // load the new component list and swap it with the current one... + ComponentContainer newComponents; + if(!loadComponentList(this, filename, patchesPattern(), newComponents)) + { + qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); + return false; + } + else + { + // FIXME: actually use fine-grained updates, not this... + beginResetModel(); + // disconnect all the old components + for(auto component: d->components) + { + disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + } + d->components.clear(); + d->componentIndex.clear(); + for(auto component: newComponents) + { + if(d->componentIndex.contains(component->m_uid)) + { + qWarning() << "Ignoring duplicate component entry" << component->m_uid; + continue; + } + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + d->components.append(component); + d->componentIndex[component->m_uid] = component; + } + endResetModel(); + d->loaded = true; + return true; + } } void ComponentList::reload(Net::Mode netmode) { - // Do not reload when the update/resolve task is running. It is in control. - if(d->m_updateTask) - { - return; - } - - // flush any scheduled saves to not lose state - saveNow(); - - // FIXME: differentiate when a reapply is required by propagating state from components - invalidateLaunchProfile(); - - if(load()) - { - resolve(netmode); - } + // Do not reload when the update/resolve task is running. It is in control. + if(d->m_updateTask) + { + return; + } + + // flush any scheduled saves to not lose state + saveNow(); + + // FIXME: differentiate when a reapply is required by propagating state from components + invalidateLaunchProfile(); + + if(load()) + { + resolve(netmode); + } } shared_qobject_ptr ComponentList::getCurrentTask() { - return d->m_updateTask; + return d->m_updateTask; } void ComponentList::resolve(Net::Mode netmode) { - auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); - d->m_updateTask.reset(updateTask); - connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); - connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); - d->m_updateTask->start(); + auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this); + d->m_updateTask.reset(updateTask); + connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded); + connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed); + d->m_updateTask->start(); } void ComponentList::updateSucceeded() { - qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); - d->m_updateTask.reset(); - invalidateLaunchProfile(); + qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); + d->m_updateTask.reset(); + invalidateLaunchProfile(); } void ComponentList::updateFailed(const QString& error) { - qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; - d->m_updateTask.reset(); - invalidateLaunchProfile(); + qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; + d->m_updateTask.reset(); + invalidateLaunchProfile(); } // NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig). static void upgradeDeprecatedFiles(QString root, QString instanceName) { - auto versionJsonPath = FS::PathCombine(root, "version.json"); - auto customJsonPath = FS::PathCombine(root, "custom.json"); - auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); - - QString sourceFile; - QString renameFile; - - // convert old crap. - if(QFile::exists(customJsonPath)) - { - sourceFile = customJsonPath; - renameFile = versionJsonPath; - } - else if(QFile::exists(versionJsonPath)) - { - sourceFile = versionJsonPath; - } - if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) - { - if(!FS::ensureFilePathExists(mcJson)) - { - qWarning() << "Couldn't create patches folder for" << instanceName; - return; - } - if(!renameFile.isEmpty() && QFile::exists(renameFile)) - { - if(!QFile::rename(renameFile, renameFile + ".old")) - { - qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; - return; - } - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); - ProfileUtils::removeLwjglFromPatch(file); - file->uid = "net.minecraft"; - file->version = file->minecraftVersion; - file->name = "Minecraft"; - - Meta::Require needsLwjgl; - needsLwjgl.uid = "org.lwjgl"; - file->requires.insert(needsLwjgl); - - if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) - { - return; - } - if(!QFile::rename(sourceFile, sourceFile + ".old")) - { - qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; - return; - } - } + auto versionJsonPath = FS::PathCombine(root, "version.json"); + auto customJsonPath = FS::PathCombine(root, "custom.json"); + auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json"); + + QString sourceFile; + QString renameFile; + + // convert old crap. + if(QFile::exists(customJsonPath)) + { + sourceFile = customJsonPath; + renameFile = versionJsonPath; + } + else if(QFile::exists(versionJsonPath)) + { + sourceFile = versionJsonPath; + } + if(!sourceFile.isEmpty() && !QFile::exists(mcJson)) + { + if(!FS::ensureFilePathExists(mcJson)) + { + qWarning() << "Couldn't create patches folder for" << instanceName; + return; + } + if(!renameFile.isEmpty() && QFile::exists(renameFile)) + { + if(!QFile::rename(renameFile, renameFile + ".old")) + { + qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName; + return; + } + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false); + ProfileUtils::removeLwjglFromPatch(file); + file->uid = "net.minecraft"; + file->version = file->minecraftVersion; + file->name = "Minecraft"; + + Meta::Require needsLwjgl; + needsLwjgl.uid = "org.lwjgl"; + file->requires.insert(needsLwjgl); + + if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson)) + { + return; + } + if(!QFile::rename(sourceFile, sourceFile + ".old")) + { + qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName; + return; + } + } } /* @@ -431,774 +433,793 @@ static void upgradeDeprecatedFiles(QString root, QString instanceName) */ bool ComponentList::migratePreComponentConfig() { - // upgrade the very old files from the beginnings of MultiMC 5 - upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); - - QList components; - QSet loaded; - - auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) - { - auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); - auto intendedVersion = d->getOldConfigVersion(uid); - // load up the base minecraft patch - ComponentPtr component; - if(QFile::exists(jsonFilePath)) - { - if(intendedVersion.isEmpty()) - { - intendedVersion = emptyVersion; - } - auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); - // fix uid - file->uid = uid; - // if version is missing, add it from the outside. - if(file->version.isEmpty()) - { - file->version = intendedVersion; - } - // if this is a dependency (LWJGL), mark it also as volatile - if(asDependency) - { - file->m_volatile = true; - } - // insert requirements if needed - if(!req.uid.isEmpty()) - { - file->requires.insert(req); - } - // insert conflicts if needed - if(!conflict.uid.isEmpty()) - { - file->conflicts.insert(conflict); - } - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); - component = new Component(this, uid, file); - component->m_version = intendedVersion; - } - else if(!intendedVersion.isEmpty()) - { - auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); - component = new Component(this, metaVersion); - } - else - { - return; - } - component->m_dependencyOnly = asDependency; - component->m_important = !asDependency; - components.append(component); - }; - // TODO: insert depends and conflicts here if these are customized files... - Meta::Require reqLwjgl; - reqLwjgl.uid = "org.lwjgl"; - reqLwjgl.suggests = "2.9.1"; - Meta::Require conflictLwjgl3; - conflictLwjgl3.uid = "org.lwjgl3"; - Meta::Require nullReq; - addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); - addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); - - // first, collect all other file-based patches and load them - QMap loadedComponents; - QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); - for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) - { - // parse the file - qDebug() << "Reading" << info.fileName(); - auto file = ProfileUtils::parseJsonFile(info, true); - - // correct missing or wrong uid based on the file name - QString uid = info.completeBaseName(); - - // ignore builtins, they've been handled already - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - - // handle horrible corner cases - if(uid.isEmpty()) - { - // if you have a file named '.json', make it just go away. - // FIXME: @QUALITY do not ignore return value - QFile::remove(info.absoluteFilePath()); - continue; - } - file->uid = uid; - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); - - auto component = new Component(this, file->uid, file); - auto version = d->getOldConfigVersion(file->uid); - if(!version.isEmpty()) - { - component->m_version = version; - } - loadedComponents[file->uid] = component; - } - // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files - auto loadSpecial = [&](const QString & uid, int order) - { - auto patchVersion = d->getOldConfigVersion(uid); - if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) - { - auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); - patch->setOrder(order); - loadedComponents[uid] = patch; - } - }; - loadSpecial("net.minecraftforge", 5); - loadSpecial("com.mumfrey.liteloader", 10); - - // load the old order.json file, if present - ProfileUtils::PatchOrder userOrder; - ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); - - // now add all the patches by user sort order - for (auto uid : userOrder) - { - // ignore builtins - if (uid == "net.minecraft") - continue; - if (uid == "org.lwjgl") - continue; - // ordering has a patch that is gone? - if(!loadedComponents.contains(uid)) - { - continue; - } - components.append(loadedComponents.take(uid)); - } - - // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json - if(!loadedComponents.isEmpty()) - { - // inserting into multimap by order number as key sorts the patches and detects duplicates - QMultiMap files; - auto iter = loadedComponents.begin(); - while(iter != loadedComponents.end()) - { - files.insert((*iter)->getOrder(), *iter); - iter++; - } - - // then just extract the patches and put them in the list - for (auto order : files.keys()) - { - const auto &values = files.values(order); - for(auto &value: values) - { - // TODO: put back the insertion of problem messages here, so the user knows about the id duplication - components.append(value); - } - } - } - // new we have a complete list of components... - return saveComponentList(componentsFilePath(), components); + // upgrade the very old files from the beginnings of MultiMC 5 + upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name()); + + QList components; + QSet loaded; + + auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict) + { + auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json"); + auto intendedVersion = d->getOldConfigVersion(uid); + // load up the base minecraft patch + ComponentPtr component; + if(QFile::exists(jsonFilePath)) + { + if(intendedVersion.isEmpty()) + { + intendedVersion = emptyVersion; + } + auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false); + // fix uid + file->uid = uid; + // if version is missing, add it from the outside. + if(file->version.isEmpty()) + { + file->version = intendedVersion; + } + // if this is a dependency (LWJGL), mark it also as volatile + if(asDependency) + { + file->m_volatile = true; + } + // insert requirements if needed + if(!req.uid.isEmpty()) + { + file->requires.insert(req); + } + // insert conflicts if needed + if(!conflict.uid.isEmpty()) + { + file->conflicts.insert(conflict); + } + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath); + component = new Component(this, uid, file); + component->m_version = intendedVersion; + } + else if(!intendedVersion.isEmpty()) + { + auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion); + component = new Component(this, metaVersion); + } + else + { + return; + } + component->m_dependencyOnly = asDependency; + component->m_important = !asDependency; + components.append(component); + }; + // TODO: insert depends and conflicts here if these are customized files... + Meta::Require reqLwjgl; + reqLwjgl.uid = "org.lwjgl"; + reqLwjgl.suggests = "2.9.1"; + Meta::Require conflictLwjgl3; + conflictLwjgl3.uid = "org.lwjgl3"; + Meta::Require nullReq; + addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3); + addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq); + + // first, collect all other file-based patches and load them + QMap loadedComponents; + QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches")); + for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files)) + { + // parse the file + qDebug() << "Reading" << info.fileName(); + auto file = ProfileUtils::parseJsonFile(info, true); + + // correct missing or wrong uid based on the file name + QString uid = info.completeBaseName(); + + // ignore builtins, they've been handled already + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + + // handle horrible corner cases + if(uid.isEmpty()) + { + // if you have a file named '.json', make it just go away. + // FIXME: @QUALITY do not ignore return value + QFile::remove(info.absoluteFilePath()); + continue; + } + file->uid = uid; + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath()); + + auto component = new Component(this, file->uid, file); + auto version = d->getOldConfigVersion(file->uid); + if(!version.isEmpty()) + { + component->m_version = version; + } + loadedComponents[file->uid] = component; + } + // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files + auto loadSpecial = [&](const QString & uid, int order) + { + auto patchVersion = d->getOldConfigVersion(uid); + if(!patchVersion.isEmpty() && !loadedComponents.contains(uid)) + { + auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion)); + patch->setOrder(order); + loadedComponents[uid] = patch; + } + }; + loadSpecial("net.minecraftforge", 5); + loadSpecial("com.mumfrey.liteloader", 10); + + // load the old order.json file, if present + ProfileUtils::PatchOrder userOrder; + ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder); + + // now add all the patches by user sort order + for (auto uid : userOrder) + { + // ignore builtins + if (uid == "net.minecraft") + continue; + if (uid == "org.lwjgl") + continue; + // ordering has a patch that is gone? + if(!loadedComponents.contains(uid)) + { + continue; + } + components.append(loadedComponents.take(uid)); + } + + // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json + if(!loadedComponents.isEmpty()) + { + // inserting into multimap by order number as key sorts the patches and detects duplicates + QMultiMap files; + auto iter = loadedComponents.begin(); + while(iter != loadedComponents.end()) + { + files.insert((*iter)->getOrder(), *iter); + iter++; + } + + // then just extract the patches and put them in the list + for (auto order : files.keys()) + { + const auto &values = files.values(order); + for(auto &value: values) + { + // TODO: put back the insertion of problem messages here, so the user knows about the id duplication + components.append(value); + } + } + } + // new we have a complete list of components... + return saveComponentList(componentsFilePath(), components); } // END: save/load void ComponentList::appendComponent(ComponentPtr component) { - insertComponent(d->components.size(), component); + insertComponent(d->components.size(), component); } void ComponentList::insertComponent(size_t index, ComponentPtr component) { - auto id = component->getID(); - if(id.isEmpty()) - { - qWarning() << "Attempt to add a component with empty ID!"; - return; - } - if(d->componentIndex.contains(id)) - { - qWarning() << "Attempt to add a component that is already present!"; - return; - } - beginInsertRows(QModelIndex(), index, index); - d->components.insert(index, component); - d->componentIndex[id] = component; - endInsertRows(); - connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); - scheduleSave(); + auto id = component->getID(); + if(id.isEmpty()) + { + qWarning() << "Attempt to add a component with empty ID!"; + return; + } + if(d->componentIndex.contains(id)) + { + qWarning() << "Attempt to add a component that is already present!"; + return; + } + beginInsertRows(QModelIndex(), index, index); + d->components.insert(index, component); + d->componentIndex[id] = component; + endInsertRows(); + connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged); + scheduleSave(); } void ComponentList::componentDataChanged() { - auto objPtr = qobject_cast(sender()); - if(!objPtr) - { - qWarning() << "ComponentList got dataChenged signal from a non-Component!"; - return; - } - // figure out which one is it... in a seriously dumb way. - int index = 0; - for (auto component: d->components) - { - if(component.get() == objPtr) - { - emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); - scheduleSave(); - return; - } - index++; - } - qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; + auto objPtr = qobject_cast(sender()); + if(!objPtr) + { + qWarning() << "ComponentList got dataChenged signal from a non-Component!"; + return; + } + if(objPtr->getID() == "net.minecraft") { + emit minecraftChanged(); + } + // figure out which one is it... in a seriously dumb way. + int index = 0; + for (auto component: d->components) + { + if(component.get() == objPtr) + { + emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1)); + scheduleSave(); + return; + } + index++; + } + qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!"; } bool ComponentList::remove(const int index) { - auto patch = getComponent(index); - if (!patch->isRemovable()) - { - qWarning() << "Patch" << patch->getID() << "is non-removable"; - return false; - } - - if(!removeComponent_internal(patch)) - { - qCritical() << "Patch" << patch->getID() << "could not be removed"; - return false; - } - - beginRemoveRows(QModelIndex(), index, index); - d->components.removeAt(index); - d->componentIndex.remove(patch->getID()); - endRemoveRows(); - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isRemovable()) + { + qWarning() << "Patch" << patch->getID() << "is non-removable"; + return false; + } + + if(!removeComponent_internal(patch)) + { + qCritical() << "Patch" << patch->getID() << "could not be removed"; + return false; + } + + beginRemoveRows(QModelIndex(), index, index); + d->components.removeAt(index); + d->componentIndex.remove(patch->getID()); + endRemoveRows(); + invalidateLaunchProfile(); + scheduleSave(); + return true; } bool ComponentList::remove(const QString id) { - int i = 0; - for (auto patch : d->components) - { - if (patch->getID() == id) - { - return remove(i); - } - i++; - } - return false; + int i = 0; + for (auto patch : d->components) + { + if (patch->getID() == id) + { + return remove(i); + } + i++; + } + return false; } bool ComponentList::customize(int index) { - auto patch = getComponent(index); - if (!patch->isCustomizable()) - { - qDebug() << "Patch" << patch->getID() << "is not customizable"; - return false; - } - if(!patch->customize()) - { - qCritical() << "Patch" << patch->getID() << "could not be customized"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isCustomizable()) + { + qDebug() << "Patch" << patch->getID() << "is not customizable"; + return false; + } + if(!patch->customize()) + { + qCritical() << "Patch" << patch->getID() << "could not be customized"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; } bool ComponentList::revertToBase(int index) { - auto patch = getComponent(index); - if (!patch->isRevertible()) - { - qDebug() << "Patch" << patch->getID() << "is not revertible"; - return false; - } - if(!patch->revert()) - { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; - return false; - } - invalidateLaunchProfile(); - scheduleSave(); - return true; + auto patch = getComponent(index); + if (!patch->isRevertible()) + { + qDebug() << "Patch" << patch->getID() << "is not revertible"; + return false; + } + if(!patch->revert()) + { + qCritical() << "Patch" << patch->getID() << "could not be reverted"; + return false; + } + invalidateLaunchProfile(); + scheduleSave(); + return true; } Component * ComponentList::getComponent(const QString &id) { - auto iter = d->componentIndex.find(id); - if (iter == d->componentIndex.end()) - { - return nullptr; - } - return (*iter).get(); + auto iter = d->componentIndex.find(id); + if (iter == d->componentIndex.end()) + { + return nullptr; + } + return (*iter).get(); } Component * ComponentList::getComponent(int index) { - if(index < 0 || index >= d->components.size()) - { - return nullptr; - } - return d->components[index].get(); + if(index < 0 || index >= d->components.size()) + { + return nullptr; + } + return d->components[index].get(); } QVariant ComponentList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= d->components.size()) - return QVariant(); - - auto patch = d->components.at(row); - - switch (role) - { - case Qt::CheckStateRole: - { - switch (column) - { - case NameColumn: - return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - } - case Qt::DisplayRole: - { - switch (column) - { - case NameColumn: - return d->components.at(row)->getName(); - case VersionColumn: - { - if(patch->isCustom()) - { - return QString("%1 (Custom)").arg(patch->getVersion()); - } - else - { - return patch->getVersion(); - } - } - default: - return QVariant(); - } - } - case Qt::DecorationRole: - { - switch(column) - { - case NameColumn: - { - auto severity = patch->getProblemSeverity(); - switch (severity) - { - case ProblemSeverity::Warning: - return "warning"; - case ProblemSeverity::Error: - return "error"; - default: - return QVariant(); - } - } - default: - { - return QVariant(); - } - } - } - } - return QVariant(); + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= d->components.size()) + return QVariant(); + + auto patch = d->components.at(row); + + switch (role) + { + case Qt::CheckStateRole: + { + switch (column) + { + case NameColumn: { + return patch->isEnabled() ? Qt::Checked : Qt::Unchecked; + } + default: + return QVariant(); + } + } + case Qt::DisplayRole: + { + switch (column) + { + case NameColumn: + return patch->getName(); + case VersionColumn: + { + if(patch->isCustom()) + { + return QString("%1 (Custom)").arg(patch->getVersion()); + } + else + { + return patch->getVersion(); + } + } + default: + return QVariant(); + } + } + case Qt::DecorationRole: + { + switch(column) + { + case NameColumn: + { + auto severity = patch->getProblemSeverity(); + switch (severity) + { + case ProblemSeverity::Warning: + return "warning"; + case ProblemSeverity::Error: + return "error"; + default: + return QVariant(); + } + } + default: + { + return QVariant(); + } + } + } + } + return QVariant(); } bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int role) { - if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto component = d->components[index.row()]; - if (component->setEnabled(!component->isEnabled())) - { - return true; - } - } - return false; + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index)) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + auto component = d->components[index.row()]; + if (component->setEnabled(!component->isEnabled())) + { + return true; + } + } + return false; } QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int role) const { - if (orientation == Qt::Horizontal) - { - if (role == Qt::DisplayRole) - { - switch (section) - { - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - default: - return QVariant(); - } - } - } - return QVariant(); + if (orientation == Qt::Horizontal) + { + if (role == Qt::DisplayRole) + { + switch (section) + { + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + default: + return QVariant(); + } + } + } + return QVariant(); } + +// FIXME: zero precision mess Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const { - if (!index.isValid()) - return Qt::NoItemFlags; + if (!index.isValid()) { + return Qt::NoItemFlags; + } - Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - int row = index.row(); + int row = index.row(); - if (row < 0 || row >= d->components.size()) - return Qt::NoItemFlags; + if (row < 0 || row >= d->components.size()) { + return Qt::NoItemFlags; + } - auto patch = d->components.at(row); - // TODO: this will need fine-tuning later... - if(patch->canBeDisabled()) - { - outFlags |= Qt::ItemIsUserCheckable; - } - return outFlags; + auto patch = d->components.at(row); + // TODO: this will need fine-tuning later... + if(patch->canBeDisabled() && !d->interactionDisabled) + { + outFlags |= Qt::ItemIsUserCheckable; + } + return outFlags; } int ComponentList::rowCount(const QModelIndex &parent) const { - return d->components.size(); + return d->components.size(); } int ComponentList::columnCount(const QModelIndex &parent) const { - return NUM_COLUMNS; + return NUM_COLUMNS; } void ComponentList::move(const int index, const MoveDirection direction) { - int theirIndex; - if (direction == MoveUp) - { - theirIndex = index - 1; - } - else - { - theirIndex = index + 1; - } - - if (index < 0 || index >= d->components.size()) - return; - if (theirIndex >= rowCount()) - theirIndex = rowCount() - 1; - if (theirIndex == -1) - theirIndex = rowCount() - 1; - if (index == theirIndex) - return; - int togap = theirIndex > index ? theirIndex + 1 : theirIndex; - - auto from = getComponent(index); - auto to = getComponent(theirIndex); - - if (!from || !to || !to->isMoveable() || !from->isMoveable()) - { - return; - } - beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); - d->components.swap(index, theirIndex); - endMoveRows(); - invalidateLaunchProfile(); - scheduleSave(); + int theirIndex; + if (direction == MoveUp) + { + theirIndex = index - 1; + } + else + { + theirIndex = index + 1; + } + + if (index < 0 || index >= d->components.size()) + return; + if (theirIndex >= rowCount()) + theirIndex = rowCount() - 1; + if (theirIndex == -1) + theirIndex = rowCount() - 1; + if (index == theirIndex) + return; + int togap = theirIndex > index ? theirIndex + 1 : theirIndex; + + auto from = getComponent(index); + auto to = getComponent(theirIndex); + + if (!from || !to || !to->isMoveable() || !from->isMoveable()) + { + return; + } + beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap); + d->components.swap(index, theirIndex); + endMoveRows(); + invalidateLaunchProfile(); + scheduleSave(); } void ComponentList::invalidateLaunchProfile() { - d->m_profile.reset(); + d->m_profile.reset(); } void ComponentList::installJarMods(QStringList selectedFiles) { - installJarMods_internal(selectedFiles); + installJarMods_internal(selectedFiles); } void ComponentList::installCustomJar(QString selectedFile) { - installCustomJar_internal(selectedFile); + installCustomJar_internal(selectedFile); } bool ComponentList::installEmpty(const QString& uid, const QString& name) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - auto f = std::make_shared(); - f->name = name; - f->uid = uid; - f->version = "1"; - QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + auto f = std::make_shared(); + f->name = name; + f->uid = uid; + f->version = "1"; + QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + scheduleSave(); + invalidateLaunchProfile(); + return true; } bool ComponentList::removeComponent_internal(ComponentPtr patch) { - bool ok = true; - // first, remove the patch file. this ensures it's not used anymore - auto fileName = patch->getFilename(); - if(fileName.size()) - { - QFile patchFile(fileName); - if(patchFile.exists() && !patchFile.remove()) - { - qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); - return false; - } - } - - // FIXME: we need a generic way of removing local resources, not just jar mods... - auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool - { - if (!jarMod->isLocal()) - { - return true; - } - QStringList jar, temp1, temp2, temp3; - jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); - QFileInfo finfo (jar[0]); - if(finfo.exists()) - { - QFile jarModFile(jar[0]); - if(!jarModFile.remove()) - { - qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); - return false; - } - return true; - } - return true; - }; - - auto vFile = patch->getVersionFile(); - if(vFile) - { - auto &jarMods = vFile->jarMods; - for(auto &jarmod: jarMods) - { - ok &= preRemoveJarMod(jarmod); - } - } - return ok; + bool ok = true; + // first, remove the patch file. this ensures it's not used anymore + auto fileName = patch->getFilename(); + if(fileName.size()) + { + QFile patchFile(fileName); + if(patchFile.exists() && !patchFile.remove()) + { + qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); + return false; + } + } + + // FIXME: we need a generic way of removing local resources, not just jar mods... + auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool + { + if (!jarMod->isLocal()) + { + return true; + } + QStringList jar, temp1, temp2, temp3; + jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); + QFileInfo finfo (jar[0]); + if(finfo.exists()) + { + QFile jarModFile(jar[0]); + if(!jarModFile.remove()) + { + qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); + return false; + } + return true; + } + return true; + }; + + auto vFile = patch->getVersionFile(); + if(vFile) + { + auto &jarMods = vFile->jarMods; + for(auto &jarmod: jarMods) + { + ok &= preRemoveJarMod(jarmod); + } + } + return ok; } bool ComponentList::installJarMods_internal(QStringList filepaths) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) - { - return false; - } - - for(auto filepath:filepaths) - { - QFileInfo sourceInfo(filepath); - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - QString target_filename = id + ".jar"; - QString target_id = "org.multimc.jarmod." + id; - QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; - QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); - - QFileInfo targetInfo(finalPath); - if(targetInfo.exists()) - { - return false; - } - - if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) - { - return false; - } - - auto f = std::make_shared(); - auto jarMod = std::make_shared(); - jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - jarMod->setFilename(target_filename); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->jarMods.append(jarMod); - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - } - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir())) + { + return false; + } + + for(auto filepath:filepaths) + { + QFileInfo sourceInfo(filepath); + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + QString target_filename = id + ".jar"; + QString target_id = "org.multimc.jarmod." + id; + QString target_name = sourceInfo.completeBaseName() + " (jar mod)"; + QString finalPath = FS::PathCombine(d->m_instance->jarModsDir(), target_filename); + + QFileInfo targetInfo(finalPath); + if(targetInfo.exists()) + { + return false; + } + + if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath())) + { + return false; + } + + auto f = std::make_shared(); + auto jarMod = std::make_shared(); + jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); + jarMod->setFilename(target_filename); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->jarMods.append(jarMod); + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + } + scheduleSave(); + invalidateLaunchProfile(); + return true; } bool ComponentList::installCustomJar_internal(QString filepath) { - QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); - if(!FS::ensureFolderPathExists(patchDir)) - { - return false; - } - - QString libDir = d->m_instance->getLocalLibraryPath(); - if (!FS::ensureFolderPathExists(libDir)) - { - return false; - } - - auto specifier = GradleSpecifier("org.multimc:customjar:1"); - QFileInfo sourceInfo(filepath); - QString target_filename = specifier.getFileName(); - QString target_id = specifier.artifactId(); - QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; - QString finalPath = FS::PathCombine(libDir, target_filename); - - QFileInfo jarInfo(finalPath); - if (jarInfo.exists()) - { - if(!QFile::remove(finalPath)) - { - return false; - } - } - if (!QFile::copy(filepath, finalPath)) - { - return false; - } - - auto f = std::make_shared(); - auto jarMod = std::make_shared(); - jarMod->setRawName(specifier); - jarMod->setDisplayName(sourceInfo.completeBaseName()); - jarMod->setHint("local"); - f->mainJar = jarMod; - f->name = target_name; - f->uid = target_id; - QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); - - QFile file(patchFileName); - if (!file.open(QFile::WriteOnly)) - { - qCritical() << "Error opening" << file.fileName() - << "for reading:" << file.errorString(); - return false; - } - file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); - file.close(); - - appendComponent(new Component(this, f->uid, f)); - - scheduleSave(); - invalidateLaunchProfile(); - return true; + QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches"); + if(!FS::ensureFolderPathExists(patchDir)) + { + return false; + } + + QString libDir = d->m_instance->getLocalLibraryPath(); + if (!FS::ensureFolderPathExists(libDir)) + { + return false; + } + + auto specifier = GradleSpecifier("org.multimc:customjar:1"); + QFileInfo sourceInfo(filepath); + QString target_filename = specifier.getFileName(); + QString target_id = specifier.artifactId(); + QString target_name = sourceInfo.completeBaseName() + " (custom jar)"; + QString finalPath = FS::PathCombine(libDir, target_filename); + + QFileInfo jarInfo(finalPath); + if (jarInfo.exists()) + { + if(!QFile::remove(finalPath)) + { + return false; + } + } + if (!QFile::copy(filepath, finalPath)) + { + return false; + } + + auto f = std::make_shared(); + auto jarMod = std::make_shared(); + jarMod->setRawName(specifier); + jarMod->setDisplayName(sourceInfo.completeBaseName()); + jarMod->setHint("local"); + f->mainJar = jarMod; + f->name = target_name; + f->uid = target_id; + QString patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + + QFile file(patchFileName); + if (!file.open(QFile::WriteOnly)) + { + qCritical() << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); + return false; + } + file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); + file.close(); + + appendComponent(new Component(this, f->uid, f)); + + scheduleSave(); + invalidateLaunchProfile(); + return true; } std::shared_ptr ComponentList::getProfile() const { - if(!d->m_profile) - { - try - { - auto profile = std::make_shared(); - for(auto file: d->components) - { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); - file->applyTo(profile.get()); - } - d->m_profile = profile; - } - catch (Exception & error) - { - qWarning() << "Couldn't apply profile patches because: " << error.cause(); - } - } - return d->m_profile; + if(!d->m_profile) + { + try + { + auto profile = std::make_shared(); + for(auto file: d->components) + { + qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); + file->applyTo(profile.get()); + } + d->m_profile = profile; + } + catch (const Exception &error) + { + qWarning() << "Couldn't apply profile patches because: " << error.cause(); + } + } + return d->m_profile; } void ComponentList::setOldConfigVersion(const QString& uid, const QString& version) { - if(version.isEmpty()) - { - return; - } - d->m_oldConfigVersions[uid] = version; + if(version.isEmpty()) + { + return; + } + d->m_oldConfigVersions[uid] = version; } bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important) { - auto iter = d->componentIndex.find(uid); - if(iter != d->componentIndex.end()) - { - ComponentPtr component = *iter; - // set existing - if(component->revert()) - { - component->setVersion(version); - component->setImportant(important); - return true; - } - return false; - } - else - { - // add new - auto component = new Component(this, uid); - component->m_version = version; - component->m_important = important; - appendComponent(component); - return true; - } + auto iter = d->componentIndex.find(uid); + if(iter != d->componentIndex.end()) + { + ComponentPtr component = *iter; + // set existing + if(component->revert()) + { + component->setVersion(version); + component->setImportant(important); + return true; + } + return false; + } + else + { + // add new + auto component = new Component(this, uid); + component->m_version = version; + component->m_important = important; + appendComponent(component); + return true; + } } QString ComponentList::getComponentVersion(const QString& uid) const { - const auto iter = d->componentIndex.find(uid); - if (iter != d->componentIndex.end()) - { - return (*iter)->getVersion(); - } - return QString(); + const auto iter = d->componentIndex.find(uid); + if (iter != d->componentIndex.end()) + { + return (*iter)->getVersion(); + } + return QString(); +} + +void ComponentList::disableInteraction(bool disable) +{ + if(d->interactionDisabled != disable) { + d->interactionDisabled = disable; + auto size = d->components.size(); + if(size) { + emit dataChanged(index(0), index(size - 1)); + } + } } diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h index cf4d9975..7b5e1385 100644 --- a/api/logic/minecraft/ComponentList.h +++ b/api/logic/minecraft/ComponentList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,111 +36,115 @@ class ComponentUpdateTask; class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel { - Q_OBJECT - friend ComponentUpdateTask; + Q_OBJECT + friend ComponentUpdateTask; public: - enum Columns - { - NameColumn = 0, - VersionColumn, - NUM_COLUMNS - }; + enum Columns + { + NameColumn = 0, + VersionColumn, + NUM_COLUMNS + }; - explicit ComponentList(MinecraftInstance * instance); - virtual ~ComponentList(); + explicit ComponentList(MinecraftInstance * instance); + virtual ~ComponentList(); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; - virtual int columnCount(const QModelIndex &parent) const override; - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + virtual int columnCount(const QModelIndex &parent) const override; + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. - void buildingFromScratch(); + /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch. + void buildingFromScratch(); - /// install more jar mods - void installJarMods(QStringList selectedFiles); + /// install more jar mods + void installJarMods(QStringList selectedFiles); - /// install a jar/zip as a replacement for the main jar - void installCustomJar(QString selectedFile); + /// install a jar/zip as a replacement for the main jar + void installCustomJar(QString selectedFile); - enum MoveDirection { MoveUp, MoveDown }; - /// move component file # up or down the list - void move(const int index, const MoveDirection direction); + enum MoveDirection { MoveUp, MoveDown }; + /// move component file # up or down the list + void move(const int index, const MoveDirection direction); - /// remove component file # - including files/records - bool remove(const int index); + /// remove component file # - including files/records + bool remove(const int index); - /// remove component file by id - including files/records - bool remove(const QString id); + /// remove component file by id - including files/records + bool remove(const QString id); - bool customize(int index); + bool customize(int index); - bool revertToBase(int index); + bool revertToBase(int index); - /// reload the list, reload all components, resolve dependencies - void reload(Net::Mode netmode); + /// reload the list, reload all components, resolve dependencies + void reload(Net::Mode netmode); - // reload all components, resolve dependencies - void resolve(Net::Mode netmode); + // reload all components, resolve dependencies + void resolve(Net::Mode netmode); - /// get current running task... - shared_qobject_ptr getCurrentTask(); + /// get current running task... + shared_qobject_ptr getCurrentTask(); - std::shared_ptr getProfile() const; + std::shared_ptr getProfile() const; - // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config - void setOldConfigVersion(const QString &uid, const QString &version); + // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config + void setOldConfigVersion(const QString &uid, const QString &version); - QString getComponentVersion(const QString &uid) const; + QString getComponentVersion(const QString &uid) const; - bool setComponentVersion(const QString &uid, const QString &version, bool important = false); + bool setComponentVersion(const QString &uid, const QString &version, bool important = false); - bool installEmpty(const QString &uid, const QString &name); + bool installEmpty(const QString &uid, const QString &name); - QString patchFilePathForUid(const QString &uid) const; + QString patchFilePathForUid(const QString &uid) const; - /// if there is a save scheduled, do it now. - void saveNow(); + /// if there is a save scheduled, do it now. + void saveNow(); + +signals: + void minecraftChanged(); public: - /// get the profile component by id - Component * getComponent(const QString &id); + /// get the profile component by id + Component * getComponent(const QString &id); - /// get the profile component by index - Component * getComponent(int index); + /// get the profile component by index + Component * getComponent(int index); private: - void scheduleSave(); - bool saveIsScheduled() const; + void scheduleSave(); + bool saveIsScheduled() const; - /// apply the component patches. Catches all the errors and returns true/false for success/failure - void invalidateLaunchProfile(); + /// apply the component patches. Catches all the errors and returns true/false for success/failure + void invalidateLaunchProfile(); - /// Add the component to the internal list of patches - void appendComponent(ComponentPtr component); - /// insert component so that its index is ideally the specified one (returns real index) - void insertComponent(size_t index, ComponentPtr component); + /// Add the component to the internal list of patches + void appendComponent(ComponentPtr component); + /// insert component so that its index is ideally the specified one (returns real index) + void insertComponent(size_t index, ComponentPtr component); - QString componentsFilePath() const; - QString patchesPattern() const; + QString componentsFilePath() const; + QString patchesPattern() const; private slots: - void save_internal(); - void updateSucceeded(); - void updateFailed(const QString & error); - void componentDataChanged(); + void save_internal(); + void updateSucceeded(); + void updateFailed(const QString & error); + void componentDataChanged(); + void disableInteraction(bool disable); private: - bool load(); - bool installJarMods_internal(QStringList filepaths); + bool load(); + bool installJarMods_internal(QStringList filepaths); bool installCustomJar_internal(QString filepath); - bool removeComponent_internal(ComponentPtr patch); + bool removeComponent_internal(ComponentPtr patch); - bool migratePreComponentConfig(); + bool migratePreComponentConfig(); private: /* data */ - std::unique_ptr d; + std::unique_ptr d; }; diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h index 26ca5049..7a3d498b 100644 --- a/api/logic/minecraft/ComponentList_p.h +++ b/api/logic/minecraft/ComponentList_p.h @@ -13,30 +13,31 @@ using ConnectionList = QList; struct ComponentListData { - // the instance this belongs to - MinecraftInstance *m_instance; + // the instance this belongs to + MinecraftInstance *m_instance; - // the launch profile (volatile, temporary thing created on demand) - std::shared_ptr m_profile; + // the launch profile (volatile, temporary thing created on demand) + std::shared_ptr m_profile; - // version information migrated from instance.cfg file. Single use on migration! - std::map m_oldConfigVersions; - QString getOldConfigVersion(const QString& uid) const - { - const auto iter = m_oldConfigVersions.find(uid); - if(iter != m_oldConfigVersions.cend()) - { - return (*iter).second; - } - return QString(); - } + // version information migrated from instance.cfg file. Single use on migration! + std::map m_oldConfigVersions; + QString getOldConfigVersion(const QString& uid) const + { + const auto iter = m_oldConfigVersions.find(uid); + if(iter != m_oldConfigVersions.cend()) + { + return (*iter).second; + } + return QString(); + } - // persistent list of components and related machinery - ComponentContainer components; - ComponentIndex componentIndex; - bool dirty = false; - QTimer m_saveTimer; - shared_qobject_ptr m_updateTask; - bool loaded = false; + // persistent list of components and related machinery + ComponentContainer components; + ComponentIndex componentIndex; + bool dirty = false; + QTimer m_saveTimer; + shared_qobject_ptr m_updateTask; + bool loaded = false; + bool interactionDisabled = true; }; diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp index 2d6ceb91..15003160 100644 --- a/api/logic/minecraft/ComponentUpdateTask.cpp +++ b/api/logic/minecraft/ComponentUpdateTask.cpp @@ -32,12 +32,12 @@ */ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent) - : Task(parent) + : Task(parent) { - d.reset(new ComponentUpdateTaskData); - d->m_list = list; - d->mode = mode; - d->netmode = netmode; + d.reset(new ComponentUpdateTaskData); + d->m_list = list; + d->mode = mode; + d->netmode = netmode; } ComponentUpdateTask::~ComponentUpdateTask() @@ -46,356 +46,356 @@ ComponentUpdateTask::~ComponentUpdateTask() void ComponentUpdateTask::executeTask() { - qDebug() << "Loading components"; - loadComponents(); + qDebug() << "Loading components"; + loadComponents(); } namespace { enum class LoadResult { - LoadedLocal, - RequiresRemote, - Failed + LoadedLocal, + RequiresRemote, + Failed }; LoadResult composeLoadResult(LoadResult a, LoadResult b) { - if (a < b) - { - return b; - } - return a; + if (a < b) + { + return b; + } + return a; } static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr& loadTask, Net::Mode netmode) { - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto customPatchFilename = component->getFilename(); - if(QFile::exists(customPatchFilename)) - { - // if local file exists... - - // check for uid problems inside... - bool fileChanged = false; - auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); - if(file->uid != component->m_uid) - { - file->uid = component->m_uid; - fileChanged = true; - } - if(fileChanged) - { - // FIXME: @QUALITY do not ignore return value - ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); - } - - component->m_file = file; - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); - component->m_metaVersion = metaVersion; - if(metaVersion->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaVersion->load(netmode); - loadTask = metaVersion->getCurrentTask(); - if(loadTask) - result = LoadResult::RequiresRemote; - else if (metaVersion->isLoaded()) - result = LoadResult::LoadedLocal; - else - result = LoadResult::Failed; - } - } - return result; + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto customPatchFilename = component->getFilename(); + if(QFile::exists(customPatchFilename)) + { + // if local file exists... + + // check for uid problems inside... + bool fileChanged = false; + auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false); + if(file->uid != component->m_uid) + { + file->uid = component->m_uid; + fileChanged = true; + } + if(fileChanged) + { + // FIXME: @QUALITY do not ignore return value + ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename); + } + + component->m_file = file; + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version); + component->m_metaVersion = metaVersion; + if(metaVersion->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaVersion->load(netmode); + loadTask = metaVersion->getCurrentTask(); + if(loadTask) + result = LoadResult::RequiresRemote; + else if (metaVersion->isLoaded()) + result = LoadResult::LoadedLocal; + else + result = LoadResult::Failed; + } + } + return result; } // FIXME: dead code. determine if this can still be useful? /* static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr& loadTask, Net::Mode netmode) { - if(component->m_loaded) - { - qDebug() << component->getName() << "is already loaded"; - return LoadResult::LoadedLocal; - } - - LoadResult result = LoadResult::Failed; - auto metaList = ENV.metadataIndex()->get(component->m_uid); - if(metaList->isLoaded()) - { - component->m_loaded = true; - result = LoadResult::LoadedLocal; - } - else - { - metaList->load(netmode); - loadTask = metaList->getCurrentTask(); - result = LoadResult::RequiresRemote; - } - return result; + if(component->m_loaded) + { + qDebug() << component->getName() << "is already loaded"; + return LoadResult::LoadedLocal; + } + + LoadResult result = LoadResult::Failed; + auto metaList = ENV.metadataIndex()->get(component->m_uid); + if(metaList->isLoaded()) + { + component->m_loaded = true; + result = LoadResult::LoadedLocal; + } + else + { + metaList->load(netmode); + loadTask = metaList->getCurrentTask(); + result = LoadResult::RequiresRemote; + } + return result; } */ static LoadResult loadIndex(shared_qobject_ptr& loadTask, Net::Mode netmode) { - // FIXME: DECIDE. do we want to run the update task anyway? - if(ENV.metadataIndex()->isLoaded()) - { - qDebug() << "Index is already loaded"; - return LoadResult::LoadedLocal; - } - ENV.metadataIndex()->load(netmode); - loadTask = ENV.metadataIndex()->getCurrentTask(); - if(loadTask) - { - return LoadResult::RequiresRemote; - } - // FIXME: this is assuming the load succeeded... did it really? - return LoadResult::LoadedLocal; + // FIXME: DECIDE. do we want to run the update task anyway? + if(ENV.metadataIndex()->isLoaded()) + { + qDebug() << "Index is already loaded"; + return LoadResult::LoadedLocal; + } + ENV.metadataIndex()->load(netmode); + loadTask = ENV.metadataIndex()->getCurrentTask(); + if(loadTask) + { + return LoadResult::RequiresRemote; + } + // FIXME: this is assuming the load succeeded... did it really? + return LoadResult::LoadedLocal; } } void ComponentUpdateTask::loadComponents() { - LoadResult result = LoadResult::LoadedLocal; - size_t taskIndex = 0; - size_t componentIndex = 0; - d->remoteLoadSuccessful = true; - // load the main index (it is needed to determine if components can revert) - { - // FIXME: tear out as a method? or lambda? - shared_qobject_ptr indexLoadTask; - auto singleResult = loadIndex(indexLoadTask, d->netmode); - result = composeLoadResult(result, singleResult); - if(indexLoadTask) - { - qDebug() << "Remote loading is being run for metadata index"; - RemoteLoadStatus status; - status.type = RemoteLoadStatus::Type::Index; - d->remoteLoadStatusList.append(status); - connect(indexLoadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - taskIndex++; - } - } - // load all the components OR their lists... - for (auto component: d->m_list->d->components) - { - shared_qobject_ptr loadTask; - LoadResult singleResult; - RemoteLoadStatus::Type loadType; - // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... + LoadResult result = LoadResult::LoadedLocal; + size_t taskIndex = 0; + size_t componentIndex = 0; + d->remoteLoadSuccessful = true; + // load the main index (it is needed to determine if components can revert) + { + // FIXME: tear out as a method? or lambda? + shared_qobject_ptr indexLoadTask; + auto singleResult = loadIndex(indexLoadTask, d->netmode); + result = composeLoadResult(result, singleResult); + if(indexLoadTask) + { + qDebug() << "Remote loading is being run for metadata index"; + RemoteLoadStatus status; + status.type = RemoteLoadStatus::Type::Index; + d->remoteLoadStatusList.append(status); + connect(indexLoadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(indexLoadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + taskIndex++; + } + } + // load all the components OR their lists... + for (auto component: d->m_list->d->components) + { + shared_qobject_ptr loadTask; + LoadResult singleResult; + RemoteLoadStatus::Type loadType; + // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that... #if 0 - switch(d->mode) - { - case Mode::Launch: - { - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; - break; - } - case Mode::Resolution: - { - singleResult = loadComponentList(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::List; - break; - } - } + switch(d->mode) + { + case Mode::Launch: + { + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; + break; + } + case Mode::Resolution: + { + singleResult = loadComponentList(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::List; + break; + } + } #else - singleResult = loadComponent(component, loadTask, d->netmode); - loadType = RemoteLoadStatus::Type::Version; + singleResult = loadComponent(component, loadTask, d->netmode); + loadType = RemoteLoadStatus::Type::Version; #endif - if(singleResult == LoadResult::LoadedLocal) - { - component->updateCachedData(); - } - result = composeLoadResult(result, singleResult); - if (loadTask) - { - qDebug() << "Remote loading is being run for" << component->getName(); - connect(loadTask.get(), &Task::succeeded, [=]() - { - remoteLoadSucceeded(taskIndex); - }); - connect(loadTask.get(), &Task::failed, [=](const QString & error) - { - remoteLoadFailed(taskIndex, error); - }); - RemoteLoadStatus status; - status.type = loadType; - status.componentListIndex = componentIndex; - d->remoteLoadStatusList.append(status); - taskIndex++; - } - componentIndex++; - } - d->remoteTasksInProgress = taskIndex; - switch(result) - { - case LoadResult::LoadedLocal: - { - // Everything got loaded. Advance to dependency resolution. - resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); - break; - } - case LoadResult::RequiresRemote: - { - // we wait for signals. - break; - } - case LoadResult::Failed: - { - emitFailed(tr("Some component metadata load tasks failed.")); - break; - } - } + if(singleResult == LoadResult::LoadedLocal) + { + component->updateCachedData(); + } + result = composeLoadResult(result, singleResult); + if (loadTask) + { + qDebug() << "Remote loading is being run for" << component->getName(); + connect(loadTask.get(), &Task::succeeded, [=]() + { + remoteLoadSucceeded(taskIndex); + }); + connect(loadTask.get(), &Task::failed, [=](const QString & error) + { + remoteLoadFailed(taskIndex, error); + }); + RemoteLoadStatus status; + status.type = loadType; + status.componentListIndex = componentIndex; + d->remoteLoadStatusList.append(status); + taskIndex++; + } + componentIndex++; + } + d->remoteTasksInProgress = taskIndex; + switch(result) + { + case LoadResult::LoadedLocal: + { + // Everything got loaded. Advance to dependency resolution. + resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); + break; + } + case LoadResult::RequiresRemote: + { + // we wait for signals. + break; + } + case LoadResult::Failed: + { + emitFailed(tr("Some component metadata load tasks failed.")); + break; + } + } } namespace { - struct RequireEx : public Meta::Require - { - size_t indexOfFirstDependee = 0; - }; - struct RequireCompositionResult - { - bool ok; - RequireEx outcome; - }; - using RequireExSet = std::set; + struct RequireEx : public Meta::Require + { + size_t indexOfFirstDependee = 0; + }; + struct RequireCompositionResult + { + bool ok; + RequireEx outcome; + }; + using RequireExSet = std::set; } static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b) { - assert(a.uid == b.uid); - RequireEx out; - out.uid = a.uid; - out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); - if(a.equalsVersion.isEmpty()) - { - out.equalsVersion = b.equalsVersion; - } - else if (b.equalsVersion.isEmpty()) - { - out.equalsVersion = a.equalsVersion; - } - else if (a.equalsVersion == b.equalsVersion) - { - out.equalsVersion = a.equalsVersion; - } - else - { - // FIXME: mark error as explicit version conflict - return {false, out}; - } - - if(a.suggests.isEmpty()) - { - out.suggests = b.suggests; - } - else if (b.suggests.isEmpty()) - { - out.suggests = a.suggests; - } - else - { - Version aVer(a.suggests); - Version bVer(b.suggests); - out.suggests = (aVer < bVer ? b.suggests : a.suggests); - } - return {true, out}; + assert(a.uid == b.uid); + RequireEx out; + out.uid = a.uid; + out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee); + if(a.equalsVersion.isEmpty()) + { + out.equalsVersion = b.equalsVersion; + } + else if (b.equalsVersion.isEmpty()) + { + out.equalsVersion = a.equalsVersion; + } + else if (a.equalsVersion == b.equalsVersion) + { + out.equalsVersion = a.equalsVersion; + } + else + { + // FIXME: mark error as explicit version conflict + return {false, out}; + } + + if(a.suggests.isEmpty()) + { + out.suggests = b.suggests; + } + else if (b.suggests.isEmpty()) + { + out.suggests = a.suggests; + } + else + { + Version aVer(a.suggests); + Version bVer(b.suggests); + out.suggests = (aVer < bVer ? b.suggests : a.suggests); + } + return {true, out}; } // gather the requirements from all components, finding any obvious conflicts static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output) { - bool succeeded = true; - size_t componentNum = 0; - for(auto component: input) - { - auto &componentRequires = component->m_cachedRequires; - for(const auto & componentRequire: componentRequires) - { - auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ - return req.uid == componentRequire.uid; - }); - - RequireEx componenRequireEx; - componenRequireEx.uid = componentRequire.uid; - componenRequireEx.suggests = componentRequire.suggests; - componenRequireEx.equalsVersion = componentRequire.equalsVersion; - componenRequireEx.indexOfFirstDependee = componentNum; - - if(found != output.cend()) - { - // found... process it further - auto result = composeRequirement(componenRequireEx, *found); - if(result.ok) - { - output.erase(componenRequireEx); - output.insert(result.outcome); - } - else - { - qCritical() - << "Conflicting requirements:" - << componentRequire.uid - << "versions:" - << componentRequire.equalsVersion - << ";" - << (*found).equalsVersion; - } - succeeded &= result.ok; - } - else - { - // not found, accumulate - output.insert(componenRequireEx); - } - } - componentNum++; - } - return succeeded; + bool succeeded = true; + size_t componentNum = 0; + for(auto component: input) + { + auto &componentRequires = component->m_cachedRequires; + for(const auto & componentRequire: componentRequires) + { + auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){ + return req.uid == componentRequire.uid; + }); + + RequireEx componenRequireEx; + componenRequireEx.uid = componentRequire.uid; + componenRequireEx.suggests = componentRequire.suggests; + componenRequireEx.equalsVersion = componentRequire.equalsVersion; + componenRequireEx.indexOfFirstDependee = componentNum; + + if(found != output.cend()) + { + // found... process it further + auto result = composeRequirement(componenRequireEx, *found); + if(result.ok) + { + output.erase(componenRequireEx); + output.insert(result.outcome); + } + else + { + qCritical() + << "Conflicting requirements:" + << componentRequire.uid + << "versions:" + << componentRequire.equalsVersion + << ";" + << (*found).equalsVersion; + } + succeeded &= result.ok; + } + else + { + // not found, accumulate + output.insert(componenRequireEx); + } + } + componentNum++; + } + return succeeded; } /// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps) static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove) { - for(const auto & component: components) - { - if(!component->m_dependencyOnly) - continue; - if(!component->m_cachedVolatile) - continue; - RequireEx reqNeedle; - reqNeedle.uid = component->m_uid; - const auto iter = reqs.find(reqNeedle); - if(iter == reqs.cend()) - { - toRemove.append(component->m_uid); - } - } + for(const auto & component: components) + { + if(!component->m_dependencyOnly) + continue; + if(!component->m_cachedVolatile) + continue; + RequireEx reqNeedle; + reqNeedle.uid = component->m_uid; + const auto iter = reqs.find(reqNeedle); + if(iter == reqs.cend()) + { + toRemove.append(component->m_uid); + } + } } /** @@ -408,86 +408,86 @@ static void getTrivialRemovals(const ComponentContainer & components, const Requ */ static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange) { - enum class Decision - { - Undetermined, - Met, - Missing, - VersionNotSame, - LockedVersionNotSame - } decision = Decision::Undetermined; - - QString reqStr; - bool succeeded = true; - // list the composed requirements and say if they are met or unmet - for(auto & req: input) - { - do - { - if(req.equalsVersion.isEmpty()) - { - reqStr = QString("Req: %1").arg(req.uid); - if(index.contains(req.uid)) - { - decision = Decision::Met; - } - else - { - toAdd.insert(req); - decision = Decision::Missing; - } - break; - } - else - { - reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); - const auto & compIter = index.find(req.uid); - if(compIter == index.cend()) - { - toAdd.insert(req); - decision = Decision::Missing; - break; - } - auto & comp = (*compIter); - if(comp->getVersion() != req.equalsVersion) - { - if(comp->m_dependencyOnly) - { - decision = Decision::VersionNotSame; - } - else - { - decision = Decision::LockedVersionNotSame; - } - break; - } - decision = Decision::Met; - } - } while(false); - switch(decision) - { - case Decision::Undetermined: - qCritical() << "No decision for" << reqStr; - succeeded = false; - break; - case Decision::Met: - qDebug() << reqStr << "Is met."; - break; - case Decision::Missing: - qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; - toAdd.insert(req); - break; - case Decision::VersionNotSame: - qDebug() << reqStr << "already has different version that can be changed."; - toChange.insert(req); - break; - case Decision::LockedVersionNotSame: - qDebug() << reqStr << "already has different version that cannot be changed."; - succeeded = false; - break; - } - } - return succeeded; + enum class Decision + { + Undetermined, + Met, + Missing, + VersionNotSame, + LockedVersionNotSame + } decision = Decision::Undetermined; + + QString reqStr; + bool succeeded = true; + // list the composed requirements and say if they are met or unmet + for(auto & req: input) + { + do + { + if(req.equalsVersion.isEmpty()) + { + reqStr = QString("Req: %1").arg(req.uid); + if(index.contains(req.uid)) + { + decision = Decision::Met; + } + else + { + toAdd.insert(req); + decision = Decision::Missing; + } + break; + } + else + { + reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion); + const auto & compIter = index.find(req.uid); + if(compIter == index.cend()) + { + toAdd.insert(req); + decision = Decision::Missing; + break; + } + auto & comp = (*compIter); + if(comp->getVersion() != req.equalsVersion) + { + if(comp->m_dependencyOnly) + { + decision = Decision::VersionNotSame; + } + else + { + decision = Decision::LockedVersionNotSame; + } + break; + } + decision = Decision::Met; + } + } while(false); + switch(decision) + { + case Decision::Undetermined: + qCritical() << "No decision for" << reqStr; + succeeded = false; + break; + case Decision::Met: + qDebug() << reqStr << "Is met."; + break; + case Decision::Missing: + qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; + toAdd.insert(req); + break; + case Decision::VersionNotSame: + qDebug() << reqStr << "already has different version that can be changed."; + toChange.insert(req); + break; + case Decision::LockedVersionNotSame: + qDebug() << reqStr << "already has different version that cannot be changed."; + succeeded = false; + break; + } + } + return succeeded; } // FIXME, TODO: decouple dependency resolution from loading @@ -495,197 +495,206 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi // FIXME: throw all this away and use a graph void ComponentUpdateTask::resolveDependencies(bool checkOnly) { - qDebug() << "Resolving dependencies"; - /* - * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: - * 1. There are conflicting dependencies on the same uid with different exact version numbers - * -> hard error - * 2. A dependency has non-matching exact version number - * -> hard error - * 3. A dependency is entirely missing and needs to be injected before the dependee(s) - * -> requirements are injected - * - * NOTE: this is a placeholder and should eventually be replaced with something 'serious' - */ - auto & components = d->m_list->d->components; - auto & componentIndex = d->m_list->d->componentIndex; - - RequireExSet allRequires; - QStringList toRemove; - do - { - allRequires.clear(); - toRemove.clear(); - if(!gatherRequirementsFromComponents(components, allRequires)) - { - emitFailed(tr("Conflicting requirements detected during dependency checking!")); - return; - } - getTrivialRemovals(components, allRequires, toRemove); - if(!toRemove.isEmpty()) - { - qDebug() << "Removing obsolete components..."; - for(auto & remove : toRemove) - { - qDebug() << "Removing" << remove; - d->m_list->remove(remove); - } - } - } while (!toRemove.isEmpty()); - RequireExSet toAdd; - RequireExSet toChange; - bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); - if(!succeeded) - { - emitFailed(tr("Instance has conflicting dependencies.")); - return; - } - if(checkOnly) - { - if(toAdd.size() || toChange.size()) - { - emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); - } - else - { - emitSucceeded(); - } - return; - } - - bool recursionNeeded = false; - if(toAdd.size()) - { - // add stuff... - for(auto &add: toAdd) - { - ComponentPtr component = new Component(d->m_list, add.uid); - if(!add.equalsVersion.isEmpty()) - { - // exact version - qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; - component->m_version = add.equalsVersion; - } - else - { - // version needs to be decided - qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; + qDebug() << "Resolving dependencies"; + /* + * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: + * 1. There are conflicting dependencies on the same uid with different exact version numbers + * -> hard error + * 2. A dependency has non-matching exact version number + * -> hard error + * 3. A dependency is entirely missing and needs to be injected before the dependee(s) + * -> requirements are injected + * + * NOTE: this is a placeholder and should eventually be replaced with something 'serious' + */ + auto & components = d->m_list->d->components; + auto & componentIndex = d->m_list->d->componentIndex; + + RequireExSet allRequires; + QStringList toRemove; + do + { + allRequires.clear(); + toRemove.clear(); + if(!gatherRequirementsFromComponents(components, allRequires)) + { + emitFailed(tr("Conflicting requirements detected during dependency checking!")); + return; + } + getTrivialRemovals(components, allRequires, toRemove); + if(!toRemove.isEmpty()) + { + qDebug() << "Removing obsolete components..."; + for(auto & remove : toRemove) + { + qDebug() << "Removing" << remove; + d->m_list->remove(remove); + } + } + } while (!toRemove.isEmpty()); + RequireExSet toAdd; + RequireExSet toChange; + bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); + if(!succeeded) + { + emitFailed(tr("Instance has conflicting dependencies.")); + return; + } + if(checkOnly) + { + if(toAdd.size() || toChange.size()) + { + emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); + } + else + { + emitSucceeded(); + } + return; + } + + bool recursionNeeded = false; + if(toAdd.size()) + { + // add stuff... + for(auto &add: toAdd) + { + ComponentPtr component = new Component(d->m_list, add.uid); + if(!add.equalsVersion.isEmpty()) + { + // exact version + qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; + component->m_version = add.equalsVersion; + } + else + { + // version needs to be decided + qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; // ############################################################################################################ // HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. - if(!add.suggests.isEmpty()) - { - component->m_version = add.suggests; - } - else - { - if(add.uid == "org.lwjgl") - { - component->m_version = "2.9.1"; - } - else if (add.uid == "org.lwjgl3") - { - component->m_version = "3.1.2"; - } - } + if(!add.suggests.isEmpty()) + { + component->m_version = add.suggests; + } + else + { + if(add.uid == "org.lwjgl") + { + component->m_version = "2.9.1"; + } + else if (add.uid == "org.lwjgl3") + { + component->m_version = "3.1.2"; + } + else if (add.uid == "net.fabricmc.intermediary") + { + auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){ + return cmp->getID() == "net.minecraft"; + }); + if(minecraft != components.end()) { + component->m_version = (*minecraft)->getVersion(); + } + } + } // HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. // ############################################################################################################ - } - component->m_dependencyOnly = true; - // FIXME: this should not work directly with the component list - d->m_list->insertComponent(add.indexOfFirstDependee, component); - componentIndex[add.uid] = component; - } - recursionNeeded = true; - } - if(toChange.size()) - { - // change a version of something that exists - for(auto &change: toChange) - { - // FIXME: this should not work directly with the component list - qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; - auto component = componentIndex[change.uid]; - component->setVersion(change.equalsVersion); - } - recursionNeeded = true; - } - - if(recursionNeeded) - { - loadComponents(); - } - else - { - emitSucceeded(); - } + } + component->m_dependencyOnly = true; + // FIXME: this should not work directly with the component list + d->m_list->insertComponent(add.indexOfFirstDependee, component); + componentIndex[add.uid] = component; + } + recursionNeeded = true; + } + if(toChange.size()) + { + // change a version of something that exists + for(auto &change: toChange) + { + // FIXME: this should not work directly with the component list + qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; + auto component = componentIndex[change.uid]; + component->setVersion(change.equalsVersion); + } + recursionNeeded = true; + } + + if(recursionNeeded) + { + loadComponents(); + } + else + { + emitSucceeded(); + } } void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) { - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "succeeded"; - taskSlot.succeeded = false; - taskSlot.finished = true; - d->remoteTasksInProgress --; - // update the cached data of the component from the downloaded version file. - if (taskSlot.type == RemoteLoadStatus::Type::Version) - { - auto component = d->m_list->getComponent(taskSlot.componentListIndex); - component->m_loaded = true; - component->updateCachedData(); - } - checkIfAllFinished(); + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "succeeded"; + taskSlot.succeeded = false; + taskSlot.finished = true; + d->remoteTasksInProgress --; + // update the cached data of the component from the downloaded version file. + if (taskSlot.type == RemoteLoadStatus::Type::Version) + { + auto component = d->m_list->getComponent(taskSlot.componentListIndex); + component->m_loaded = true; + component->updateCachedData(); + } + checkIfAllFinished(); } void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) { - auto &taskSlot = d->remoteLoadStatusList[taskIndex]; - if(taskSlot.finished) - { - qWarning() << "Got multiple results from remote load task" << taskIndex; - return; - } - qDebug() << "Remote task" << taskIndex << "failed: " << msg; - d->remoteLoadSuccessful = false; - taskSlot.succeeded = false; - taskSlot.finished = true; - taskSlot.error = msg; - d->remoteTasksInProgress --; - checkIfAllFinished(); + auto &taskSlot = d->remoteLoadStatusList[taskIndex]; + if(taskSlot.finished) + { + qWarning() << "Got multiple results from remote load task" << taskIndex; + return; + } + qDebug() << "Remote task" << taskIndex << "failed: " << msg; + d->remoteLoadSuccessful = false; + taskSlot.succeeded = false; + taskSlot.finished = true; + taskSlot.error = msg; + d->remoteTasksInProgress --; + checkIfAllFinished(); } void ComponentUpdateTask::checkIfAllFinished() { - if(d->remoteTasksInProgress) - { - // not yet... - return; - } - if(d->remoteLoadSuccessful) - { - // nothing bad happened... clear the temp load status and proceed with looking at dependencies - d->remoteLoadStatusList.clear(); - resolveDependencies(d->mode == Mode::Launch); - } - else - { - // remote load failed... report error and bail - QStringList allErrorsList; - for(auto & item: d->remoteLoadStatusList) - { - if(!item.succeeded) - { - allErrorsList.append(item.error); - } - } - auto allErrors = allErrorsList.join("\n"); - emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); - d->remoteLoadStatusList.clear(); - } + if(d->remoteTasksInProgress) + { + // not yet... + return; + } + if(d->remoteLoadSuccessful) + { + // nothing bad happened... clear the temp load status and proceed with looking at dependencies + d->remoteLoadStatusList.clear(); + resolveDependencies(d->mode == Mode::Launch); + } + else + { + // remote load failed... report error and bail + QStringList allErrorsList; + for(auto & item: d->remoteLoadStatusList) + { + if(!item.succeeded) + { + allErrorsList.append(item.error); + } + } + auto allErrors = allErrorsList.join("\n"); + emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors)); + d->remoteLoadStatusList.clear(); + } } diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h index 11d122b6..4cb29a89 100644 --- a/api/logic/minecraft/ComponentUpdateTask.h +++ b/api/logic/minecraft/ComponentUpdateTask.h @@ -9,29 +9,29 @@ struct ComponentUpdateTaskData; class ComponentUpdateTask : public Task { - Q_OBJECT + Q_OBJECT public: - enum class Mode - { - Launch, - Resolution - }; + enum class Mode + { + Launch, + Resolution + }; public: - explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); - virtual ~ComponentUpdateTask(); + explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0); + virtual ~ComponentUpdateTask(); protected: - void executeTask(); + void executeTask(); private: - void loadComponents(); - void resolveDependencies(bool checkOnly); + void loadComponents(); + void resolveDependencies(bool checkOnly); - void remoteLoadSucceeded(size_t index); - void remoteLoadFailed(size_t index, const QString &msg); - void checkIfAllFinished(); + void remoteLoadSucceeded(size_t index); + void remoteLoadFailed(size_t index, const QString &msg); + void checkIfAllFinished(); private: - std::unique_ptr d; + std::unique_ptr d; }; diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h index a5216506..5989cf07 100644 --- a/api/logic/minecraft/ComponentUpdateTask_p.h +++ b/api/logic/minecraft/ComponentUpdateTask_p.h @@ -9,24 +9,24 @@ class ComponentList; struct RemoteLoadStatus { - enum class Type - { - Index, - List, - Version - } type = Type::Version; - size_t componentListIndex = 0; - bool finished = false; - bool succeeded = false; - QString error; + enum class Type + { + Index, + List, + Version + } type = Type::Version; + size_t componentListIndex = 0; + bool finished = false; + bool succeeded = false; + QString error; }; struct ComponentUpdateTaskData { - ComponentList * m_list = nullptr; - QList remoteLoadStatusList; - bool remoteLoadSuccessful = true; - size_t remoteTasksInProgress = 0; - ComponentUpdateTask::Mode mode; - Net::Mode netmode; + ComponentList * m_list = nullptr; + QList remoteLoadStatusList; + bool remoteLoadSuccessful = true; + size_t remoteTasksInProgress = 0; + ComponentUpdateTask::Mode mode; + Net::Mode netmode; }; diff --git a/api/logic/minecraft/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h index f842008c..959325c6 100644 --- a/api/logic/minecraft/GradleSpecifier.h +++ b/api/logic/minecraft/GradleSpecifier.h @@ -6,138 +6,138 @@ struct GradleSpecifier { - GradleSpecifier() - { - m_valid = false; - } - GradleSpecifier(QString value) - { - operator=(value); - } - GradleSpecifier & operator =(const QString & value) - { - /* - org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar - DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" - DEBUG 1 "org.gradle.test.classifiers" - DEBUG 2 "service" - DEBUG 3 "1.0" - DEBUG 4 ":jdk15" - DEBUG 5 "jdk15" - DEBUG 6 "@jar" - DEBUG 7 "jar" - */ - QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); - m_valid = matcher.exactMatch(value); - auto elements = matcher.capturedTexts(); - m_groupId = elements[1]; - m_artifactId = elements[2]; - m_version = elements[3]; - m_classifier = elements[5]; - if(!elements[7].isEmpty()) - { - m_extension = elements[7]; - } - return *this; - } - operator QString() const - { - if(!m_valid) - return "INVALID"; - QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; - if(!m_classifier.isEmpty()) - { - retval += ":" + m_classifier; - } - if(m_extension.isExplicit()) - { - retval += "@" + m_extension; - } - return retval; - } - QString getFileName() const - { - QString filename = m_artifactId + '-' + m_version; - if(!m_classifier.isEmpty()) - { - filename += "-" + m_classifier; - } - filename += "." + m_extension; - return filename; - } - QString toPath(const QString & filenameOverride = QString()) const - { - if(!m_valid) - return "INVALID"; - QString filename; - if(filenameOverride.isEmpty()) - { - filename = getFileName(); - } - else - { - filename = filenameOverride; - } - QString path = m_groupId; - path.replace('.', '/'); - path += '/' + m_artifactId + '/' + m_version + '/' + filename; - return path; - } - inline bool valid() const - { - return m_valid; - } - inline QString version() const - { - return m_version; - } - inline QString groupId() const - { - return m_groupId; - } - inline QString artifactId() const - { - return m_artifactId; - } - inline void setClassifier(const QString & classifier) - { - m_classifier = classifier; - } - inline QString classifier() const - { - return m_classifier; - } - inline QString extension() const - { - return m_extension; - } - inline QString artifactPrefix() const - { - return m_groupId + ":" + m_artifactId; - } - bool matchName(const GradleSpecifier & other) const - { - return other.artifactId() == artifactId() && other.groupId() == groupId(); - } - bool operator==(const GradleSpecifier & other) const - { - if(m_groupId != other.m_groupId) - return false; - if(m_artifactId != other.m_artifactId) - return false; - if(m_version != other.m_version) - return false; - if(m_classifier != other.m_classifier) - return false; - if(m_extension != other.m_extension) - return false; - return true; - } + GradleSpecifier() + { + m_valid = false; + } + GradleSpecifier(QString value) + { + operator=(value); + } + GradleSpecifier & operator =(const QString & value) + { + /* + org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar + DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar" + DEBUG 1 "org.gradle.test.classifiers" + DEBUG 2 "service" + DEBUG 3 "1.0" + DEBUG 4 ":jdk15" + DEBUG 5 "jdk15" + DEBUG 6 "@jar" + DEBUG 7 "jar" + */ + QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?"); + m_valid = matcher.exactMatch(value); + auto elements = matcher.capturedTexts(); + m_groupId = elements[1]; + m_artifactId = elements[2]; + m_version = elements[3]; + m_classifier = elements[5]; + if(!elements[7].isEmpty()) + { + m_extension = elements[7]; + } + return *this; + } + operator QString() const + { + if(!m_valid) + return "INVALID"; + QString retval = m_groupId + ":" + m_artifactId + ":" + m_version; + if(!m_classifier.isEmpty()) + { + retval += ":" + m_classifier; + } + if(m_extension.isExplicit()) + { + retval += "@" + m_extension; + } + return retval; + } + QString getFileName() const + { + QString filename = m_artifactId + '-' + m_version; + if(!m_classifier.isEmpty()) + { + filename += "-" + m_classifier; + } + filename += "." + m_extension; + return filename; + } + QString toPath(const QString & filenameOverride = QString()) const + { + if(!m_valid) + return "INVALID"; + QString filename; + if(filenameOverride.isEmpty()) + { + filename = getFileName(); + } + else + { + filename = filenameOverride; + } + QString path = m_groupId; + path.replace('.', '/'); + path += '/' + m_artifactId + '/' + m_version + '/' + filename; + return path; + } + inline bool valid() const + { + return m_valid; + } + inline QString version() const + { + return m_version; + } + inline QString groupId() const + { + return m_groupId; + } + inline QString artifactId() const + { + return m_artifactId; + } + inline void setClassifier(const QString & classifier) + { + m_classifier = classifier; + } + inline QString classifier() const + { + return m_classifier; + } + inline QString extension() const + { + return m_extension; + } + inline QString artifactPrefix() const + { + return m_groupId + ":" + m_artifactId; + } + bool matchName(const GradleSpecifier & other) const + { + return other.artifactId() == artifactId() && other.groupId() == groupId(); + } + bool operator==(const GradleSpecifier & other) const + { + if(m_groupId != other.m_groupId) + return false; + if(m_artifactId != other.m_artifactId) + return false; + if(m_version != other.m_version) + return false; + if(m_classifier != other.m_classifier) + return false; + if(m_extension != other.m_extension) + return false; + return true; + } private: - QString m_groupId; - QString m_artifactId; - QString m_version; - QString m_classifier; - DefaultVariable m_extension = DefaultVariable("jar"); - bool m_valid = false; + QString m_groupId; + QString m_artifactId; + QString m_version; + QString m_classifier; + DefaultVariable m_extension = DefaultVariable("jar"); + bool m_valid = false; }; diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp index 155522cd..f49ec718 100644 --- a/api/logic/minecraft/GradleSpecifier_test.cpp +++ b/api/logic/minecraft/GradleSpecifier_test.cpp @@ -5,71 +5,71 @@ class GradleSpecifierTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Positive_data() - { - QTest::addColumn("through"); - - QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; - QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; - QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; - QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; - QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; - } - void test_Positive() - { - QFETCH(QString, through); - - QString converted = GradleSpecifier(through); - - QCOMPARE(converted, through); - } - - void test_Path_data() - { - QTest::addColumn("spec"); - QTest::addColumn("expected"); - - QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; - QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; - } - void test_Path() - { - QFETCH(QString, spec); - QFETCH(QString, expected); - - QString converted = GradleSpecifier(spec).toPath(); - - QCOMPARE(converted, expected); - } - void test_Negative_data() - { - QTest::addColumn("input"); - - QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; - QTest::newRow("nonsense") << "I like turtles"; - QTest::newRow("empty string") << ""; - QTest::newRow("missing version") << "herp.derp:artifact"; - } - void test_Negative() - { - QFETCH(QString, input); - - GradleSpecifier spec(input); - QVERIFY(!spec.valid()); - QCOMPARE(spec.operator QString(), QString("INVALID")); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_Positive_data() + { + QTest::addColumn("through"); + + QTest::newRow("3 parter") << "org.gradle.test.classifiers:service:1.0"; + QTest::newRow("classifier") << "org.gradle.test.classifiers:service:1.0:jdk15"; + QTest::newRow("jarextension") << "org.gradle.test.classifiers:service:1.0@jar"; + QTest::newRow("jarboth") << "org.gradle.test.classifiers:service:1.0:jdk15@jar"; + QTest::newRow("packxz") << "org.gradle.test.classifiers:service:1.0:jdk15@jar.pack.xz"; + } + void test_Positive() + { + QFETCH(QString, through); + + QString converted = GradleSpecifier(through); + + QCOMPARE(converted, through); + } + + void test_Path_data() + { + QTest::addColumn("spec"); + QTest::addColumn("expected"); + + QTest::newRow("3 parter") << "group.id:artifact:1.0" << "group/id/artifact/1.0/artifact-1.0.jar"; + QTest::newRow("doom") << "id.software:doom:1.666:demons@wad" << "id/software/doom/1.666/doom-1.666-demons.wad"; + } + void test_Path() + { + QFETCH(QString, spec); + QFETCH(QString, expected); + + QString converted = GradleSpecifier(spec).toPath(); + + QCOMPARE(converted, expected); + } + void test_Negative_data() + { + QTest::addColumn("input"); + + QTest::newRow("too many :") << "org:gradle.test:class:::ifiers:service:1.0::"; + QTest::newRow("nonsense") << "I like turtles"; + QTest::newRow("empty string") << ""; + QTest::newRow("missing version") << "herp.derp:artifact"; + } + void test_Negative() + { + QFETCH(QString, input); + + GradleSpecifier spec(input); + QVERIFY(!spec.valid()); + QCOMPARE(spec.operator QString(), QString("INVALID")); + } }; QTEST_GUILESS_MAIN(GradleSpecifierTest) diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp index 436a39d9..c39bdf04 100644 --- a/api/logic/minecraft/LaunchProfile.cpp +++ b/api/logic/minecraft/LaunchProfile.cpp @@ -3,295 +3,295 @@ void LaunchProfile::clear() { - m_minecraftVersion.clear(); - m_minecraftVersionType.clear(); - m_minecraftAssets.reset(); - m_minecraftArguments.clear(); - m_tweakers.clear(); - m_mainClass.clear(); - m_appletClass.clear(); - m_libraries.clear(); - m_traits.clear(); - m_jarMods.clear(); - m_mainJar.reset(); - m_problemSeverity = ProblemSeverity::None; + m_minecraftVersion.clear(); + m_minecraftVersionType.clear(); + m_minecraftAssets.reset(); + m_minecraftArguments.clear(); + m_tweakers.clear(); + m_mainClass.clear(); + m_appletClass.clear(); + m_libraries.clear(); + m_traits.clear(); + m_jarMods.clear(); + m_mainJar.reset(); + m_problemSeverity = ProblemSeverity::None; } static void applyString(const QString & from, QString & to) { - if(from.isEmpty()) - return; - to = from; + if(from.isEmpty()) + return; + to = from; } void LaunchProfile::applyMinecraftVersion(const QString& id) { - applyString(id, this->m_minecraftVersion); + applyString(id, this->m_minecraftVersion); } void LaunchProfile::applyAppletClass(const QString& appletClass) { - applyString(appletClass, this->m_appletClass); + applyString(appletClass, this->m_appletClass); } void LaunchProfile::applyMainClass(const QString& mainClass) { - applyString(mainClass, this->m_mainClass); + applyString(mainClass, this->m_mainClass); } void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments) { - applyString(minecraftArguments, this->m_minecraftArguments); + applyString(minecraftArguments, this->m_minecraftArguments); } void LaunchProfile::applyMinecraftVersionType(const QString& type) { - applyString(type, this->m_minecraftVersionType); + applyString(type, this->m_minecraftVersionType); } void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets) { - if(assets) - { - m_minecraftAssets = assets; - } + if(assets) + { + m_minecraftAssets = assets; + } } void LaunchProfile::applyTraits(const QSet& traits) { - this->m_traits.unite(traits); + this->m_traits.unite(traits); } void LaunchProfile::applyTweakers(const QStringList& tweakers) { - // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence - QStringList newTweakers; - for(auto & tweaker: m_tweakers) - { - if (tweakers.contains(tweaker)) - { - continue; - } - newTweakers.append(tweaker); - } - // then just append the new tweakers (or moved original ones) - newTweakers += tweakers; - m_tweakers = newTweakers; + // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence + QStringList newTweakers; + for(auto & tweaker: m_tweakers) + { + if (tweakers.contains(tweaker)) + { + continue; + } + newTweakers.append(tweaker); + } + // then just append the new tweakers (or moved original ones) + newTweakers += tweakers; + m_tweakers = newTweakers; } void LaunchProfile::applyJarMods(const QList& jarMods) { - this->m_jarMods.append(jarMods); + this->m_jarMods.append(jarMods); } static int findLibraryByName(QList *haystack, const GradleSpecifier &needle) { - int retval = -1; - for (int i = 0; i < haystack->size(); ++i) - { - if (haystack->at(i)->rawName().matchName(needle)) - { - // only one is allowed. - if (retval != -1) - return -1; - retval = i; - } - } - return retval; + int retval = -1; + for (int i = 0; i < haystack->size(); ++i) + { + if (haystack->at(i)->rawName().matchName(needle)) + { + // only one is allowed. + if (retval != -1) + return -1; + retval = i; + } + } + return retval; } void LaunchProfile::applyMods(const QList& mods) { - QList * list = &m_mods; - for(auto & mod: mods) - { - auto modCopy = Library::limitedCopy(mod); - - // find the mod by name. - const int index = findLibraryByName(list, mod->rawName()); - // mod not found? just add it. - if (index < 0) - { - list->append(modCopy); - return; - } - - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(mod->version()) > Version(existingLibrary->version())) - { - list->replace(index, modCopy); - } - } + QList * list = &m_mods; + for(auto & mod: mods) + { + auto modCopy = Library::limitedCopy(mod); + + // find the mod by name. + const int index = findLibraryByName(list, mod->rawName()); + // mod not found? just add it. + if (index < 0) + { + list->append(modCopy); + return; + } + + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(mod->version()) > Version(existingLibrary->version())) + { + list->replace(index, modCopy); + } + } } void LaunchProfile::applyLibrary(LibraryPtr library) { - if(!library->isActive()) - { - return; - } + if(!library->isActive()) + { + return; + } - QList * list = &m_libraries; - if(library->isNative()) - { - list = &m_nativeLibraries; - } + QList * list = &m_libraries; + if(library->isNative()) + { + list = &m_nativeLibraries; + } - auto libraryCopy = Library::limitedCopy(library); + auto libraryCopy = Library::limitedCopy(library); - // find the library by name. - const int index = findLibraryByName(list, library->rawName()); - // library not found? just add it. - if (index < 0) - { - list->append(libraryCopy); - return; - } + // find the library by name. + const int index = findLibraryByName(list, library->rawName()); + // library not found? just add it. + if (index < 0) + { + list->append(libraryCopy); + return; + } - auto existingLibrary = list->at(index); - // if we are higher it means we should update - if (Version(library->version()) > Version(existingLibrary->version())) - { - list->replace(index, libraryCopy); - } + auto existingLibrary = list->at(index); + // if we are higher it means we should update + if (Version(library->version()) > Version(existingLibrary->version())) + { + list->replace(index, libraryCopy); + } } const LibraryPtr LaunchProfile::getMainJar() const { - return m_mainJar; + return m_mainJar; } void LaunchProfile::applyMainJar(LibraryPtr jar) { - if(jar) - { - m_mainJar = jar; - } + if(jar) + { + m_mainJar = jar; + } } void LaunchProfile::applyProblemSeverity(ProblemSeverity severity) { - if (m_problemSeverity < severity) - { - m_problemSeverity = severity; - } + if (m_problemSeverity < severity) + { + m_problemSeverity = severity; + } } const QList LaunchProfile::getProblems() const { - // FIXME: implement something that actually makes sense here - return {}; + // FIXME: implement something that actually makes sense here + return {}; } QString LaunchProfile::getMinecraftVersion() const { - return m_minecraftVersion; + return m_minecraftVersion; } QString LaunchProfile::getAppletClass() const { - return m_appletClass; + return m_appletClass; } QString LaunchProfile::getMainClass() const { - return m_mainClass; + return m_mainClass; } const QSet &LaunchProfile::getTraits() const { - return m_traits; + return m_traits; } const QStringList & LaunchProfile::getTweakers() const { - return m_tweakers; + return m_tweakers; } bool LaunchProfile::hasTrait(const QString& trait) const { - return m_traits.contains(trait); + return m_traits.contains(trait); } ProblemSeverity LaunchProfile::getProblemSeverity() const { - return m_problemSeverity; + return m_problemSeverity; } QString LaunchProfile::getMinecraftVersionType() const { - return m_minecraftVersionType; + return m_minecraftVersionType; } std::shared_ptr LaunchProfile::getMinecraftAssets() const { - if(!m_minecraftAssets) - { - return std::make_shared("legacy"); - } - return m_minecraftAssets; + if(!m_minecraftAssets) + { + return std::make_shared("legacy"); + } + return m_minecraftAssets; } QString LaunchProfile::getMinecraftArguments() const { - return m_minecraftArguments; + return m_minecraftArguments; } const QList & LaunchProfile::getJarMods() const { - return m_jarMods; + return m_jarMods; } const QList & LaunchProfile::getLibraries() const { - return m_libraries; + return m_libraries; } const QList & LaunchProfile::getNativeLibraries() const { - return m_nativeLibraries; + return m_nativeLibraries; } void LaunchProfile::getLibraryFiles( - const QString& architecture, - QStringList& jars, - QStringList& nativeJars, - const QString& overridePath, - const QString& tempPath + const QString& architecture, + QStringList& jars, + QStringList& nativeJars, + const QString& overridePath, + const QString& tempPath ) const { - QStringList native32, native64; - jars.clear(); - nativeJars.clear(); - for (auto lib : getLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - // NOTE: order is important here, add main jar last to the lists - if(m_mainJar) - { - // FIXME: HACK!! jar modding is weird and unsystematic! - if(m_jarMods.size()) - { - QDir tempDir(tempPath); - jars.append(tempDir.absoluteFilePath("minecraft.jar")); - } - else - { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - } - for (auto lib : getNativeLibraries()) - { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); - } - if(architecture == "32") - { - nativeJars.append(native32); - } - else if(architecture == "64") - { - nativeJars.append(native64); - } + QStringList native32, native64; + jars.clear(); + nativeJars.clear(); + for (auto lib : getLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + // NOTE: order is important here, add main jar last to the lists + if(m_mainJar) + { + // FIXME: HACK!! jar modding is weird and unsystematic! + if(m_jarMods.size()) + { + QDir tempDir(tempPath); + jars.append(tempDir.absoluteFilePath("minecraft.jar")); + } + else + { + m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + } + for (auto lib : getNativeLibraries()) + { + lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + } + if(architecture == "32") + { + nativeJars.append(native32); + } + else if(architecture == "64") + { + nativeJars.append(native64); + } } diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h index e7f5f4af..77174079 100644 --- a/api/logic/minecraft/LaunchProfile.h +++ b/api/logic/minecraft/LaunchProfile.h @@ -6,94 +6,94 @@ class LaunchProfile: public ProblemProvider { public: - virtual ~LaunchProfile() {}; + virtual ~LaunchProfile() {}; public: /* application of profile variables from patches */ - void applyMinecraftVersion(const QString& id); - void applyMainClass(const QString& mainClass); - void applyAppletClass(const QString& appletClass); - void applyMinecraftArguments(const QString& minecraftArguments); - void applyMinecraftVersionType(const QString& type); - void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); - void applyTraits(const QSet &traits); - void applyTweakers(const QStringList &tweakers); - void applyJarMods(const QList &jarMods); - void applyMods(const QList &jarMods); - void applyLibrary(LibraryPtr library); - void applyMainJar(LibraryPtr jar); - void applyProblemSeverity(ProblemSeverity severity); - /// clear the profile - void clear(); + void applyMinecraftVersion(const QString& id); + void applyMainClass(const QString& mainClass); + void applyAppletClass(const QString& appletClass); + void applyMinecraftArguments(const QString& minecraftArguments); + void applyMinecraftVersionType(const QString& type); + void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets); + void applyTraits(const QSet &traits); + void applyTweakers(const QStringList &tweakers); + void applyJarMods(const QList &jarMods); + void applyMods(const QList &jarMods); + void applyLibrary(LibraryPtr library); + void applyMainJar(LibraryPtr jar); + void applyProblemSeverity(ProblemSeverity severity); + /// clear the profile + void clear(); public: /* getters for profile variables */ - QString getMinecraftVersion() const; - QString getMainClass() const; - QString getAppletClass() const; - QString getMinecraftVersionType() const; - MojangAssetIndexInfo::Ptr getMinecraftAssets() const; - QString getMinecraftArguments() const; - const QSet & getTraits() const; - const QStringList & getTweakers() const; - const QList & getJarMods() const; - const QList & getLibraries() const; - const QList & getNativeLibraries() const; - const LibraryPtr getMainJar() const; - void getLibraryFiles( - const QString & architecture, - QStringList & jars, - QStringList & nativeJars, - const QString & overridePath, - const QString & tempPath - ) const; - bool hasTrait(const QString & trait) const; - ProblemSeverity getProblemSeverity() const override; - const QList getProblems() const override; + QString getMinecraftVersion() const; + QString getMainClass() const; + QString getAppletClass() const; + QString getMinecraftVersionType() const; + MojangAssetIndexInfo::Ptr getMinecraftAssets() const; + QString getMinecraftArguments() const; + const QSet & getTraits() const; + const QStringList & getTweakers() const; + const QList & getJarMods() const; + const QList & getLibraries() const; + const QList & getNativeLibraries() const; + const LibraryPtr getMainJar() const; + void getLibraryFiles( + const QString & architecture, + QStringList & jars, + QStringList & nativeJars, + const QString & overridePath, + const QString & tempPath + ) const; + bool hasTrait(const QString & trait) const; + ProblemSeverity getProblemSeverity() const override; + const QList getProblems() const override; private: - /// the version of Minecraft - jar to use - QString m_minecraftVersion; + /// the version of Minecraft - jar to use + QString m_minecraftVersion; - /// Release type - "release" or "snapshot" - QString m_minecraftVersionType; + /// Release type - "release" or "snapshot" + QString m_minecraftVersionType; - /// Assets type - "legacy" or a version ID - MojangAssetIndexInfo::Ptr m_minecraftAssets; + /// Assets type - "legacy" or a version ID + MojangAssetIndexInfo::Ptr m_minecraftAssets; - /** - * 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 m_minecraftArguments; + /** + * 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 m_minecraftArguments; - /// A list of all tweaker classes - QStringList m_tweakers; + /// A list of all tweaker classes + QStringList m_tweakers; - /// The main class to load first - QString m_mainClass; + /// The main class to load first + QString m_mainClass; - /// The applet class, for some very old minecraft releases - QString m_appletClass; + /// The applet class, for some very old minecraft releases + QString m_appletClass; - /// the list of libraries - QList m_libraries; + /// the list of libraries + QList m_libraries; - /// the main jar - LibraryPtr m_mainJar; + /// the main jar + LibraryPtr m_mainJar; - /// the list of libraries - QList m_nativeLibraries; + /// the list of libraries + QList m_nativeLibraries; - /// traits, collected from all the version files (version files can only add) - QSet m_traits; + /// traits, collected from all the version files (version files can only add) + QSet m_traits; - /// A list of jar mods. version files can add those. - QList m_jarMods; + /// A list of jar mods. version files can add those. + QList m_jarMods; - /// the list of mods - QList m_mods; + /// the list of mods + QList m_mods; - ProblemSeverity m_problemSeverity = ProblemSeverity::None; + ProblemSeverity m_problemSeverity = ProblemSeverity::None; }; diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp index cd1afde4..a56e8110 100644 --- a/api/logic/minecraft/Library.cpp +++ b/api/logic/minecraft/Library.cpp @@ -9,316 +9,313 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, - QStringList& native64, const QString &overridePath) const + QStringList& native64, const QString &overridePath) const { - bool local = isLocal(); - auto actualPath = [&](QString relPath) - { - QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); - if(local && !overridePath.isEmpty()) - { - QString fileName = out.fileName(); - auto fullPath = FS::PathCombine(overridePath, fileName); - qDebug() << fullPath; - QFileInfo fileinfo(fullPath); - if(fileinfo.exists()) - { - return fileinfo.absoluteFilePath(); - } - } - return out.absoluteFilePath(); - }; - QString raw_storage = storageSuffix(system); - if(isNative()) - { - if (raw_storage.contains("${arch}")) - { - auto nat32Storage = raw_storage; - nat32Storage.replace("${arch}", "32"); - auto nat64Storage = raw_storage; - nat64Storage.replace("${arch}", "64"); - native32 += actualPath(nat32Storage); - native64 += actualPath(nat64Storage); - } - else - { - native += actualPath(raw_storage); - } - } - else - { - jar += actualPath(raw_storage); - } + bool local = isLocal(); + auto actualPath = [&](QString relPath) + { + QFileInfo out(FS::PathCombine(storagePrefix(), relPath)); + if(local && !overridePath.isEmpty()) + { + QString fileName = out.fileName(); + return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath(); + } + return out.absoluteFilePath(); + }; + QString raw_storage = storageSuffix(system); + if(isNative()) + { + if (raw_storage.contains("${arch}")) + { + auto nat32Storage = raw_storage; + nat32Storage.replace("${arch}", "32"); + auto nat64Storage = raw_storage; + nat64Storage.replace("${arch}", "64"); + native32 += actualPath(nat32Storage); + native64 += actualPath(nat64Storage); + } + else + { + native += actualPath(raw_storage); + } + } + else + { + jar += actualPath(raw_storage); + } } -QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache, - QStringList& failedFiles, const QString & overridePath) const +QList< std::shared_ptr< NetAction > > Library::getDownloads( + OpSys system, + class HttpMetaCache* cache, + QStringList& failedLocalFiles, + const QString & overridePath +) const { - QList out; - bool isAlwaysStale = (hint() == "always-stale"); - bool local = isLocal(); - bool isForge = (hint() == "forge-pack-xz"); + QList out; + bool stale = isAlwaysStale(); + bool local = isLocal(); - auto add_download = [&](QString storage, QString url, QString sha1 = QString()) - { - auto entry = cache->resolveEntry("libraries", storage); - if(isAlwaysStale) - { - entry->setStale(true); - } - if (!entry->isStale()) - return true; - if(local) - { - if(!overridePath.isEmpty()) - { - QString fileName; - int position = storage.lastIndexOf('/'); - if(position == -1) - { - fileName = storage; - } - else - { - fileName = storage.mid(position); - } - auto fullPath = FS::PathCombine(overridePath, fileName); - QFileInfo fileinfo(fullPath); - if(fileinfo.exists()) - { - return true; - } - } - QFileInfo fileinfo(entry->getFullPath()); - if(!fileinfo.exists()) - { - failedFiles.append(entry->getFullPath()); - return false; - } - return true; - } - Net::Download::Options options; - if(isAlwaysStale) - { - options |= Net::Download::Option::AcceptLocalFiles; - } - if (isForge) - { - qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; - out.append(ForgeXzDownload::make(storage, entry)); - } - else - { - if(sha1.size()) - { - auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); - auto dl = Net::Download::makeCached(url, entry, options); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; - out.append(dl); - } - else - { - out.append(Net::Download::makeCached(url, entry, options)); - qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; - } - } - return true; - }; + auto check_local_file = [&](QString storage) + { + QFileInfo fileinfo(storage); + QString fileName = fileinfo.fileName(); + auto fullPath = FS::PathCombine(overridePath, fileName); + QFileInfo localFileInfo(fullPath); + if(!localFileInfo.exists()) + { + failedLocalFiles.append(localFileInfo.filePath()); + return false; + } + return true; + }; - QString raw_storage = storageSuffix(system); - if(m_mojangDownloads) - { - if(isNative()) - { - if(m_nativeClassifiers.contains(system)) - { - auto nativeClassifier = m_nativeClassifiers[system]; - if(nativeClassifier.contains("${arch}")) - { - auto nat32Classifier = nativeClassifier; - nat32Classifier.replace("${arch}", "32"); - auto nat64Classifier = nativeClassifier; - nat64Classifier.replace("${arch}", "64"); - auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); - if(nat32info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "32"); - add_download(cooked_storage, nat32info->url, nat32info->sha1); - } - auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); - if(nat64info) - { - auto cooked_storage = raw_storage; - cooked_storage.replace("${arch}", "64"); - add_download(cooked_storage, nat64info->url, nat64info->sha1); - } - } - else - { - auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); - if(info) - { - add_download(raw_storage, info->url, info->sha1); - } - } - } - else - { - qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; - } - } - else - { - if(m_mojangDownloads->artifact) - { - auto artifact = m_mojangDownloads->artifact; - add_download(raw_storage, artifact->url, artifact->sha1); - } - else - { - qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; - } - } - } - else - { - auto raw_dl = [&](){ - if (!m_absoluteURL.isEmpty()) - { - return m_absoluteURL; - } + auto add_download = [&](QString storage, QString url, QString sha1) + { + if(local) + { + return check_local_file(storage); + } + auto entry = cache->resolveEntry("libraries", storage); + if(stale) + { + entry->setStale(true); + } + if (!entry->isStale()) + return true; + Net::Download::Options options; + if(stale) + { + options |= Net::Download::Option::AcceptLocalFiles; + } + if (isForge()) + { + qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url; + out.append(ForgeXzDownload::make(url, storage, entry)); + } + else + { + if(sha1.size()) + { + auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); + auto dl = Net::Download::makeCached(url, entry, options); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url; + out.append(dl); + } + else + { + out.append(Net::Download::makeCached(url, entry, options)); + qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url; + } + } + return true; + }; - if (m_repositoryURL.isEmpty()) - { - return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage; - } + QString raw_storage = storageSuffix(system); + if(m_mojangDownloads) + { + if(isNative()) + { + if(m_nativeClassifiers.contains(system)) + { + auto nativeClassifier = m_nativeClassifiers[system]; + if(nativeClassifier.contains("${arch}")) + { + auto nat32Classifier = nativeClassifier; + nat32Classifier.replace("${arch}", "32"); + auto nat64Classifier = nativeClassifier; + nat64Classifier.replace("${arch}", "64"); + auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier); + if(nat32info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "32"); + add_download(cooked_storage, nat32info->url, nat32info->sha1); + } + auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier); + if(nat64info) + { + auto cooked_storage = raw_storage; + cooked_storage.replace("${arch}", "64"); + add_download(cooked_storage, nat64info->url, nat64info->sha1); + } + } + else + { + auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier); + if(info) + { + add_download(raw_storage, info->url, info->sha1); + } + } + } + else + { + qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS"; + } + } + else + { + if(m_mojangDownloads->artifact) + { + auto artifact = m_mojangDownloads->artifact; + add_download(raw_storage, artifact->url, artifact->sha1); + } + else + { + qDebug() << "Ignoring java library" << m_name << "because it has no artifact"; + } + } + } + else + { + auto raw_dl = [&]() + { + if (!m_absoluteURL.isEmpty()) + { + return m_absoluteURL; + } - if(m_repositoryURL.endsWith('/')) - { - return m_repositoryURL + raw_storage; - } - else - { - return m_repositoryURL + QChar('/') + raw_storage; - } - }(); - if (raw_storage.contains("${arch}")) - { - QString cooked_storage = raw_storage; - QString cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32")); - cooked_storage = raw_storage; - cooked_dl = raw_dl; - add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64")); - } - else - { - add_download(raw_storage, raw_dl); - } - } - return out; + if (m_repositoryURL.isEmpty()) + { + return URLConstants::LIBRARY_BASE + raw_storage; + } + + if(m_repositoryURL.endsWith('/')) + { + return m_repositoryURL + raw_storage; + } + else + { + return m_repositoryURL + QChar('/') + raw_storage; + } + }(); + if (raw_storage.contains("${arch}")) + { + QString cooked_storage = raw_storage; + QString cooked_dl = raw_dl; + add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString()); + cooked_storage = raw_storage; + cooked_dl = raw_dl; + add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString()); + } + else + { + add_download(raw_storage, raw_dl, QString()); + } + } + return out; } bool Library::isActive() const { - bool result = true; - if (m_rules.empty()) - { - result = true; - } - else - { - RuleAction ruleResult = Disallow; - for (auto rule : m_rules) - { - RuleAction temp = rule->apply(this); - if (temp != Defer) - ruleResult = temp; - } - result = result && (ruleResult == Allow); - } - if (isNative()) - { - result = result && m_nativeClassifiers.contains(currentSystem); - } - return result; + bool result = true; + if (m_rules.empty()) + { + result = true; + } + else + { + RuleAction ruleResult = Disallow; + for (auto rule : m_rules) + { + RuleAction temp = rule->apply(this); + if (temp != Defer) + ruleResult = temp; + } + result = result && (ruleResult == Allow); + } + if (isNative()) + { + result = result && m_nativeClassifiers.contains(currentSystem); + } + return result; } bool Library::isLocal() const { - return m_hint == "local"; + return m_hint == "local"; +} + +bool Library::isAlwaysStale() const +{ + return m_hint == "always-stale"; +} + +bool Library::isForge() const +{ + return m_hint == "forge-pack-xz"; } void Library::setStoragePrefix(QString prefix) { - m_storagePrefix = prefix; + m_storagePrefix = prefix; } QString Library::defaultStoragePrefix() { - return "libraries/"; + return "libraries/"; } QString Library::storagePrefix() const { - if(m_storagePrefix.isEmpty()) - { - return defaultStoragePrefix(); - } - return m_storagePrefix; + if(m_storagePrefix.isEmpty()) + { + return defaultStoragePrefix(); + } + return m_storagePrefix; } QString Library::filename(OpSys system) const { - if(!m_filename.isEmpty()) - { - return m_filename; - } - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.getFileName(); - } + if(!m_filename.isEmpty()) + { + return m_filename; + } + // non-native? use only the gradle specifier + if (!isNative()) + { + return m_name.getFileName(); + } - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.getFileName(); + // otherwise native, override classifiers. Mojang HACK! + GradleSpecifier nativeSpec = m_name; + if (m_nativeClassifiers.contains(system)) + { + nativeSpec.setClassifier(m_nativeClassifiers[system]); + } + else + { + nativeSpec.setClassifier("INVALID"); + } + return nativeSpec.getFileName(); } QString Library::displayName(OpSys system) const { - if(!m_displayname.isEmpty()) - return m_displayname; - return filename(system); + if(!m_displayname.isEmpty()) + return m_displayname; + return filename(system); } QString Library::storageSuffix(OpSys system) const { - // non-native? use only the gradle specifier - if (!isNative()) - { - return m_name.toPath(m_filename); - } + // non-native? use only the gradle specifier + if (!isNative()) + { + return m_name.toPath(m_filename); + } - // otherwise native, override classifiers. Mojang HACK! - GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) - { - nativeSpec.setClassifier(m_nativeClassifiers[system]); - } - else - { - nativeSpec.setClassifier("INVALID"); - } - return nativeSpec.toPath(m_filename); + // otherwise native, override classifiers. Mojang HACK! + GradleSpecifier nativeSpec = m_name; + if (m_nativeClassifiers.contains(system)) + { + nativeSpec.setClassifier(m_nativeClassifiers[system]); + } + else + { + nativeSpec.setClassifier("INVALID"); + } + return nativeSpec.toPath(m_filename); } diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h index 9577de33..014f6745 100644 --- a/api/logic/minecraft/Library.h +++ b/api/logic/minecraft/Library.h @@ -24,191 +24,197 @@ typedef std::shared_ptr LibraryPtr; class MULTIMC_LOGIC_EXPORT Library { - friend class OneSixVersionFormat; - friend class MojangVersionFormat; - friend class LibraryTest; + friend class OneSixVersionFormat; + friend class MojangVersionFormat; + friend class LibraryTest; public: - Library() - { - } - Library(const QString &name) - { - m_name = name; - } - /// limited copy without some data. TODO: why? - static LibraryPtr limitedCopy(LibraryPtr base) - { - auto newlib = std::make_shared(); - newlib->m_name = base->m_name; - newlib->m_repositoryURL = base->m_repositoryURL; - newlib->m_hint = base->m_hint; - newlib->m_absoluteURL = base->m_absoluteURL; - newlib->m_extractExcludes = base->m_extractExcludes; - newlib->m_nativeClassifiers = base->m_nativeClassifiers; - newlib->m_rules = base->m_rules; - newlib->m_storagePrefix = base->m_storagePrefix; - newlib->m_mojangDownloads = base->m_mojangDownloads; - newlib->m_filename = base->m_filename; - return newlib; - } + Library() + { + } + Library(const QString &name) + { + m_name = name; + } + /// limited copy without some data. TODO: why? + static LibraryPtr limitedCopy(LibraryPtr base) + { + auto newlib = std::make_shared(); + newlib->m_name = base->m_name; + newlib->m_repositoryURL = base->m_repositoryURL; + newlib->m_hint = base->m_hint; + newlib->m_absoluteURL = base->m_absoluteURL; + newlib->m_extractExcludes = base->m_extractExcludes; + newlib->m_nativeClassifiers = base->m_nativeClassifiers; + newlib->m_rules = base->m_rules; + newlib->m_storagePrefix = base->m_storagePrefix; + newlib->m_mojangDownloads = base->m_mojangDownloads; + newlib->m_filename = base->m_filename; + return newlib; + } public: /* methods */ - /// Returns the raw name field - const GradleSpecifier & rawName() const - { - return m_name; - } - - void setRawName(const GradleSpecifier & spec) - { - m_name = spec; - } - - void setClassifier(const QString & spec) - { - m_name.setClassifier(spec); - } - - /// returns the full group and artifact prefix - QString artifactPrefix() const - { - return m_name.artifactPrefix(); - } - - /// get the artifact ID - QString artifactId() const - { - return m_name.artifactId(); - } - - /// get the artifact version - QString version() const - { - return m_name.version(); - } - - /// Returns true if the library is native - bool isNative() const - { - return m_nativeClassifiers.size() != 0; - } - - void setStoragePrefix(QString prefix = QString()); - - /// Set the url base for downloads - void setRepositoryURL(const QString &base_url) - { - m_repositoryURL = base_url; - } - - void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, - QStringList & native32, QStringList & native64, const QString & overridePath) const; - - void setAbsoluteUrl(const QString &absolute_url) - { - m_absoluteURL = absolute_url; - } - - void setFilename(const QString &filename) - { - m_filename = filename; - } - - /// Get the file name of the library - QString filename(OpSys system) const; - - // DEPRECATED: set a display name, used by jar mods only - void setDisplayName(const QString & displayName) - { - m_displayname = displayName; - } - - /// Get the file name of the library - QString displayName(OpSys system) const; - - void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) - { - m_mojangDownloads = info; - } - - void setHint(const QString &hint) - { - m_hint = hint; - } - - /// Set the load rules - void setRules(QList> rules) - { - m_rules = rules; - } - - /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; - - /// Returns true if the library is contained in an instance and false if it is shared - bool isLocal() const; - - // Get a list of downloads for this library - QList getDownloads(OpSys system, class HttpMetaCache * cache, - QStringList & failedFiles, const QString & overridePath) const; + /// Returns the raw name field + const GradleSpecifier & rawName() const + { + return m_name; + } + + void setRawName(const GradleSpecifier & spec) + { + m_name = spec; + } + + void setClassifier(const QString & spec) + { + m_name.setClassifier(spec); + } + + /// returns the full group and artifact prefix + QString artifactPrefix() const + { + return m_name.artifactPrefix(); + } + + /// get the artifact ID + QString artifactId() const + { + return m_name.artifactId(); + } + + /// get the artifact version + QString version() const + { + return m_name.version(); + } + + /// Returns true if the library is native + bool isNative() const + { + return m_nativeClassifiers.size() != 0; + } + + void setStoragePrefix(QString prefix = QString()); + + /// Set the url base for downloads + void setRepositoryURL(const QString &base_url) + { + m_repositoryURL = base_url; + } + + void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, + QStringList & native32, QStringList & native64, const QString & overridePath) const; + + void setAbsoluteUrl(const QString &absolute_url) + { + m_absoluteURL = absolute_url; + } + + void setFilename(const QString &filename) + { + m_filename = filename; + } + + /// Get the file name of the library + QString filename(OpSys system) const; + + // DEPRECATED: set a display name, used by jar mods only + void setDisplayName(const QString & displayName) + { + m_displayname = displayName; + } + + /// Get the file name of the library + QString displayName(OpSys system) const; + + void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) + { + m_mojangDownloads = info; + } + + void setHint(const QString &hint) + { + m_hint = hint; + } + + /// Set the load rules + void setRules(QList> rules) + { + m_rules = rules; + } + + /// Returns true if the library should be loaded (or extracted, in case of natives) + bool isActive() const; + + /// Returns true if the library is contained in an instance and false if it is shared + bool isLocal() const; + + /// Returns true if the library is to always be checked for updates + bool isAlwaysStale() const; + + /// Return true if the library requires forge XZ hacks + bool isForge() const; + + // Get a list of downloads for this library + QList getDownloads(OpSys system, class HttpMetaCache * cache, + QStringList & failedLocalFiles, const QString & overridePath) const; private: /* methods */ - /// the default storage prefix used by MultiMC - static QString defaultStoragePrefix(); + /// the default storage prefix used by MultiMC + static QString defaultStoragePrefix(); - /// Get the prefix - root of the storage to be used - QString storagePrefix() const; + /// Get the prefix - root of the storage to be used + QString storagePrefix() const; - /// Get the relative file path where the library should be saved - QString storageSuffix(OpSys system) const; + /// Get the relative file path where the library should be saved + QString storageSuffix(OpSys system) const; - QString hint() const - { - return m_hint; - } + QString hint() const + { + return m_hint; + } protected: /* data */ - /// the basic gradle dependency specifier. - GradleSpecifier m_name; + /// the basic gradle dependency specifier. + GradleSpecifier m_name; - /// DEPRECATED URL prefix of the maven repo where the file can be downloaded - QString m_repositoryURL; + /// DEPRECATED URL prefix of the maven repo where the file can be downloaded + QString m_repositoryURL; - /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined - QString m_absoluteURL; + /// DEPRECATED: MultiMC-specific absolute URL. takes precedence over the implicit maven repo URL, if defined + QString m_absoluteURL; - /// MultiMC extension - filename override - QString m_filename; + /// MultiMC extension - filename override + QString m_filename; - /// DEPRECATED MultiMC extension - display name - QString m_displayname; + /// DEPRECATED MultiMC extension - display name + QString m_displayname; - /** - * MultiMC-specific type hint - modifies how the library is treated - */ - QString m_hint; + /** + * MultiMC-specific type hint - modifies how the library is treated + */ + QString m_hint; - /** - * storage - by default the local libraries folder in multimc, but could be elsewhere - * MultiMC specific, because of FTB. - */ - QString m_storagePrefix; + /** + * storage - by default the local libraries folder in multimc, but could be elsewhere + * MultiMC specific, because of FTB. + */ + QString m_storagePrefix; - /// true if the library had an extract/excludes section (even empty) - bool m_hasExcludes = false; + /// true if the library had an extract/excludes section (even empty) + bool m_hasExcludes = false; - /// a list of files that shouldn't be extracted from the library - QStringList m_extractExcludes; + /// a list of files that shouldn't be extracted from the library + QStringList m_extractExcludes; - /// native suffixes per OS - QMap m_nativeClassifiers; + /// native suffixes per OS + QMap m_nativeClassifiers; - /// true if the library had a rules section (even empty) - bool applyRules = false; + /// true if the library had a rules section (even empty) + bool applyRules = false; - /// rules associated with the library - QList> m_rules; + /// rules associated with the library + QList> m_rules; - /// MOJANG: container with Mojang style download info - MojangLibraryDownloadInfo::Ptr m_mojangDownloads; + /// MOJANG: container with Mojang style download info + MojangLibraryDownloadInfo::Ptr m_mojangDownloads; }; diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp index 1aac951b..c3d6150d 100644 --- a/api/logic/minecraft/Library_test.cpp +++ b/api/logic/minecraft/Library_test.cpp @@ -9,261 +9,261 @@ class LibraryTest : public QObject { - Q_OBJECT + Q_OBJECT private: - LibraryPtr readMojangJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file); - } - // get absolute path to expected storage, assuming default cache prefix - QStringList getStorage(QString relative) - { - return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; - } + LibraryPtr readMojangJson(const char *file) + { + auto path = QFINDTESTDATA(file); + QFile jsonFile(path); + jsonFile.open(QIODevice::ReadOnly); + auto data = jsonFile.readAll(); + jsonFile.close(); + return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file); + } + // get absolute path to expected storage, assuming default cache prefix + QStringList getStorage(QString relative) + { + return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; + } private slots: - void initTestCase() - { - cache.reset(new HttpMetaCache()); - cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir("data").absolutePath(); - } - void test_legacy() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); - QCOMPARE(test.isNative(), false); + void initTestCase() + { + cache.reset(new HttpMetaCache()); + cache->addBase("libraries", QDir("libraries").absolutePath()); + dataDir = QDir("data").absolutePath(); + } + void test_legacy() + { + Library test("test.package:testname:testversion"); + QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); + QCOMPARE(test.isNative(), false); - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_url() - { - QStringList failedFiles; - Library test("test.package:testname:testversion"); - test.setRepositoryURL("file://foo/bar"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 1); - QCOMPARE(failedFiles, {}); - NetActionPtr dl = downloads[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_broken() - { - Library test("test.package:testname:testversion"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); - QCOMPARE(downloads.size(), 0); - QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar")); - } - void test_legacy_url_local_override() - { - Library test("com.paulscode:codecwav:20101023"); - QCOMPARE(test.isNative(), false); - QStringList failedFiles; - test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); - QCOMPARE(downloads.size(), 0); - qDebug() << failedFiles; - QCOMPARE(failedFiles.size(), 0); + QStringList jar, native, native32, native64; + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); + QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + void test_legacy_url() + { + QStringList failedFiles; + Library test("test.package:testname:testversion"); + test.setRepositoryURL("file://foo/bar"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + QCOMPARE(downloads.size(), 1); + QCOMPARE(failedFiles, {}); + NetActionPtr dl = downloads[0]; + QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion.jar")); + } + void test_legacy_url_local_broken() + { + Library test("test.package:testname:testversion"); + QCOMPARE(test.isNative(), false); + QStringList failedFiles; + test.setHint("local"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + QCOMPARE(downloads.size(), 0); + QCOMPARE(failedFiles, {"testname-testversion.jar"}); + } + void test_legacy_url_local_override() + { + Library test("com.paulscode:codecwav:20101023"); + QCOMPARE(test.isNative(), false); + QStringList failedFiles; + test.setHint("local"); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data")); + QCOMPARE(downloads.size(), 0); + qDebug() << failedFiles; + QCOMPARE(failedFiles.size(), 0); - QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - void test_legacy_native() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - auto dl = dls[0]; - QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); - } - } - void test_legacy_native_arch() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; - test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); - } - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); - } - } - void test_legacy_native_arch_local_override() - { - Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.setHint("local"); - QCOMPARE(test.isNative(), true); - test.setRepositoryURL("file://foo/bar"); - { - QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); - QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); - QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")}); - } - } - void test_onenine() - { - auto test = readMojangJson("data/lib-simple.json"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); - } - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_local_override() - { - auto test = readMojangJson("data/lib-simple.json"); - test->setHint("local"); - { - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); - QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); - QCOMPARE(native, {}); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - } - { - QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); - QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {}); - } - } - void test_onenine_native() - { - auto test = readMojangJson("data/lib-native.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); - QCOMPARE(jar, QStringList()); - QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - QCOMPARE(native32, {}); - QCOMPARE(native64, {}); - QStringList failedFiles; - auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 1); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); - } - void test_onenine_native_arch() - { - auto test = readMojangJson("data/lib-native-arch.json"); - QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); - QCOMPARE(jar, {}); - QCOMPARE(native, {}); - QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - QStringList failedFiles; - auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); - QCOMPARE(dls.size(), 2); - QCOMPARE(failedFiles, {}); - QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); - QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); - } + QStringList jar, native, native32, native64; + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + void test_legacy_native() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + auto dl = dls[0]; + QCOMPARE(dl->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux.jar")); + } + } + void test_legacy_native_arch() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; + test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; + test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); + } + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); + } + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); + QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); + QStringList failedFiles; + auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-64.jar")); + } + } + void test_legacy_native_arch_local_override() + { + Library test("test.package:testname:testversion"); + test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; + test.setHint("local"); + QCOMPARE(test.isNative(), true); + test.setRepositoryURL("file://foo/bar"); + { + QStringList jar, native, native32, native64; + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()}); + QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()}); + QStringList failedFiles; + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"}); + } + } + void test_onenine() + { + auto test = readMojangJson("data/lib-simple.json"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); + } + test->setHint("local"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {}); + } + } + void test_onenine_local_override() + { + auto test = readMojangJson("data/lib-simple.json"); + test->setHint("local"); + { + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString("data")); + QCOMPARE(jar, {QFileInfo("data/codecwav-20101023.jar").absoluteFilePath()}); + QCOMPARE(native, {}); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + } + { + QStringList failedFiles; + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString("data")); + QCOMPARE(dls.size(), 0); + QCOMPARE(failedFiles, {}); + } + } + void test_onenine_native() + { + auto test = readMojangJson("data/lib-native.json"); + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + QCOMPARE(jar, QStringList()); + QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + QCOMPARE(native32, {}); + QCOMPARE(native64, {}); + QStringList failedFiles; + auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 1); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); + } + void test_onenine_native_arch() + { + auto test = readMojangJson("data/lib-native-arch.json"); + QStringList jar, native, native32, native64; + test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + QCOMPARE(jar, {}); + QCOMPARE(native, {}); + QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); + QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); + QStringList failedFiles; + auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + QCOMPARE(dls.size(), 2); + QCOMPARE(failedFiles, {}); + QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); + QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); + } private: - std::unique_ptr cache; - QString dataDir; + std::unique_ptr cache; + QString dataDir; }; QTEST_GUILESS_MAIN(LibraryTest) diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp index 0fffc99f..28073edf 100644 --- a/api/logic/minecraft/MinecraftInstance.cpp +++ b/api/logic/minecraft/MinecraftInstance.cpp @@ -20,12 +20,14 @@ #include "minecraft/launch/DirectJavaLaunch.h" #include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ClaimAccount.h" +#include "minecraft/launch/ReconstructAssets.h" +#include "minecraft/launch/ScanModFolders.h" #include "java/launch/CheckJava.h" #include "java/JavaUtils.h" #include "meta/Index.h" #include "meta/VersionList.h" -#include "ModList.h" +#include "mod/ModFolderModel.h" #include "WorldList.h" #include "icons/IIconList.h" @@ -35,6 +37,7 @@ #include "AssetsUtils.h" #include "MinecraftUpdate.h" #include "MinecraftLoadAndCheck.h" +#include #define IBUS "@im=ibus" @@ -42,909 +45,934 @@ // if either of the settings {a, b} is true, this also resolves to true class OrSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - OrSetting(QString id, std::shared_ptr a, std::shared_ptr b) - :Setting({id}, false), m_a(a), m_b(b) - { - } - virtual QVariant get() const - { - bool a = m_a->get().toBool(); - bool b = m_b->get().toBool(); - return a || b; - } - virtual void reset() {} - virtual void set(QVariant value) {} + OrSetting(QString id, std::shared_ptr a, std::shared_ptr b) + :Setting({id}, false), m_a(a), m_b(b) + { + } + virtual QVariant get() const + { + bool a = m_a->get().toBool(); + bool b = m_b->get().toBool(); + return a || b; + } + virtual void reset() {} + virtual void set(QVariant value) {} private: - std::shared_ptr m_a; - std::shared_ptr m_b; + std::shared_ptr m_a; + std::shared_ptr m_b; }; MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) -{ - // Java Settings - auto javaOverride = m_settings->registerSetting("OverrideJava", false); - auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); - auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); - - // combinations - auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); - auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); - - m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); - m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); - - // special! - m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); - - // Window Size - auto windowSetting = m_settings->registerSetting("OverrideWindow", false); - m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); - - // Memory - auto memorySetting = m_settings->registerSetting("OverrideMemory", false); - m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); - - // Minecraft launch method - auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); - m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); - - // DEPRECATED: Read what versions the user configuration thinks should be used - m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); - m_settings->registerSetting("LWJGLVersion", ""); - m_settings->registerSetting("ForgeVersion", ""); - m_settings->registerSetting("LiteloaderVersion", ""); - - m_components.reset(new ComponentList(this)); - m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); - auto setting = m_settings->getSetting("LWJGLVersion"); - m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); - m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); - m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); -} - -void MinecraftInstance::init() -{ + : BaseInstance(globalSettings, settings, rootDir) +{ + // Java Settings + auto javaOverride = m_settings->registerSetting("OverrideJava", false); + auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); + auto argsOverride = m_settings->registerSetting("OverrideJavaArgs", false); + + // combinations + auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); + auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); + + m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); + m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); + + // special! + m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); + + // Window Size + auto windowSetting = m_settings->registerSetting("OverrideWindow", false); + m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); + + // Memory + auto memorySetting = m_settings->registerSetting("OverrideMemory", false); + m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); + + // Minecraft launch method + auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); + m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + + // DEPRECATED: Read what versions the user configuration thinks should be used + m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, ""); + m_settings->registerSetting("LWJGLVersion", ""); + m_settings->registerSetting("ForgeVersion", ""); + m_settings->registerSetting("LiteloaderVersion", ""); + + m_components.reset(new ComponentList(this)); + m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString()); + auto setting = m_settings->getSetting("LWJGLVersion"); + m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString()); + m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString()); + m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString()); } void MinecraftInstance::saveNow() { - m_components->saveNow(); + m_components->saveNow(); } QString MinecraftInstance::typeName() const { - return "Minecraft"; + return "Minecraft"; } std::shared_ptr MinecraftInstance::getComponentList() const { - return m_components; + return m_components; } QSet MinecraftInstance::traits() const { - auto components = getComponentList(); - if (!components) - { - return {"version-incomplete"}; - } - auto profile = components->getProfile(); - if (!profile) - { - return {"version-incomplete"}; - } - return profile->getTraits(); + auto components = getComponentList(); + if (!components) + { + return {"version-incomplete"}; + } + auto profile = components->getProfile(); + if (!profile) + { + return {"version-incomplete"}; + } + return profile->getTraits(); } -QString MinecraftInstance::minecraftRoot() const +QString MinecraftInstance::gameRoot() const { - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString MinecraftInstance::binRoot() const { - return FS::PathCombine(minecraftRoot(), "bin"); + return FS::PathCombine(gameRoot(), "bin"); } QString MinecraftInstance::getNativePath() const { - QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); - return natives_dir.absolutePath(); + QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/")); + return natives_dir.absolutePath(); } QString MinecraftInstance::getLocalLibraryPath() const { - QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); - return libraries_dir.absolutePath(); + QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/")); + return libraries_dir.absolutePath(); +} + +QString MinecraftInstance::jarModsDir() const +{ + QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); + return jarmods_dir.absolutePath(); } QString MinecraftInstance::loaderModsDir() const { - return FS::PathCombine(minecraftRoot(), "mods"); + return FS::PathCombine(gameRoot(), "mods"); +} + +QString MinecraftInstance::modsCacheLocation() const +{ + return FS::PathCombine(instanceRoot(), "mods.cache"); } QString MinecraftInstance::coreModsDir() const { - return FS::PathCombine(minecraftRoot(), "coremods"); + return FS::PathCombine(gameRoot(), "coremods"); } QString MinecraftInstance::resourcePacksDir() const { - return FS::PathCombine(minecraftRoot(), "resourcepacks"); + return FS::PathCombine(gameRoot(), "resourcepacks"); } QString MinecraftInstance::texturePacksDir() const { - return FS::PathCombine(minecraftRoot(), "texturepacks"); + return FS::PathCombine(gameRoot(), "texturepacks"); } QString MinecraftInstance::instanceConfigFolder() const { - return FS::PathCombine(minecraftRoot(), "config"); + return FS::PathCombine(gameRoot(), "config"); } -QString MinecraftInstance::jarModsDir() const +QString MinecraftInstance::libDir() const { - return FS::PathCombine(instanceRoot(), "jarmods"); + return FS::PathCombine(gameRoot(), "lib"); } -QString MinecraftInstance::libDir() const +QString MinecraftInstance::worldDir() const { - return FS::PathCombine(minecraftRoot(), "lib"); + return FS::PathCombine(gameRoot(), "saves"); } -QString MinecraftInstance::worldDir() const +QString MinecraftInstance::resourcesDir() const { - return FS::PathCombine(minecraftRoot(), "saves"); + return FS::PathCombine(gameRoot(), "resources"); } QDir MinecraftInstance::librariesPath() const { - return QDir::current().absoluteFilePath("libraries"); + return QDir::current().absoluteFilePath("libraries"); } QDir MinecraftInstance::jarmodsPath() const { - return QDir(jarModsDir()); + return QDir(jarModsDir()); } QDir MinecraftInstance::versionsPath() const { - return QDir::current().absoluteFilePath("versions"); + return QDir::current().absoluteFilePath("versions"); } QStringList MinecraftInstance::getClassPath() const { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return jars; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return jars; } QString MinecraftInstance::getMainClass() const { - auto profile = m_components->getProfile(); - return profile->getMainClass(); + auto profile = m_components->getProfile(); + return profile->getMainClass(); } QStringList MinecraftInstance::getNativeJars() const { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - return nativeJars; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + auto profile = m_components->getProfile(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + return nativeJars; } QStringList MinecraftInstance::extraArguments() const { - auto list = BaseInstance::extraArguments(); - auto version = getComponentList(); - if (!version) - return list; - auto jarMods = getJarMods(); - if (!jarMods.isEmpty()) - { - list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", - "-Dfml.ignorePatchDiscrepancies=true"}); - } - return list; + auto list = BaseInstance::extraArguments(); + auto version = getComponentList(); + if (!version) + return list; + auto jarMods = getJarMods(); + if (!jarMods.isEmpty()) + { + list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true", + "-Dfml.ignorePatchDiscrepancies=true"}); + } + return list; } QStringList MinecraftInstance::javaArguments() const { - QStringList args; + QStringList args; - // custom args go first. we want to override them if we have our own here. - args.append(extraArguments()); + // custom args go first. we want to override them if we have our own here. + args.append(extraArguments()); - // OSX dock icon and name + // OSX dock icon and name #ifdef Q_OS_MAC - args << "-Xdock:icon=icon.png"; - args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); + args << "-Xdock:icon=icon.png"; + args << QString("-Xdock:name=\"%1\"").arg(windowTitle()); #endif - auto traits_ = traits(); - // HACK: fix issues on macOS with 1.13 snapshots - // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them + auto traits_ = traits(); + // HACK: fix issues on macOS with 1.13 snapshots + // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them #ifdef Q_OS_MAC - if(traits_.contains("FirstThreadOnMacOS")) - { - args << QString("-XstartOnFirstThread"); - } + if(traits_.contains("FirstThreadOnMacOS")) + { + args << QString("-XstartOnFirstThread"); + } #endif - // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 + // HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767 #ifdef Q_OS_WIN32 - args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" - "minecraft.exe.heapdump"); + args << QString("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_" + "minecraft.exe.heapdump"); #endif - int min = settings()->get("MinMemAlloc").toInt(); - int max = settings()->get("MaxMemAlloc").toInt(); - if(min < max) - { - args << QString("-Xms%1m").arg(min); - args << QString("-Xmx%1m").arg(max); - } - else - { - args << QString("-Xms%1m").arg(max); - args << QString("-Xmx%1m").arg(min); - } - - // No PermGen in newer java. - JavaVersion javaVersion = getJavaVersion(); - if(javaVersion.requiresPermGen()) - { - auto permgen = settings()->get("PermGen").toInt(); - if (permgen != 64) - { - args << QString("-XX:PermSize=%1m").arg(permgen); - } - } - - args << "-Duser.language=en"; - - return args; + int min = settings()->get("MinMemAlloc").toInt(); + int max = settings()->get("MaxMemAlloc").toInt(); + if(min < max) + { + args << QString("-Xms%1m").arg(min); + args << QString("-Xmx%1m").arg(max); + } + else + { + args << QString("-Xms%1m").arg(max); + args << QString("-Xmx%1m").arg(min); + } + + // No PermGen in newer java. + JavaVersion javaVersion = getJavaVersion(); + if(javaVersion.requiresPermGen()) + { + auto permgen = settings()->get("PermGen").toInt(); + if (permgen != 64) + { + args << QString("-XX:PermSize=%1m").arg(permgen); + } + } + + args << "-Duser.language=en"; + + return args; } QMap MinecraftInstance::getVariables() const { - QMap out; - out.insert("INST_NAME", name()); - out.insert("INST_ID", id()); - out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); - out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath()); - out.insert("INST_JAVA", settings()->get("JavaPath").toString()); - out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); - return out; + QMap out; + out.insert("INST_NAME", name()); + out.insert("INST_ID", id()); + out.insert("INST_DIR", QDir(instanceRoot()).absolutePath()); + out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath()); + out.insert("INST_JAVA", settings()->get("JavaPath").toString()); + out.insert("INST_JAVA_ARGS", javaArguments().join(' ')); + return out; } QProcessEnvironment MinecraftInstance::createEnvironment() { - // prepare the process environment - QProcessEnvironment env = CleanEnviroment(); + // prepare the process environment + QProcessEnvironment env = CleanEnviroment(); - // export some infos - auto variables = getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) - { - env.insert(it.key(), it.value()); - } - return env; + // export some infos + auto variables = getVariables(); + for (auto it = variables.begin(); it != variables.end(); ++it) + { + env.insert(it.key(), it.value()); + } + return env; } static QString replaceTokensIn(QString text, QMap with) { - QString result; - QRegExp token_regexp("\\$\\{(.+)\\}"); - token_regexp.setMinimal(true); - QStringList list; - int tail = 0; - int head = 0; - while ((head = token_regexp.indexIn(text, head)) != -1) - { - result.append(text.mid(tail, head - tail)); - QString key = token_regexp.cap(1); - auto iter = with.find(key); - if (iter != with.end()) - { - result.append(*iter); - } - head += token_regexp.matchedLength(); - tail = head; - } - result.append(text.mid(tail)); - return result; + QString result; + QRegExp token_regexp("\\$\\{(.+)\\}"); + token_regexp.setMinimal(true); + QStringList list; + int tail = 0; + int head = 0; + while ((head = token_regexp.indexIn(text, head)) != -1) + { + result.append(text.mid(tail, head - tail)); + QString key = token_regexp.cap(1); + auto iter = with.find(key); + if (iter != with.end()) + { + result.append(*iter); + } + head += token_regexp.matchedLength(); + tail = head; + } + result.append(text.mid(tail)); + return result; } QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const { - auto profile = m_components->getProfile(); - QString args_pattern = profile->getMinecraftArguments(); - for (auto tweaker : profile->getTweakers()) - { - args_pattern += " --tweakClass " + tweaker; - } - - QMap token_mapping; - // yggdrasil! - if(session) - { - token_mapping["auth_username"] = session->username; - token_mapping["auth_session"] = session->session; - token_mapping["auth_access_token"] = session->access_token; - token_mapping["auth_player_name"] = session->player_name; - token_mapping["auth_uuid"] = session->uuid; - token_mapping["user_properties"] = session->serializeUserProperties(); - token_mapping["user_type"] = session->user_type; - } - - // blatant self-promotion. - token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; - - token_mapping["version_type"] = profile->getMinecraftVersionType(); - - QString absRootDir = QDir(minecraftRoot()).absolutePath(); - token_mapping["game_directory"] = absRootDir; - QString absAssetsDir = QDir("assets/").absolutePath(); - auto assets = profile->getMinecraftAssets(); - // FIXME: this is wrong and should be run as an async task - token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath(); - - // 1.7.3+ assets tokens - token_mapping["assets_root"] = absAssetsDir; - token_mapping["assets_index_name"] = assets->id; - - QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); - for (int i = 0; i < parts.length(); i++) - { - parts[i] = replaceTokensIn(parts[i], token_mapping); - } - return parts; + auto profile = m_components->getProfile(); + QString args_pattern = profile->getMinecraftArguments(); + for (auto tweaker : profile->getTweakers()) + { + args_pattern += " --tweakClass " + tweaker; + } + + QMap token_mapping; + // yggdrasil! + if(session) + { + token_mapping["auth_username"] = session->username; + token_mapping["auth_session"] = session->session; + token_mapping["auth_access_token"] = session->access_token; + token_mapping["auth_player_name"] = session->player_name; + token_mapping["auth_uuid"] = session->uuid; + token_mapping["user_properties"] = session->serializeUserProperties(); + token_mapping["user_type"] = session->user_type; + } + + // blatant self-promotion. + token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5"; + + token_mapping["version_type"] = profile->getMinecraftVersionType(); + + QString absRootDir = QDir(gameRoot()).absolutePath(); + token_mapping["game_directory"] = absRootDir; + QString absAssetsDir = QDir("assets/").absolutePath(); + auto assets = profile->getMinecraftAssets(); + token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath(); + + // 1.7.3+ assets tokens + token_mapping["assets_root"] = absAssetsDir; + token_mapping["assets_index_name"] = assets->id; + + QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts); + for (int i = 0; i < parts.length(); i++) + { + parts[i] = replaceTokensIn(parts[i], token_mapping); + } + return parts; } QString MinecraftInstance::createLaunchScript(AuthSessionPtr session) { - QString launchScript; - - if (!m_components) - return QString(); - auto profile = m_components->getProfile(); - if(!profile) - return QString(); - - auto mainClass = getMainClass(); - if (!mainClass.isEmpty()) - { - launchScript += "mainClass " + mainClass + "\n"; - } - auto appletClass = profile->getAppletClass(); - if (!appletClass.isEmpty()) - { - launchScript += "appletClass " + appletClass + "\n"; - } - - // generic minecraft params - for (auto param : processMinecraftArgs(session)) - { - launchScript += "param " + param + "\n"; - } - - // window size, title and state, legacy - { - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - windowParams = "max"; - else - windowParams = QString("%1x%2") - .arg(settings()->get("MinecraftWinWidth").toInt()) - .arg(settings()->get("MinecraftWinHeight").toInt()); - launchScript += "windowTitle " + windowTitle() + "\n"; - launchScript += "windowParams " + windowParams + "\n"; - } - - // legacy auth - if(session) - { - launchScript += "userName " + session->player_name + "\n"; - launchScript += "sessionId " + session->session + "\n"; - } - - // libraries and class path. - { - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - for(auto file: jars) - { - launchScript += "cp " + file + "\n"; - } - for(auto file: nativeJars) - { - launchScript += "ext " + file + "\n"; - } - launchScript += "natives " + getNativePath() + "\n"; - } - - for (auto trait : profile->getTraits()) - { - launchScript += "traits " + trait + "\n"; - } - launchScript += "launcher onesix\n"; - // qDebug() << "Generated launch script:" << launchScript; - return launchScript; + QString launchScript; + + if (!m_components) + return QString(); + auto profile = m_components->getProfile(); + if(!profile) + return QString(); + + auto mainClass = getMainClass(); + if (!mainClass.isEmpty()) + { + launchScript += "mainClass " + mainClass + "\n"; + } + auto appletClass = profile->getAppletClass(); + if (!appletClass.isEmpty()) + { + launchScript += "appletClass " + appletClass + "\n"; + } + + // generic minecraft params + for (auto param : processMinecraftArgs(session)) + { + launchScript += "param " + param + "\n"; + } + + // window size, title and state, legacy + { + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + windowParams = "max"; + else + windowParams = QString("%1x%2") + .arg(settings()->get("MinecraftWinWidth").toInt()) + .arg(settings()->get("MinecraftWinHeight").toInt()); + launchScript += "windowTitle " + windowTitle() + "\n"; + launchScript += "windowParams " + windowParams + "\n"; + } + + // legacy auth + if(session) + { + launchScript += "userName " + session->player_name + "\n"; + launchScript += "sessionId " + session->session + "\n"; + } + + // libraries and class path. + { + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + for(auto file: jars) + { + launchScript += "cp " + file + "\n"; + } + for(auto file: nativeJars) + { + launchScript += "ext " + file + "\n"; + } + launchScript += "natives " + getNativePath() + "\n"; + } + + for (auto trait : profile->getTraits()) + { + launchScript += "traits " + trait + "\n"; + } + launchScript += "launcher onesix\n"; + // qDebug() << "Generated launch script:" << launchScript; + return launchScript; } QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session) { - QStringList out; - out << "Main Class:" << " " + getMainClass() << ""; - out << "Native path:" << " " + getNativePath() << ""; - - auto profile = m_components->getProfile(); - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << "traits " + trait; - } - out << ""; - } - - // libraries and class path. - { - out << "Libraries:"; - QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); - auto printLibFile = [&](const QString & path) - { - QFileInfo info(path); - if(info.exists()) - { - out << " " + path; - } - else - { - out << " " + path + " (missing)"; - } - }; - for(auto file: jars) - { - printLibFile(file); - } - out << ""; - out << "Native libraries:"; - for(auto file: nativeJars) - { - printLibFile(file); - } - out << ""; - } - - if(loaderModList()->size()) - { - out << "Mods:"; - for(auto & mod: loaderModList()->allMods()) - { - if(!mod.enabled()) - continue; - if(mod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + mod.filename().completeBaseName(); - } - out << ""; - } - - if(coreModList()->size()) - { - out << "Core Mods:"; - for(auto & coremod: coreModList()->allMods()) - { - if(!coremod.enabled()) - continue; - if(coremod.type() == Mod::MOD_FOLDER) - continue; - // TODO: proper implementation would need to descend into folders. - - out << " " + coremod.filename().completeBaseName(); - } - out << ""; - } - - auto & jarMods = profile->getJarMods(); - if(jarMods.size()) - { - out << "Jar Mods:"; - for(auto & jarmod: jarMods) - { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); - if(displayname != realname) - { - out << " " + displayname + " (" + realname + ")"; - } - else - { - out << " " + realname; - } - } - out << ""; - } - - auto params = processMinecraftArgs(nullptr); - out << "Params:"; - out << " " + params.join(' '); - out << ""; - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; + QStringList out; + out << "Main Class:" << " " + getMainClass() << ""; + out << "Native path:" << " " + getNativePath() << ""; + + auto profile = m_components->getProfile(); + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) + { + out << "traits " + trait; + } + out << ""; + } + + // libraries and class path. + { + out << "Libraries:"; + QStringList jars, nativeJars; + auto javaArchitecture = settings()->get("JavaArchitecture").toString(); + profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + auto printLibFile = [&](const QString & path) + { + QFileInfo info(path); + if(info.exists()) + { + out << " " + path; + } + else + { + out << " " + path + " (missing)"; + } + }; + for(auto file: jars) + { + printLibFile(file); + } + out << ""; + out << "Native libraries:"; + for(auto file: nativeJars) + { + printLibFile(file); + } + out << ""; + } + + auto printModList = [&](const QString & label, ModFolderModel & model) { + if(model.size()) + { + out << QString("%1:").arg(label); + auto modList = model.allMods(); + std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) { + auto aName = a.filename().completeBaseName(); + auto bName = b.filename().completeBaseName(); + return aName.localeAwareCompare(bName) < 0; + }); + for(auto & mod: modList) + { + if(mod.type() == Mod::MOD_FOLDER) + { + out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)"; + continue; + } + + if(mod.enabled()) { + out << u8" [✔️] " + mod.filename().completeBaseName(); + } + else { + out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)"; + } + + } + out << ""; + } + }; + + printModList("Mods", *(loaderModList().get())); + printModList("Core Mods", *(coreModList().get())); + + auto & jarMods = profile->getJarMods(); + if(jarMods.size()) + { + out << "Jar Mods:"; + for(auto & jarmod: jarMods) + { + auto displayname = jarmod->displayName(currentSystem); + auto realname = jarmod->filename(currentSystem); + if(displayname != realname) + { + out << " " + displayname + " (" + realname + ")"; + } + else + { + out << " " + realname; + } + } + out << ""; + } + + auto params = processMinecraftArgs(nullptr); + out << "Params:"; + out << " " + params.join(' '); + out << ""; + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } QMap MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session) { - if(!session) - { - return QMap(); - } - auto & sessionRef = *session.get(); - QMap filter; - auto addToFilter = [&filter](QString key, QString value) - { - if(key.trimmed().size()) - { - filter[key] = value; - } - }; - if (sessionRef.session != "-") - { - addToFilter(sessionRef.session, tr("")); - } - addToFilter(sessionRef.access_token, tr("")); - addToFilter(sessionRef.client_token, tr("")); - addToFilter(sessionRef.uuid, tr("")); - - auto i = sessionRef.u.properties.begin(); - while (i != sessionRef.u.properties.end()) - { - if(i.key() == "preferredLanguage") - { - ++i; - continue; - } - addToFilter(i.value(), "<" + i.key().toUpper() + ">"); - ++i; - } - return filter; + if(!session) + { + return QMap(); + } + auto & sessionRef = *session.get(); + QMap filter; + auto addToFilter = [&filter](QString key, QString value) + { + if(key.trimmed().size()) + { + filter[key] = value; + } + }; + if (sessionRef.session != "-") + { + addToFilter(sessionRef.session, tr("")); + } + addToFilter(sessionRef.access_token, tr("")); + addToFilter(sessionRef.client_token, tr("")); + addToFilter(sessionRef.uuid, tr("")); + + auto i = sessionRef.u.properties.begin(); + while (i != sessionRef.u.properties.end()) + { + if(i.value().length() <= 3) { + ++i; + continue; + } + addToFilter(i.value(), "<" + i.key().toUpper() + ">"); + ++i; + } + return filter; } MessageLevel::Enum MinecraftInstance::guessLevel(const QString &line, MessageLevel::Enum level) { - QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); - auto match = re.match(line); - if(match.hasMatch()) - { - // New style logs from log4j - QString timestamp = match.captured("timestamp"); - QString levelStr = match.captured("level"); - if(levelStr == "INFO") - level = MessageLevel::Message; - if(levelStr == "WARN") - level = MessageLevel::Warning; - if(levelStr == "ERROR") - level = MessageLevel::Error; - if(levelStr == "FATAL") - level = MessageLevel::Fatal; - if(levelStr == "TRACE" || levelStr == "DEBUG") - level = MessageLevel::Debug; - } - else - { - // Old style forge logs - if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || - line.contains("[FINER]") || line.contains("[FINEST]")) - level = MessageLevel::Message; - if (line.contains("[SEVERE]") || line.contains("[STDERR]")) - level = MessageLevel::Error; - if (line.contains("[WARNING]")) - level = MessageLevel::Warning; - if (line.contains("[DEBUG]")) - level = MessageLevel::Debug; - } - if (line.contains("overwriting existing")) - return MessageLevel::Fatal; - //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * - static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; - if (line.contains("Exception in thread") - || line.contains(QRegularExpression("\\s+at " + javaSymbol)) - || line.contains(QRegularExpression("Caused by: " + javaSymbol)) - || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) - || line.contains(QRegularExpression("... \\d+ more$")) - ) - return MessageLevel::Error; - return level; + QRegularExpression re("\\[(?[0-9:]+)\\] \\[[^/]+/(?[^\\]]+)\\]"); + auto match = re.match(line); + if(match.hasMatch()) + { + // New style logs from log4j + QString timestamp = match.captured("timestamp"); + QString levelStr = match.captured("level"); + if(levelStr == "INFO") + level = MessageLevel::Message; + if(levelStr == "WARN") + level = MessageLevel::Warning; + if(levelStr == "ERROR") + level = MessageLevel::Error; + if(levelStr == "FATAL") + level = MessageLevel::Fatal; + if(levelStr == "TRACE" || levelStr == "DEBUG") + level = MessageLevel::Debug; + } + else + { + // Old style forge logs + if (line.contains("[INFO]") || line.contains("[CONFIG]") || line.contains("[FINE]") || + line.contains("[FINER]") || line.contains("[FINEST]")) + level = MessageLevel::Message; + if (line.contains("[SEVERE]") || line.contains("[STDERR]")) + level = MessageLevel::Error; + if (line.contains("[WARNING]")) + level = MessageLevel::Warning; + if (line.contains("[DEBUG]")) + level = MessageLevel::Debug; + } + if (line.contains("overwriting existing")) + return MessageLevel::Fatal; + //NOTE: this diverges from the real regexp. no unicode, the first section is + instead of * + static const QString javaSymbol = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*"; + if (line.contains("Exception in thread") + || line.contains(QRegularExpression("\\s+at " + javaSymbol)) + || line.contains(QRegularExpression("Caused by: " + javaSymbol)) + || line.contains(QRegularExpression("([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$]?[a-zA-Z\\d_$]*(Exception|Error|Throwable)")) + || line.contains(QRegularExpression("... \\d+ more$")) + ) + return MessageLevel::Error; + return level; } IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher() { - auto combined = std::make_shared(); - combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); - combined->add(std::make_shared("crash-.*\\.txt")); - combined->add(std::make_shared("IDMap dump.*\\.txt$")); - combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); - return combined; + auto combined = std::make_shared(); + combined->add(std::make_shared(".*\\.log(\\.[0-9]*)?(\\.gz)?$")); + combined->add(std::make_shared("crash-.*\\.txt")); + combined->add(std::make_shared("IDMap dump.*\\.txt$")); + combined->add(std::make_shared("ModLoader\\.txt(\\..*)?$")); + return combined; } QString MinecraftInstance::getLogFileRoot() { - return minecraftRoot(); + return gameRoot(); } QString MinecraftInstance::prettifyTimeDuration(int64_t duration) { - int seconds = (int) (duration % 60); - duration /= 60; - int minutes = (int) (duration % 60); - duration /= 60; - int hours = (int) (duration % 24); - int days = (int) (duration / 24); - if((hours == 0)&&(days == 0)) - { - return tr("%1m %2s").arg(minutes).arg(seconds); - } - if (days == 0) - { - return tr("%1h %2m").arg(hours).arg(minutes); - } - return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); + int seconds = (int) (duration % 60); + duration /= 60; + int minutes = (int) (duration % 60); + duration /= 60; + int hours = (int) (duration % 24); + int days = (int) (duration / 24); + if((hours == 0)&&(days == 0)) + { + return tr("%1m %2s").arg(minutes).arg(seconds); + } + if (days == 0) + { + return tr("%1h %2m").arg(hours).arg(minutes); + } + return tr("%1d %2h %3m").arg(days).arg(hours).arg(minutes); } QString MinecraftInstance::getStatusbarDescription() { - QStringList traits; - if (hasVersionBroken()) - { - traits.append(tr("broken")); - } - - QString description; - description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); - if(totalTimePlayed() > 0) - { - description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); - } - if(hasCrashed()) - { - description.append(tr(", has crashed.")); - } - return description; + QStringList traits; + if (hasVersionBroken()) + { + traits.append(tr("broken")); + } + + QString description; + description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName())); + if(totalTimePlayed() > 0) + { + description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed()))); + } + if(hasCrashed()) + { + description.append(tr(", has crashed.")); + } + return description; } shared_qobject_ptr MinecraftInstance::createUpdateTask(Net::Mode mode) { - switch (mode) - { - case Net::Mode::Offline: - { - return shared_qobject_ptr(new MinecraftLoadAndCheck(this)); - } - case Net::Mode::Online: - { - return shared_qobject_ptr(new OneSixUpdate(this)); - } - } - return nullptr; -} - -std::shared_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session) -{ - auto process = LaunchTask::create(std::dynamic_pointer_cast(getSharedPtr())); - auto pptr = process.get(); - - ENV.icons()->saveIcon(iconKey(), FS::PathCombine(minecraftRoot(), "icon.png"), "PNG"); - - // print a header - { - process->appendStep(std::make_shared(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC)); - } - - // check java - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // check launch method - QStringList validMethods = {"LauncherPart", "DirectJava"}; - QString method = launchMethod(); - if(!validMethods.contains(method)) - { - process->appendStep(std::make_shared(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); - return process; - } - - // run pre-launch command if that's needed - if(getPreLaunchCommand().size()) - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - process->appendStep(step); - } - - // if we aren't in offline mode,. - if(session->status != AuthSession::PlayableOffline) - { - process->appendStep(std::make_shared(pptr, session)); - process->appendStep(std::make_shared(pptr, Net::Mode::Online)); - } - else - { - process->appendStep(std::make_shared(pptr, Net::Mode::Offline)); - } - - // if there are any jar mods - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // print some instance info here... - { - auto step = std::make_shared(pptr, session); - process->appendStep(step); - } - - // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732) - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - // extract native jars if needed - { - auto step = std::make_shared(pptr); - process->appendStep(step); - } - - { - // actually launch the game - auto method = launchMethod(); - if(method == "LauncherPart") - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - process->appendStep(step); - } - else if (method == "DirectJava") - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - step->setAuthSession(session); - process->appendStep(step); - } - } - - // run post-exit command if that's needed - if(getPostExitCommand().size()) - { - auto step = std::make_shared(pptr); - step->setWorkingDirectory(minecraftRoot()); - process->appendStep(step); - } - if (session) - { - process->setCensorFilter(createCensorFilterFromSession(session)); - } - m_launchProcess = process; - emit launchTaskChanged(m_launchProcess); - return m_launchProcess; + switch (mode) + { + case Net::Mode::Offline: + { + return shared_qobject_ptr(new MinecraftLoadAndCheck(this)); + } + case Net::Mode::Online: + { + return shared_qobject_ptr(new MinecraftUpdate(this)); + } + } + return nullptr; +} + +shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session) +{ + // FIXME: get rid of shared_from_this ... + auto process = LaunchTask::create(std::dynamic_pointer_cast(shared_from_this())); + auto pptr = process.get(); + + ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG"); + + // print a header + { + process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC)); + } + + // check java + { + process->appendStep(new CheckJava(pptr)); + } + + // check launch method + QStringList validMethods = {"LauncherPart", "DirectJava"}; + QString method = launchMethod(); + if(!validMethods.contains(method)) + { + process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal)); + return process; + } + + // run pre-launch command if that's needed + if(getPreLaunchCommand().size()) + { + auto step = new PreLaunchCommand(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + + // if we aren't in offline mode,. + if(session->status != AuthSession::PlayableOffline) + { + process->appendStep(new ClaimAccount(pptr, session)); + process->appendStep(new Update(pptr, Net::Mode::Online)); + } + else + { + process->appendStep(new Update(pptr, Net::Mode::Offline)); + } + + // if there are any jar mods + { + process->appendStep(new ModMinecraftJar(pptr)); + } + + // if there are any jar mods + { + process->appendStep(new ScanModFolders(pptr)); + } + + // print some instance info here... + { + process->appendStep(new PrintInstanceInfo(pptr, session)); + } + + // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732) + { + process->appendStep(new CreateServerResourcePacksFolder(pptr)); + } + + // extract native jars if needed + { + process->appendStep(new ExtractNatives(pptr)); + } + + // reconstruct assets if needed + { + process->appendStep(new ReconstructAssets(pptr)); + } + + { + // actually launch the game + auto method = launchMethod(); + if(method == "LauncherPart") + { + auto step = new LauncherPartLaunch(pptr); + step->setWorkingDirectory(gameRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + else if (method == "DirectJava") + { + auto step = new DirectJavaLaunch(pptr); + step->setWorkingDirectory(gameRoot()); + step->setAuthSession(session); + process->appendStep(step); + } + } + + // run post-exit command if that's needed + if(getPostExitCommand().size()) + { + auto step = new PostLaunchCommand(pptr); + step->setWorkingDirectory(gameRoot()); + process->appendStep(step); + } + if (session) + { + process->setCensorFilter(createCensorFilterFromSession(session)); + } + m_launchProcess = process; + emit launchTaskChanged(m_launchProcess); + return m_launchProcess; } QString MinecraftInstance::launchMethod() { - return m_settings->get("MCLaunchMethod").toString(); + return m_settings->get("MCLaunchMethod").toString(); } JavaVersion MinecraftInstance::getJavaVersion() const { - return JavaVersion(settings()->get("JavaVersion").toString()); + return JavaVersion(settings()->get("JavaVersion").toString()); } -std::shared_ptr MinecraftInstance::loaderModList() const +std::shared_ptr MinecraftInstance::loaderModList() const { - if (!m_loader_mod_list) - { - m_loader_mod_list.reset(new ModList(loaderModsDir())); - } - m_loader_mod_list->update(); - return m_loader_mod_list; + if (!m_loader_mod_list) + { + m_loader_mod_list.reset(new ModFolderModel(loaderModsDir())); + m_loader_mod_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction); + } + return m_loader_mod_list; } -std::shared_ptr MinecraftInstance::coreModList() const +std::shared_ptr MinecraftInstance::coreModList() const { - if (!m_core_mod_list) - { - m_core_mod_list.reset(new ModList(coreModsDir())); - } - m_core_mod_list->update(); - return m_core_mod_list; + if (!m_core_mod_list) + { + m_core_mod_list.reset(new ModFolderModel(coreModsDir())); + m_core_mod_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction); + } + return m_core_mod_list; } -std::shared_ptr MinecraftInstance::resourcePackList() const +std::shared_ptr MinecraftInstance::resourcePackList() const { - if (!m_resource_pack_list) - { - m_resource_pack_list.reset(new ModList(resourcePacksDir())); - } - m_resource_pack_list->update(); - return m_resource_pack_list; + if (!m_resource_pack_list) + { + m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir())); + m_resource_pack_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction); + } + return m_resource_pack_list; } -std::shared_ptr MinecraftInstance::texturePackList() const +std::shared_ptr MinecraftInstance::texturePackList() const { - if (!m_texture_pack_list) - { - m_texture_pack_list.reset(new ModList(texturePacksDir())); - } - m_texture_pack_list->update(); - return m_texture_pack_list; + if (!m_texture_pack_list) + { + m_texture_pack_list.reset(new ModFolderModel(texturePacksDir())); + m_texture_pack_list->disableInteraction(isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction); + } + return m_texture_pack_list; } std::shared_ptr MinecraftInstance::worldList() const { - if (!m_world_list) - { - m_world_list.reset(new WorldList(worldDir())); - } - return m_world_list; + if (!m_world_list) + { + m_world_list.reset(new WorldList(worldDir())); + } + return m_world_list; +} + +std::shared_ptr MinecraftInstance::gameOptionsModel() const +{ + if (!m_game_options) + { + m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt"))); + } + return m_game_options; } QList< Mod > MinecraftInstance::getJarMods() const { - auto profile = m_components->getProfile(); - QList mods; - for (auto jarmod : profile->getJarMods()) - { - QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); - // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); - mods.push_back(Mod(QFileInfo(jar[0]))); - } - return mods; + auto profile = m_components->getProfile(); + QList mods; + for (auto jarmod : profile->getJarMods()) + { + QStringList jar, temp1, temp2, temp3; + jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); + // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); + mods.push_back(Mod(QFileInfo(jar[0]))); + } + return mods; } diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h index 446a39d5..a8f64109 100644 --- a/api/logic/minecraft/MinecraftInstance.h +++ b/api/logic/minecraft/MinecraftInstance.h @@ -1,124 +1,132 @@ #pragma once #include "BaseInstance.h" #include -#include "minecraft/Mod.h" +#include "minecraft/mod/Mod.h" #include #include #include "multimc_logic_export.h" -class ModList; +class ModFolderModel; class WorldList; +class GameOptions; class LaunchStep; class ComponentList; class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance { - Q_OBJECT + Q_OBJECT public: - MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - virtual ~MinecraftInstance() {}; - virtual void init() override; - virtual void saveNow(); - - // FIXME: remove - QString typeName() const override; - // FIXME: remove - QSet traits() const override; - - bool canEdit() const override - { - return true; - } - - bool canExport() const override - { - return true; - } - - ////// Directories and files ////// - QString jarModsDir() const; - QString resourcePacksDir() const; - QString texturePacksDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString libDir() const; - QString worldDir() const; - QDir jarmodsPath() const; - QDir librariesPath() const; - QDir versionsPath() const; - QString instanceConfigFolder() const override; - QString minecraftRoot() const; // Path to the instance's minecraft directory. - QString binRoot() const; // Path to the instance's minecraft bin directory. - QString getNativePath() const; // where to put the natives during/before launch - QString getLocalLibraryPath() const; // where the instance-local libraries should be - - - ////// Profile management ////// - std::shared_ptr getComponentList() const; - - ////// Mod Lists ////// - std::shared_ptr loaderModList() const; - std::shared_ptr coreModList() const; - std::shared_ptr resourcePackList() const; - std::shared_ptr texturePackList() const; - std::shared_ptr worldList() const; - - - ////// Launch stuff ////// - shared_qobject_ptr createUpdateTask(Net::Mode mode) override; - std::shared_ptr createLaunchTask(AuthSessionPtr account) override; - QStringList extraArguments() const override; - QStringList verboseDescription(AuthSessionPtr session) override; - QList getJarMods() const; - QString createLaunchScript(AuthSessionPtr session); - /// get arguments passed to java - QStringList javaArguments() const; - - /// get variables for launch command variable substitution/environment - QMap getVariables() const override; - - /// create an environment for launching processes - QProcessEnvironment createEnvironment() override; - - /// guess log level from a line of minecraft log - MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; - - IPathMatcher::Ptr getLogFileMatcher() override; - - QString getLogFileRoot() override; - - QString getStatusbarDescription() override; - - // FIXME: remove - virtual QStringList getClassPath() const; - // FIXME: remove - virtual QStringList getNativeJars() const; - // FIXME: remove - virtual QString getMainClass() const; - - // FIXME: remove - virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; - - virtual JavaVersion getJavaVersion() const; - -signals: - void versionReloaded(); + MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + virtual ~MinecraftInstance() {}; + virtual void saveNow() override; + + // FIXME: remove + QString typeName() const override; + // FIXME: remove + QSet traits() const override; + + bool canEdit() const override + { + return true; + } + + bool canExport() const override + { + return true; + } + + ////// Directories and files ////// + QString jarModsDir() const; + QString resourcePacksDir() const; + QString texturePacksDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString modsCacheLocation() const; + QString libDir() const; + QString worldDir() const; + QString resourcesDir() const; + QDir jarmodsPath() const; + QDir librariesPath() const; + QDir versionsPath() const; + QString instanceConfigFolder() const override; + + // Path to the instance's minecraft directory. + QString gameRoot() const override; + + // Path to the instance's minecraft bin directory. + QString binRoot() const; + + // where to put the natives during/before launch + QString getNativePath() const; + + // where the instance-local libraries should be + QString getLocalLibraryPath() const; + + + ////// Profile management ////// + std::shared_ptr getComponentList() const; + + ////// Mod Lists ////// + std::shared_ptr loaderModList() const; + std::shared_ptr coreModList() const; + std::shared_ptr resourcePackList() const; + std::shared_ptr texturePackList() const; + std::shared_ptr worldList() const; + std::shared_ptr gameOptionsModel() const; + + ////// Launch stuff ////// + shared_qobject_ptr createUpdateTask(Net::Mode mode) override; + shared_qobject_ptr createLaunchTask(AuthSessionPtr account) override; + QStringList extraArguments() const override; + QStringList verboseDescription(AuthSessionPtr session) override; + QList getJarMods() const; + QString createLaunchScript(AuthSessionPtr session); + /// get arguments passed to java + QStringList javaArguments() const; + + /// get variables for launch command variable substitution/environment + QMap getVariables() const override; + + /// create an environment for launching processes + QProcessEnvironment createEnvironment() override; + + /// guess log level from a line of minecraft log + MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; + + IPathMatcher::Ptr getLogFileMatcher() override; + + QString getLogFileRoot() override; + + QString getStatusbarDescription() override; + + // FIXME: remove + virtual QStringList getClassPath() const; + // FIXME: remove + virtual QStringList getNativeJars() const; + // FIXME: remove + virtual QString getMainClass() const; + + // FIXME: remove + virtual QStringList processMinecraftArgs(AuthSessionPtr account) const; + + virtual JavaVersion getJavaVersion() const; protected: - QMap createCensorFilterFromSession(AuthSessionPtr session); - QStringList validLaunchMethods(); - QString launchMethod(); + QMap createCensorFilterFromSession(AuthSessionPtr session); + QStringList validLaunchMethods(); + QString launchMethod(); private: - QString prettifyTimeDuration(int64_t duration); + QString prettifyTimeDuration(int64_t duration); protected: // data - std::shared_ptr m_components; - mutable std::shared_ptr m_loader_mod_list; - mutable std::shared_ptr m_core_mod_list; - mutable std::shared_ptr m_resource_pack_list; - mutable std::shared_ptr m_texture_pack_list; - mutable std::shared_ptr m_world_list; + std::shared_ptr m_components; + mutable std::shared_ptr m_loader_mod_list; + mutable std::shared_ptr m_core_mod_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_texture_pack_list; + mutable std::shared_ptr m_world_list; + mutable std::shared_ptr m_game_options; }; typedef std::shared_ptr MinecraftInstancePtr; diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp index c64bbddf..593e2fc4 100644 --- a/api/logic/minecraft/MinecraftLoadAndCheck.cpp +++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp @@ -8,38 +8,38 @@ MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *p void MinecraftLoadAndCheck::executeTask() { - // add offline metadata load task - auto components = m_inst->getComponentList(); - components->reload(Net::Mode::Offline); - m_task = components->getCurrentTask(); + // add offline metadata load task + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Offline); + m_task = components->getCurrentTask(); - if(!m_task) - { - emitSucceeded(); - return; - } - connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); - connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); - connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); - connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); + if(!m_task) + { + emitSucceeded(); + return; + } + connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); + connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); } void MinecraftLoadAndCheck::subtaskSucceeded() { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - emitSucceeded(); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + emitSucceeded(); } void MinecraftLoadAndCheck::subtaskFailed(QString error) { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - emitFailed(error); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + emitFailed(error); } diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h index 00515f2d..6924ca2d 100644 --- a/api/logic/minecraft/MinecraftLoadAndCheck.h +++ b/api/logic/minecraft/MinecraftLoadAndCheck.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,19 +29,20 @@ class MinecraftInstance; class MinecraftLoadAndCheck : public Task { - Q_OBJECT + Q_OBJECT public: - explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); - void executeTask() override; + explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0); + virtual ~MinecraftLoadAndCheck() {}; + void executeTask() override; private slots: - void subtaskSucceeded(); - void subtaskFailed(QString error); + void subtaskSucceeded(); + void subtaskFailed(QString error); private: - MinecraftInstance *m_inst = nullptr; - shared_qobject_ptr m_task; - QString m_preFailure; - QString m_fail_reason; + MinecraftInstance *m_inst = nullptr; + shared_qobject_ptr m_task; + QString m_preFailure; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp index 86835fa4..00558e37 100644 --- a/api/logic/minecraft/MinecraftUpdate.cpp +++ b/api/logic/minecraft/MinecraftUpdate.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,148 +37,148 @@ #include #include -OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) +MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst) { } -void OneSixUpdate::executeTask() +void MinecraftUpdate::executeTask() { - m_tasks.clear(); - // create folders - { - m_tasks.append(std::make_shared(m_inst)); - } - - // add metadata update task if necessary - { - auto components = m_inst->getComponentList(); - components->reload(Net::Mode::Online); - auto task = components->getCurrentTask(); - if(task) - { - m_tasks.append(task.unwrap()); - } - } - - // libraries download - { - m_tasks.append(std::make_shared(m_inst)); - } - - // FML libraries download and copy into the instance - { - m_tasks.append(std::make_shared(m_inst)); - } - - // assets update - { - m_tasks.append(std::make_shared(m_inst)); - } - - if(!m_preFailure.isEmpty()) - { - emitFailed(m_preFailure); - return; - } - next(); + m_tasks.clear(); + // create folders + { + m_tasks.append(std::make_shared(m_inst)); + } + + // add metadata update task if necessary + { + auto components = m_inst->getComponentList(); + components->reload(Net::Mode::Online); + auto task = components->getCurrentTask(); + if(task) + { + m_tasks.append(task.unwrap()); + } + } + + // libraries download + { + m_tasks.append(std::make_shared(m_inst)); + } + + // FML libraries download and copy into the instance + { + m_tasks.append(std::make_shared(m_inst)); + } + + // assets update + { + m_tasks.append(std::make_shared(m_inst)); + } + + if(!m_preFailure.isEmpty()) + { + emitFailed(m_preFailure); + return; + } + next(); } -void OneSixUpdate::next() +void MinecraftUpdate::next() { - if(m_abort) - { - emitFailed(tr("Aborted by user.")); - return; - } - if(m_failed_out_of_order) - { - emitFailed(m_fail_reason); - return; - } - m_currentTask ++; - if(m_currentTask > 0) - { - auto task = m_tasks[m_currentTask - 1]; - disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); - disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); - disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress); - disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); - } - if(m_currentTask == m_tasks.size()) - { - emitSucceeded(); - return; - } - auto task = m_tasks[m_currentTask]; - // if the task is already finished by the time we look at it, skip it - if(task->isFinished()) - { - qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); - next(); - } - connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded); - connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed); - connect(task.get(), &Task::progress, this, &OneSixUpdate::progress); - connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus); - // if the task is already running, do not start it again - if(!task->isRunning()) - { - task->start(); - } + if(m_abort) + { + emitFailed(tr("Aborted by user.")); + return; + } + if(m_failed_out_of_order) + { + emitFailed(m_fail_reason); + return; + } + m_currentTask ++; + if(m_currentTask > 0) + { + auto task = m_tasks[m_currentTask - 1]; + disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); + disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + } + if(m_currentTask == m_tasks.size()) + { + emitSucceeded(); + return; + } + auto task = m_tasks[m_currentTask]; + // if the task is already finished by the time we look at it, skip it + if(task->isFinished()) + { + qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get(); + next(); + } + connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded); + connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed); + connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress); + connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus); + // if the task is already running, do not start it again + if(!task->isRunning()) + { + task->start(); + } } -void OneSixUpdate::subtaskSucceeded() +void MinecraftUpdate::subtaskSucceeded() { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order."; - return; - } - next(); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order."; + return; + } + next(); } -void OneSixUpdate::subtaskFailed(QString error) +void MinecraftUpdate::subtaskFailed(QString error) { - if(isFinished()) - { - qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; - } - auto senderTask = QObject::sender(); - auto currentTask = m_tasks[m_currentTask].get(); - if(senderTask != currentTask) - { - qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order."; - m_failed_out_of_order = true; - m_fail_reason = error; - return; - } - emitFailed(error); + if(isFinished()) + { + qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; + return; + } + auto senderTask = QObject::sender(); + auto currentTask = m_tasks[m_currentTask].get(); + if(senderTask != currentTask) + { + qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order."; + m_failed_out_of_order = true; + m_fail_reason = error; + return; + } + emitFailed(error); } -bool OneSixUpdate::abort() +bool MinecraftUpdate::abort() { - if(!m_abort) - { - m_abort = true; - auto task = m_tasks[m_currentTask]; - if(task->canAbort()) - { - return task->abort(); - } - } - return true; + if(!m_abort) + { + m_abort = true; + auto task = m_tasks[m_currentTask]; + if(task->canAbort()) + { + return task->abort(); + } + } + return true; } -bool OneSixUpdate::canAbort() const +bool MinecraftUpdate::canAbort() const { - return true; + return true; } diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h index 78c02049..eb8e74db 100644 --- a/api/logic/minecraft/MinecraftUpdate.h +++ b/api/logic/minecraft/MinecraftUpdate.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,29 +27,31 @@ class MinecraftVersion; class MinecraftInstance; -class OneSixUpdate : public Task +class MinecraftUpdate : public Task { - Q_OBJECT + Q_OBJECT public: - explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0); - void executeTask() override; - bool canAbort() const override; + explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0); + virtual ~MinecraftUpdate() {}; + + void executeTask() override; + bool canAbort() const override; private slots: - bool abort() override; - void subtaskSucceeded(); - void subtaskFailed(QString error); + bool abort() override; + void subtaskSucceeded(); + void subtaskFailed(QString error); private: - void next(); + void next(); private: - MinecraftInstance *m_inst = nullptr; - QList> m_tasks; - QString m_preFailure; - int m_currentTask = -1; - bool m_abort = false; - bool m_failed_out_of_order = false; - QString m_fail_reason; + MinecraftInstance *m_inst = nullptr; + QList> m_tasks; + QString m_preFailure; + int m_currentTask = -1; + bool m_abort = false; + bool m_failed_out_of_order = false; + QString m_fail_reason; }; diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp deleted file mode 100644 index 03e04b2b..00000000 --- a/api/logic/minecraft/Mod.cpp +++ /dev/null @@ -1,378 +0,0 @@ -/* Copyright 2013-2018 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 "Mod.h" -#include "settings/INIFile.h" -#include -#include - -Mod::Mod(const QFileInfo &file) -{ - repath(file); - m_changedDateTime = file.lastModified(); -} - -void Mod::repath(const QFileInfo &file) -{ - m_file = file; - QString name_base = file.fileName(); - - m_type = Mod::MOD_UNKNOWN; - - if (m_file.isDir()) - { - m_type = MOD_FOLDER; - m_name = name_base; - m_mmc_id = name_base; - } - else if (m_file.isFile()) - { - if (name_base.endsWith(".disabled")) - { - m_enabled = false; - name_base.chop(9); - } - else - { - m_enabled = true; - } - m_mmc_id = name_base; - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) - { - m_type = MOD_ZIPFILE; - name_base.chop(4); - } - else if (name_base.endsWith(".litemod")) - { - m_type = MOD_LITEMOD; - name_base.chop(8); - } - else - { - m_type = MOD_SINGLEFILE; - } - m_name = name_base; - } - - if (m_type == MOD_ZIPFILE) - { - QuaZip zip(m_file.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("mcmod.info")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadMCModInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - else if (zip.setCurrentFile("forgeversion.properties")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadForgeInfo(file.readAll()); - file.close(); - zip.close(); - return; - } - - zip.close(); - } - else if (m_type == MOD_FOLDER) - { - QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info")); - if (mcmod_info.isFile()) - { - QFile mcmod(mcmod_info.filePath()); - if (!mcmod.open(QIODevice::ReadOnly)) - return; - auto data = mcmod.readAll(); - if (data.isEmpty() || data.isNull()) - return; - ReadMCModInfo(data); - } - } - else if (m_type == MOD_LITEMOD) - { - QuaZip zip(m_file.filePath()); - if (!zip.open(QuaZip::mdUnzip)) - return; - - QuaZipFile file(&zip); - - if (zip.setCurrentFile("litemod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { - zip.close(); - return; - } - - ReadLiteModInfo(file.readAll()); - file.close(); - } - zip.close(); - } -} - -// NEW format -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 - -// OLD format: -// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc -void Mod::ReadMCModInfo(QByteArray contents) -{ - auto getInfoFromArray = [&](QJsonArray arr)->void - { - if (!arr.at(0).isObject()) - return; - auto firstObj = arr.at(0).toObject(); - m_mod_id = firstObj.value("modid").toString(); - m_name = firstObj.value("name").toString(); - m_version = firstObj.value("version").toString(); - m_homeurl = firstObj.value("url").toString(); - m_updateurl = firstObj.value("updateUrl").toString(); - m_homeurl = m_homeurl.trimmed(); - if(!m_homeurl.isEmpty()) - { - // fix up url. - if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") && - !m_homeurl.startsWith("ftp://")) - { - m_homeurl.prepend("http://"); - } - } - m_description = firstObj.value("description").toString(); - QJsonArray authors = firstObj.value("authorList").toArray(); - if (authors.size() == 0) - authors = firstObj.value("authors").toArray(); - - if (authors.size() == 0) - m_authors = ""; - else if (authors.size() >= 1) - { - m_authors = authors.at(0).toString(); - for (int i = 1; i < authors.size(); i++) - { - m_authors += ", " + authors.at(i).toString(); - } - } - m_credits = firstObj.value("credits").toString(); - return; - } - ; - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - // this is the very old format that had just the array - if (jsonDoc.isArray()) - { - getInfoFromArray(jsonDoc.array()); - } - else if (jsonDoc.isObject()) - { - auto val = jsonDoc.object().value("modinfoversion"); - if(val.isUndefined()) - val = jsonDoc.object().value("modListVersion"); - int version = val.toDouble(); - if (version != 2) - { - qCritical() << "BAD stuff happened to mod json:"; - qCritical() << contents; - return; - } - auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isUndefined()) - arrVal = jsonDoc.object().value("modList"); - if (arrVal.isArray()) - { - getInfoFromArray(arrVal.toArray()); - } - } -} - -void Mod::ReadForgeInfo(QByteArray contents) -{ - // Read the data - m_name = "Minecraft Forge"; - m_mod_id = "Forge"; - m_homeurl = "http://www.minecraftforge.net/forum/"; - INIFile ini; - if (!ini.loadFile(contents)) - return; - - QString major = ini.get("forge.major.number", "0").toString(); - QString minor = ini.get("forge.minor.number", "0").toString(); - QString revision = ini.get("forge.revision.number", "0").toString(); - QString build = ini.get("forge.build.number", "0").toString(); - - m_version = major + "." + minor + "." + revision + "." + build; -} - -void Mod::ReadLiteModInfo(QByteArray contents) -{ - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); - auto object = jsonDoc.object(); - if (object.contains("name")) - { - m_mod_id = m_name = object.value("name").toString(); - } - if (object.contains("version")) - { - m_version = object.value("version").toString(""); - } - else - { - m_version = object.value("revision").toString(""); - } - m_mcversion = object.value("mcversion").toString(); - m_authors = object.value("author").toString(); - m_description = object.value("description").toString(); - m_homeurl = object.value("url").toString(); -} - -bool Mod::replace(Mod &with) -{ - if (!destroy()) - return false; - bool success = false; - auto t = with.type(); - - if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD) - { - qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath(); - success = QFile::copy(with.m_file.filePath(), m_file.filePath()); - } - if (t == MOD_FOLDER) - { - success = FS::copy(with.m_file.filePath(), m_file.path())(); - } - if (success) - { - m_name = with.m_name; - m_mmc_id = with.m_mmc_id; - m_mod_id = with.m_mod_id; - m_version = with.m_version; - m_mcversion = with.m_mcversion; - m_description = with.m_description; - m_authors = with.m_authors; - m_credits = with.m_credits; - m_homeurl = with.m_homeurl; - m_type = with.m_type; - m_file.refresh(); - } - return success; -} - -bool Mod::destroy() -{ - if (m_type == MOD_FOLDER) - { - QDir d(m_file.filePath()); - if (d.removeRecursively()) - { - m_type = MOD_UNKNOWN; - return true; - } - return false; - } - else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD) - { - QFile f(m_file.filePath()); - if (f.remove()) - { - m_type = MOD_UNKNOWN; - return true; - } - return false; - } - return true; -} - -QString Mod::version() const -{ - switch (type()) - { - case MOD_ZIPFILE: - case MOD_LITEMOD: - return m_version; - case MOD_FOLDER: - return "Folder"; - case MOD_SINGLEFILE: - return "File"; - default: - return "VOID"; - } -} - -bool Mod::enable(bool value) -{ - if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) - return false; - - if (m_enabled == value) - return false; - - QString path = m_file.absoluteFilePath(); - if (value) - { - QFile foo(path); - if (!path.endsWith(".disabled")) - return false; - path.chop(9); - if (!foo.rename(path)) - return false; - } - else - { - QFile foo(path); - path += ".disabled"; - if (!foo.rename(path)) - return false; - } - m_file = QFileInfo(path); - m_enabled = value; - return true; -} -bool Mod::operator==(const Mod &other) const -{ - return mmc_id() == other.mmc_id(); -} -bool Mod::strongCompare(const Mod &other) const -{ - return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type(); -} diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/Mod.h deleted file mode 100644 index ccab1867..00000000 --- a/api/logic/minecraft/Mod.h +++ /dev/null @@ -1,142 +0,0 @@ -/* Copyright 2013-2018 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 - -class Mod -{ -public: - enum ModType - { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. - MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. - MOD_LITEMOD, //!< The mod is a litemod - }; - - Mod(const QFileInfo &file); - - QFileInfo filename() const - { - return m_file; - } - QString mmc_id() const - { - return m_mmc_id; - } - QString mod_id() const - { - return m_mod_id; - } - ModType type() const - { - return m_type; - } - QString mcversion() const - { - return m_mcversion; - } - ; - bool valid() - { - return m_type != MOD_UNKNOWN; - } - QString name() const - { - QString name = m_name.trimmed(); - if(name.isEmpty() || name == "Example Mod") - { - return m_mmc_id; - } - return m_name; - } - - QString version() const; - - QString homeurl() const - { - return m_homeurl; - } - - QString description() const - { - return m_description; - } - - QString authors() const - { - return m_authors; - } - - QString credits() const - { - return m_credits; - } - - QDateTime dateTimeChanged() const - { - return m_changedDateTime; - } - - bool enabled() const - { - return m_enabled; - } - - bool enable(bool value); - - // delete all the files of this mod - bool destroy(); - // replace this mod with a copy of the other - bool replace(Mod &with); - // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - - // WEAK compare operator - used for replacing mods - bool operator==(const Mod &other) const; - bool strongCompare(const Mod &other) const; - -private: - void ReadMCModInfo(QByteArray contents); - void ReadForgeInfo(QByteArray contents); - void ReadLiteModInfo(QByteArray contents); - -protected: - - // FIXME: what do do with those? HMM... - /* - void ReadModInfoData(QString info); - void ReadForgeInfoData(QString infoFileData); - */ - - QFileInfo m_file; - QDateTime m_changedDateTime; - QString m_mmc_id; - QString m_mod_id; - bool m_enabled = true; - QString m_name; - QString m_version; - QString m_mcversion; - QString m_homeurl; - QString m_updateurl; - QString m_description; - QString m_authors; - QString m_credits; - - ModType m_type; -}; diff --git a/api/logic/minecraft/ModList.cpp b/api/logic/minecraft/ModList.cpp deleted file mode 100644 index 6ccf20e2..00000000 --- a/api/logic/minecraft/ModList.cpp +++ /dev/null @@ -1,367 +0,0 @@ -/* Copyright 2013-2018 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 "ModList.h" -#include -#include -#include -#include -#include -#include -#include - -ModList::ModList(const QString &dir) : QAbstractListModel(), m_dir(dir) -{ - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); -} - -void ModList::startWatching() -{ - if(is_watching) - return; - - update(); - - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } -} - -void ModList::stopWatching() -{ - if(!is_watching) - return; - - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } -} - -bool ModList::update() -{ - if (!isValid()) - return false; - - QList orderedMods; - QList newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - bool orderOrStateChanged = false; - - // if there are any untracked files... - if (folderContents.size()) - { - // the order surely changed! - for (auto entry : folderContents) - { - newMods.append(Mod(entry)); - } - orderedMods.append(newMods); - orderOrStateChanged = true; - } - // otherwise, if we were already tracking some mods - else if (mods.size()) - { - // if the number doesn't match, order changed. - if (mods.size() != orderedMods.size()) - orderOrStateChanged = true; - // if it does match, compare the mods themselves - else - for (int i = 0; i < mods.size(); i++) - { - if (!mods[i].strongCompare(orderedMods[i])) - { - orderOrStateChanged = true; - break; - } - } - } - beginResetModel(); - mods.swap(orderedMods); - endResetModel(); - if (orderOrStateChanged) - { - emit changed(); - } - return true; -} - -void ModList::directoryChanged(QString path) -{ - update(); -} - -bool ModList::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -bool ModList::installMod(const QString &filename) -{ - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - QFileInfo fileinfo(FS::NormalizePath(filename)); - - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - if (!fileinfo.exists() || !fileinfo.isReadable()) - { - return false; - } - Mod m(fileinfo); - if (!m.valid()) - return false; - - auto type = m.type(); - if (type == Mod::MOD_UNKNOWN) - return false; - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!QFile::copy(fileinfo.filePath(), newpath)) - return false; - FS::updateTimestamp(newpath); - m.repath(newpath); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - QString from = fileinfo.filePath(); - QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName()); - if (!FS::copy(from, to)()) - return false; - m.repath(to); - update(); - return true; - } - return false; -} - -bool ModList::enableMods(const QModelIndexList& indexes, bool enable) -{ - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.enable(enable); - emit dataChanged(i, i); - } - emit changed(); - return true; -} - -bool ModList::deleteMods(const QModelIndexList& indexes) -{ - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - Mod &m = mods[i.row()]; - m.destroy(); - } - emit changed(); - return true; -} - -int ModList::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - -QVariant ModList::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= mods.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return mods[row].name(); - case VersionColumn: - return mods[row].version(); - case DateColumn: - return mods[row].dateTimeChanged(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return mods[row].mmc_id(); - - case Qt::CheckStateRole: - switch (column) - { - case ActiveColumn: - return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; - default: - return QVariant(); - } - default: - return QVariant(); - } -} - -bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - auto &mod = mods[index.row()]; - if (mod.enable(!mod.enabled())) - { - emit dataChanged(index, index); - return true; - } - } - return false; -} - -QVariant ModList::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - case DateColumn: - return tr("Last changed"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - case DateColumn: - return tr("The date and time this mod was last changed (or added)."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - -Qt::ItemFlags ModList::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; -} - -Qt::DropActions ModList::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList ModList::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -bool ModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - // check if the action is supported - if (!data || !(action & supportedDropActions())) - { - return false; - } - - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - { - stopWatching(); - } - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - { - continue; - } - // TODO: implement not only copy, but also move - // FIXME: handle errors here - installMod(url.toLocalFile()); - } - if (was_watching) - { - startWatching(); - } - return true; - } - return false; -} diff --git a/api/logic/minecraft/ModList.h b/api/logic/minecraft/ModList.h deleted file mode 100644 index 72f50edb..00000000 --- a/api/logic/minecraft/ModList.h +++ /dev/null @@ -1,121 +0,0 @@ -/* Copyright 2013-2018 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 "minecraft/Mod.h" - -#include "multimc_logic_export.h" - -class LegacyInstance; -class BaseInstance; -class QFileSystemWatcher; - -/** - * A legacy mod list. - * Backed by a folder. - */ -class MULTIMC_LOGIC_EXPORT ModList : public QAbstractListModel -{ - Q_OBJECT -public: - enum Columns - { - ActiveColumn = 0, - NameColumn, - DateColumn, - VersionColumn, - NUM_COLUMNS - }; - ModList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::DropActions supportedDropActions() const override; - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; - - virtual int rowCount(const QModelIndex &) const override - { - return size(); - } - ; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual int columnCount(const QModelIndex &parent) const override; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod &operator[](size_t index) - { - return mods[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - bool installMod(const QString& filename); - - /// Deletes all the selected mods - virtual bool deleteMods(const QModelIndexList &indexes); - - /// Enable or disable listed mods - virtual bool enableMods(const QModelIndexList &indexes, bool enable = true); - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() - { - return m_dir; - } - - const QList & allMods() - { - return mods; - } - -private -slots: - void directoryChanged(QString path); - -signals: - void changed(); - -protected: - QFileSystemWatcher *m_watcher; - bool is_watching = false; - QDir m_dir; - QList mods; -}; diff --git a/api/logic/minecraft/ModList_test.cpp b/api/logic/minecraft/ModList_test.cpp deleted file mode 100644 index 155c238a..00000000 --- a/api/logic/minecraft/ModList_test.cpp +++ /dev/null @@ -1,53 +0,0 @@ - -#include -#include -#include "TestUtil.h" - -#include "FileSystem.h" -#include "minecraft/ModList.h" - -class ModListTest : public QObject -{ - Q_OBJECT - -private -slots: - // test for GH-1178 - install a folder with files to a mod list - void test_1178() - { - // source - QString source = QFINDTESTDATA("data/test_folder"); - - // sanity check - QVERIFY(!source.endsWith('/')); - - auto verify = [](QString path) - { - QDir target_dir(FS::PathCombine(path, "test_folder")); - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // 1. test with no trailing / - { - QString folder = source; - QTemporaryDir tempDir; - ModList m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - - // 2. test with trailing / - { - QString folder = source + '/'; - QTemporaryDir tempDir; - ModList m(tempDir.path()); - m.installMod(folder); - verify(tempDir.path()); - } - } -}; - -QTEST_GUILESS_MAIN(ModListTest) - -#include "ModList_test.moc" diff --git a/api/logic/minecraft/MojangDownloadInfo.h b/api/logic/minecraft/MojangDownloadInfo.h index 7399a56b..88f87287 100644 --- a/api/logic/minecraft/MojangDownloadInfo.h +++ b/api/logic/minecraft/MojangDownloadInfo.h @@ -5,78 +5,78 @@ struct MojangDownloadInfo { - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // data - /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! - QString path; - /// absolute URL of this file - QString url; - /// sha-1 checksum of the file - QString sha1; - /// size of the file in bytes - int size; + // data + /// Local filesystem path. WARNING: not used, only here so we can pass through mojang files unmolested! + QString path; + /// absolute URL of this file + QString url; + /// sha-1 checksum of the file + QString sha1; + /// size of the file in bytes + int size; }; struct MojangLibraryDownloadInfo { - MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; - MojangLibraryDownloadInfo() {}; + MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact): artifact(artifact) {}; + MojangLibraryDownloadInfo() {}; - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // methods - MojangDownloadInfo *getDownloadInfo(QString classifier) - { - if (classifier.isNull()) - { - return artifact.get(); - } - - return classifiers[classifier].get(); - } + // methods + MojangDownloadInfo *getDownloadInfo(QString classifier) + { + if (classifier.isNull()) + { + return artifact.get(); + } + + return classifiers[classifier].get(); + } - // data - MojangDownloadInfo::Ptr artifact; - QMap classifiers; + // data + MojangDownloadInfo::Ptr artifact; + QMap classifiers; }; struct MojangAssetIndexInfo : public MojangDownloadInfo { - // types - typedef std::shared_ptr Ptr; + // types + typedef std::shared_ptr Ptr; - // methods - MojangAssetIndexInfo() - { - } + // methods + MojangAssetIndexInfo() + { + } - MojangAssetIndexInfo(QString id) - { - this->id = id; - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - if(id == "legacy") - { - url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; - } - // HACK - else - { - url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; - } - known = false; - } + MojangAssetIndexInfo(QString id) + { + this->id = id; + // HACK: ignore assets from other version files than Minecraft + // workaround for stupid assets issue caused by amazon: + // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ + if(id == "legacy") + { + url = "https://launchermeta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json"; + } + // HACK + else + { + url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json"; + } + known = false; + } - // data - int totalSize; - QString id; - bool known = true; + // data + int totalSize; + QString id; + bool known = true; }; diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp index a0aa2894..33d3c54c 100644 --- a/api/logic/minecraft/MojangVersionFormat.cpp +++ b/api/logic/minecraft/MojangVersionFormat.cpp @@ -19,361 +19,361 @@ namespace Bits { static void readString(const QJsonObject &root, const QString &key, QString &variable) { - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } + if (root.contains(key)) + { + variable = requireString(root.value(key)); + } } static void readDownloadInfo(MojangDownloadInfo::Ptr out, const QJsonObject &obj) { - // optional, not used - readString(obj, "path", out->path); - // required! - out->sha1 = requireString(obj, "sha1"); - out->url = requireString(obj, "url"); - out->size = requireInteger(obj, "size"); + // optional, not used + readString(obj, "path", out->path); + // required! + out->sha1 = requireString(obj, "sha1"); + out->url = requireString(obj, "url"); + out->size = requireInteger(obj, "size"); } static void readAssetIndex(MojangAssetIndexInfo::Ptr out, const QJsonObject &obj) { - out->totalSize = requireInteger(obj, "totalSize"); - out->id = requireString(obj, "id"); - // out->known = true; + out->totalSize = requireInteger(obj, "totalSize"); + out->id = requireString(obj, "id"); + // out->known = true; } } MojangDownloadInfo::Ptr downloadInfoFromJson(const QJsonObject &obj) { - auto out = std::make_shared(); - Bits::readDownloadInfo(out, obj); - return out; + auto out = std::make_shared(); + Bits::readDownloadInfo(out, obj); + return out; } MojangAssetIndexInfo::Ptr assetIndexFromJson(const QJsonObject &obj) { - auto out = std::make_shared(); - Bits::readDownloadInfo(out, obj); - Bits::readAssetIndex(out, obj); - return out; + auto out = std::make_shared(); + Bits::readDownloadInfo(out, obj); + Bits::readAssetIndex(out, obj); + return out; } QJsonObject downloadInfoToJson(MojangDownloadInfo::Ptr info) { - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - return out; + QJsonObject out; + if(!info->path.isNull()) + { + out.insert("path", info->path); + } + out.insert("sha1", info->sha1); + out.insert("size", info->size); + out.insert("url", info->url); + return out; } MojangLibraryDownloadInfo::Ptr libDownloadInfoFromJson(const QJsonObject &libObj) { - auto out = std::make_shared(); - auto dlObj = requireObject(libObj.value("downloads")); - if(dlObj.contains("artifact")) - { - out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); - } - if(dlObj.contains("classifiers")) - { - auto classifiersObj = requireObject(dlObj, "classifiers"); - for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->classifiers[classifier] = downloadInfoFromJson(classifierObj); - } - } - return out; + auto out = std::make_shared(); + auto dlObj = requireObject(libObj.value("downloads")); + if(dlObj.contains("artifact")) + { + out->artifact = downloadInfoFromJson(requireObject(dlObj, "artifact")); + } + if(dlObj.contains("classifiers")) + { + auto classifiersObj = requireObject(dlObj, "classifiers"); + for(auto iter = classifiersObj.begin(); iter != classifiersObj.end(); iter++) + { + auto classifier = iter.key(); + auto classifierObj = requireObject(iter.value()); + out->classifiers[classifier] = downloadInfoFromJson(classifierObj); + } + } + return out; } QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo) { - QJsonObject out; - if(libinfo->artifact) - { - out.insert("artifact", downloadInfoToJson(libinfo->artifact)); - } - if(libinfo->classifiers.size()) - { - QJsonObject classifiersOut; - for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) - { - classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("classifiers", classifiersOut); - } - return out; + QJsonObject out; + if(libinfo->artifact) + { + out.insert("artifact", downloadInfoToJson(libinfo->artifact)); + } + if(libinfo->classifiers.size()) + { + QJsonObject classifiersOut; + for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++) + { + classifiersOut.insert(iter.key(), downloadInfoToJson(iter.value())); + } + out.insert("classifiers", classifiersOut); + } + return out; } QJsonObject assetIndexToJson(MojangAssetIndexInfo::Ptr info) { - QJsonObject out; - if(!info->path.isNull()) - { - out.insert("path", info->path); - } - out.insert("sha1", info->sha1); - out.insert("size", info->size); - out.insert("url", info->url); - out.insert("totalSize", info->totalSize); - out.insert("id", info->id); - return out; + QJsonObject out; + if(!info->path.isNull()) + { + out.insert("path", info->path); + } + out.insert("sha1", info->sha1); + out.insert("size", info->size); + out.insert("url", info->url); + out.insert("totalSize", info->totalSize); + out.insert("id", info->id); + return out; } void MojangVersionFormat::readVersionProperties(const QJsonObject &in, VersionFile *out) { - Bits::readString(in, "id", out->minecraftVersion); - Bits::readString(in, "mainClass", out->mainClass); - Bits::readString(in, "minecraftArguments", out->minecraftArguments); - if(out->minecraftArguments.isEmpty()) - { - QString processArguments; - Bits::readString(in, "processArguments", processArguments); - QString toCompare = processArguments.toLower(); - if (toCompare == "legacy") - { - out->minecraftArguments = " ${auth_player_name} ${auth_session}"; - } - else if (toCompare == "username_session") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; - } - else if (toCompare == "username_session_version") - { - out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; - } - else if (!toCompare.isEmpty()) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); - } - } - Bits::readString(in, "type", out->type); + Bits::readString(in, "id", out->minecraftVersion); + Bits::readString(in, "mainClass", out->mainClass); + Bits::readString(in, "minecraftArguments", out->minecraftArguments); + if(out->minecraftArguments.isEmpty()) + { + QString processArguments; + Bits::readString(in, "processArguments", processArguments); + QString toCompare = processArguments.toLower(); + if (toCompare == "legacy") + { + out->minecraftArguments = " ${auth_player_name} ${auth_session}"; + } + else if (toCompare == "username_session") + { + out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session}"; + } + else if (toCompare == "username_session_version") + { + out->minecraftArguments = "--username ${auth_player_name} --session ${auth_session} --version ${profile_name}"; + } + else if (!toCompare.isEmpty()) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("processArguments is set to unknown value '%1'").arg(processArguments)); + } + } + Bits::readString(in, "type", out->type); - Bits::readString(in, "assets", out->assets); - if(in.contains("assetIndex")) - { - out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); - } - else if (!out->assets.isNull()) - { - out->mojangAssetIndex = std::make_shared(out->assets); - } + Bits::readString(in, "assets", out->assets); + if(in.contains("assetIndex")) + { + out->mojangAssetIndex = assetIndexFromJson(requireObject(in, "assetIndex")); + } + else if (!out->assets.isNull()) + { + out->mojangAssetIndex = std::make_shared(out->assets); + } - out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); - out->updateTime = timeFromS3Time(in.value("time").toString("")); + out->releaseTime = timeFromS3Time(in.value("releaseTime").toString("")); + out->updateTime = timeFromS3Time(in.value("time").toString("")); - if (in.contains("minimumLauncherVersion")) - { - out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); - if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) - { - out->addProblem( - ProblemSeverity::Warning, - QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") - .arg(out->minimumLauncherVersion) - .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); - } - } - if(in.contains("downloads")) - { - auto downloadsObj = requireObject(in, "downloads"); - for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) - { - auto classifier = iter.key(); - auto classifierObj = requireObject(iter.value()); - out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); - } - } + if (in.contains("minimumLauncherVersion")) + { + out->minimumLauncherVersion = requireInteger(in.value("minimumLauncherVersion")); + if (out->minimumLauncherVersion > CURRENT_MINIMUM_LAUNCHER_VERSION) + { + out->addProblem( + ProblemSeverity::Warning, + QObject::tr("The 'minimumLauncherVersion' value of this version (%1) is higher than supported by MultiMC (%2). It might not work properly!") + .arg(out->minimumLauncherVersion) + .arg(CURRENT_MINIMUM_LAUNCHER_VERSION)); + } + } + if(in.contains("downloads")) + { + auto downloadsObj = requireObject(in, "downloads"); + for(auto iter = downloadsObj.begin(); iter != downloadsObj.end(); iter++) + { + auto classifier = iter.key(); + auto classifierObj = requireObject(iter.value()); + out->mojangDownloads[classifier] = downloadInfoFromJson(classifierObj); + } + } } VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename) { - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError(filename + " is not an object"); + } - QJsonObject root = doc.object(); + QJsonObject root = doc.object(); - readVersionProperties(root, out.get()); + readVersionProperties(root, out.get()); - out->name = "Minecraft"; - out->uid = "net.minecraft"; - out->version = out->minecraftVersion; - // out->filename = filename; + out->name = "Minecraft"; + out->uid = "net.minecraft"; + out->version = out->minecraftVersion; + // out->filename = filename; - if (root.contains("libraries")) - { - for (auto libVal : requireArray(root.value("libraries"))) - { - auto libObj = requireObject(libVal); + if (root.contains("libraries")) + { + for (auto libVal : requireArray(root.value("libraries"))) + { + auto libObj = requireObject(libVal); - auto lib = MojangVersionFormat::libraryFromJson(libObj, filename); - out->libraries.append(lib); - } - } - return out; + auto lib = MojangVersionFormat::libraryFromJson(libObj, filename); + out->libraries.append(lib); + } + } + return out; } void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObject& out) { - writeString(out, "id", in->minecraftVersion); - writeString(out, "mainClass", in->mainClass); - writeString(out, "minecraftArguments", in->minecraftArguments); - writeString(out, "type", in->type); - if(!in->releaseTime.isNull()) - { - writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); - } - if(!in->updateTime.isNull()) - { - writeString(out, "time", timeToS3Time(in->updateTime)); - } - if(in->minimumLauncherVersion != -1) - { - out.insert("minimumLauncherVersion", in->minimumLauncherVersion); - } - writeString(out, "assets", in->assets); - if(in->mojangAssetIndex && in->mojangAssetIndex->known) - { - out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); - } - if(in->mojangDownloads.size()) - { - QJsonObject downloadsOut; - for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) - { - downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); - } - out.insert("downloads", downloadsOut); - } + writeString(out, "id", in->minecraftVersion); + writeString(out, "mainClass", in->mainClass); + writeString(out, "minecraftArguments", in->minecraftArguments); + writeString(out, "type", in->type); + if(!in->releaseTime.isNull()) + { + writeString(out, "releaseTime", timeToS3Time(in->releaseTime)); + } + if(!in->updateTime.isNull()) + { + writeString(out, "time", timeToS3Time(in->updateTime)); + } + if(in->minimumLauncherVersion != -1) + { + out.insert("minimumLauncherVersion", in->minimumLauncherVersion); + } + writeString(out, "assets", in->assets); + if(in->mojangAssetIndex && in->mojangAssetIndex->known) + { + out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex)); + } + if(in->mojangDownloads.size()) + { + QJsonObject downloadsOut; + for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++) + { + downloadsOut.insert(iter.key(), downloadInfoToJson(iter.value())); + } + out.insert("downloads", downloadsOut); + } } QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch) { - QJsonObject root; - writeVersionProperties(patch.get(), root); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(MojangVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } + QJsonObject root; + writeVersionProperties(patch.get(), root); + if (!patch->libraries.isEmpty()) + { + QJsonArray array; + for (auto value: patch->libraries) + { + array.append(MojangVersionFormat::libraryToJson(value.get())); + } + root.insert("libraries", array); + } - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } + // write the contents to a json document. + { + QJsonDocument out; + out.setObject(root); + return out; + } } LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); - } - out->m_name = libObj.value("name").toString(); + LibraryPtr out(new Library()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field"); + } + out->m_name = libObj.value("name").toString(); - Bits::readString(libObj, "url", out->m_repositoryURL); - if (libObj.contains("extract")) - { - out->m_hasExcludes = true; - auto extractObj = requireObject(libObj.value("extract")); - for (auto excludeVal : requireArray(extractObj.value("exclude"))) - { - out->m_extractExcludes.append(requireString(excludeVal)); - } - } - if (libObj.contains("natives")) - { - QJsonObject nativesObj = requireObject(libObj.value("natives")); - for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) - { - if (!it.value().isString()) - { - qWarning() << filename << "contains an invalid native (skipping)"; - } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->m_nativeClassifiers[opSys] = it.value().toString(); - } - } - } - if (libObj.contains("rules")) - { - out->applyRules = true; - out->m_rules = rulesFromJsonV4(libObj); - } - if (libObj.contains("downloads")) - { - out->m_mojangDownloads = libDownloadInfoFromJson(libObj); - } - return out; + Bits::readString(libObj, "url", out->m_repositoryURL); + if (libObj.contains("extract")) + { + out->m_hasExcludes = true; + auto extractObj = requireObject(libObj.value("extract")); + for (auto excludeVal : requireArray(extractObj.value("exclude"))) + { + out->m_extractExcludes.append(requireString(excludeVal)); + } + } + if (libObj.contains("natives")) + { + QJsonObject nativesObj = requireObject(libObj.value("natives")); + for (auto it = nativesObj.begin(); it != nativesObj.end(); ++it) + { + if (!it.value().isString()) + { + qWarning() << filename << "contains an invalid native (skipping)"; + } + OpSys opSys = OpSys_fromString(it.key()); + if (opSys != Os_Other) + { + out->m_nativeClassifiers[opSys] = it.value().toString(); + } + } + } + if (libObj.contains("rules")) + { + out->applyRules = true; + out->m_rules = rulesFromJsonV4(libObj); + } + if (libObj.contains("downloads")) + { + out->m_mojangDownloads = libDownloadInfoFromJson(libObj); + } + return out; } QJsonObject MojangVersionFormat::libraryToJson(Library *library) { - QJsonObject libRoot; - libRoot.insert("name", (QString)library->m_name); - if (!library->m_repositoryURL.isEmpty()) - { - libRoot.insert("url", library->m_repositoryURL); - } - if (library->isNative()) - { - QJsonObject nativeList; - auto iter = library->m_nativeClassifiers.begin(); - while (iter != library->m_nativeClassifiers.end()) - { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); - iter++; - } - libRoot.insert("natives", nativeList); - if (library->m_extractExcludes.size()) - { - QJsonArray excludes; - QJsonObject extract; - for (auto exclude : library->m_extractExcludes) - { - excludes.append(exclude); - } - extract.insert("exclude", excludes); - libRoot.insert("extract", extract); - } - } - if (library->m_rules.size()) - { - QJsonArray allRules; - for (auto &rule : library->m_rules) - { - QJsonObject ruleObj = rule->toJson(); - allRules.append(ruleObj); - } - libRoot.insert("rules", allRules); - } - if(library->m_mojangDownloads) - { - auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); - libRoot.insert("downloads", downloadsObj); - } - return libRoot; + QJsonObject libRoot; + libRoot.insert("name", (QString)library->m_name); + if (!library->m_repositoryURL.isEmpty()) + { + libRoot.insert("url", library->m_repositoryURL); + } + if (library->isNative()) + { + QJsonObject nativeList; + auto iter = library->m_nativeClassifiers.begin(); + while (iter != library->m_nativeClassifiers.end()) + { + nativeList.insert(OpSys_toString(iter.key()), iter.value()); + iter++; + } + libRoot.insert("natives", nativeList); + if (library->m_extractExcludes.size()) + { + QJsonArray excludes; + QJsonObject extract; + for (auto exclude : library->m_extractExcludes) + { + excludes.append(exclude); + } + extract.insert("exclude", excludes); + libRoot.insert("extract", extract); + } + } + if (library->m_rules.size()) + { + QJsonArray allRules; + for (auto &rule : library->m_rules) + { + QJsonObject ruleObj = rule->toJson(); + allRules.append(ruleObj); + } + libRoot.insert("rules", allRules); + } + if(library->m_mojangDownloads) + { + auto downloadsObj = libDownloadInfoToJson(library->m_mojangDownloads); + libRoot.insert("downloads", downloadsObj); + } + return libRoot; } diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h index 4e141088..76c529e9 100644 --- a/api/logic/minecraft/MojangVersionFormat.h +++ b/api/logic/minecraft/MojangVersionFormat.h @@ -10,16 +10,16 @@ class MULTIMC_LOGIC_EXPORT MojangVersionFormat { friend class OneSixVersionFormat; protected: - // does not include libraries - static void readVersionProperties(const QJsonObject& in, VersionFile* out); - // does not include libraries - static void writeVersionProperties(const VersionFile* in, QJsonObject& out); + // does not include libraries + static void readVersionProperties(const QJsonObject& in, VersionFile* out); + // does not include libraries + static void writeVersionProperties(const VersionFile* in, QJsonObject& out); public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); + // version files / profile patches + static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); + // libraries + static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject libraryToJson(Library *library); }; diff --git a/api/logic/minecraft/MojangVersionFormat_test.cpp b/api/logic/minecraft/MojangVersionFormat_test.cpp index 9c8482ce..9d095340 100644 --- a/api/logic/minecraft/MojangVersionFormat_test.cpp +++ b/api/logic/minecraft/MojangVersionFormat_test.cpp @@ -6,47 +6,47 @@ class MojangVersionFormatTest : public QObject { - Q_OBJECT - - static QJsonDocument readJson(const char *file) - { - auto path = QFINDTESTDATA(file); - QFile jsonFile(path); - jsonFile.open(QIODevice::ReadOnly); - auto data = jsonFile.readAll(); - jsonFile.close(); - return QJsonDocument::fromJson(data); - } - static void writeJson(const char *file, QJsonDocument doc) - { - QFile jsonFile(file); - jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); - auto data = doc.toJson(QJsonDocument::Indented); - qDebug() << QString::fromUtf8(data); - jsonFile.write(data); - jsonFile.close(); - } + Q_OBJECT + + static QJsonDocument readJson(const char *file) + { + auto path = QFINDTESTDATA(file); + QFile jsonFile(path); + jsonFile.open(QIODevice::ReadOnly); + auto data = jsonFile.readAll(); + jsonFile.close(); + return QJsonDocument::fromJson(data); + } + static void writeJson(const char *file, QJsonDocument doc) + { + QFile jsonFile(file); + jsonFile.open(QIODevice::WriteOnly | QIODevice::Text); + auto data = doc.toJson(QJsonDocument::Indented); + qDebug() << QString::fromUtf8(data); + jsonFile.write(data); + jsonFile.close(); + } private slots: - void test_Through_Simple() - { - QJsonDocument doc = readJson("data/1.9-simple.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-simple-passthorugh.json", doc2); - - QCOMPARE(doc.toJson(), doc2.toJson()); - } - - void test_Through() - { - QJsonDocument doc = readJson("data/1.9.json"); - auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); - auto doc2 = MojangVersionFormat::versionFileToJson(vfile); - writeJson("1.9-passthorugh.json", doc2); - QCOMPARE(doc.toJson(), doc2.toJson()); - } + void test_Through_Simple() + { + QJsonDocument doc = readJson("data/1.9-simple.json"); + auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); + auto doc2 = MojangVersionFormat::versionFileToJson(vfile); + writeJson("1.9-simple-passthorugh.json", doc2); + + QCOMPARE(doc.toJson(), doc2.toJson()); + } + + void test_Through() + { + QJsonDocument doc = readJson("data/1.9.json"); + auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); + auto doc2 = MojangVersionFormat::versionFileToJson(vfile); + writeJson("1.9-passthorugh.json", doc2); + QCOMPARE(doc.toJson(), doc2.toJson()); + } }; QTEST_GUILESS_MAIN(MojangVersionFormatTest) diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp index f7ab25b3..6f3b926b 100644 --- a/api/logic/minecraft/OneSixVersionFormat.cpp +++ b/api/logic/minecraft/OneSixVersionFormat.cpp @@ -7,366 +7,366 @@ using namespace Json; static void readString(const QJsonObject &root, const QString &key, QString &variable) { - if (root.contains(key)) - { - variable = requireString(root.value(key)); - } + if (root.contains(key)) + { + variable = requireString(root.value(key)); + } } LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename) { - LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); - readString(libObj, "MMC-hint", out->m_hint); - readString(libObj, "MMC-absulute_url", out->m_absoluteURL); - readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); - readString(libObj, "MMC-filename", out->m_filename); - readString(libObj, "MMC-displayname", out->m_displayname); - return out; + LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename); + readString(libObj, "MMC-hint", out->m_hint); + readString(libObj, "MMC-absulute_url", out->m_absoluteURL); + readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL); + readString(libObj, "MMC-filename", out->m_filename); + readString(libObj, "MMC-displayname", out->m_displayname); + return out; } QJsonObject OneSixVersionFormat::libraryToJson(Library *library) { - QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); - if (library->m_absoluteURL.size()) - libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); - if (library->m_hint.size()) - libRoot.insert("MMC-hint", library->m_hint); - if (library->m_filename.size()) - libRoot.insert("MMC-filename", library->m_filename); - if (library->m_displayname.size()) - libRoot.insert("MMC-displayname", library->m_displayname); - return libRoot; + QJsonObject libRoot = MojangVersionFormat::libraryToJson(library); + if (library->m_absoluteURL.size()) + libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL); + if (library->m_hint.size()) + libRoot.insert("MMC-hint", library->m_hint); + if (library->m_filename.size()) + libRoot.insert("MMC-filename", library->m_filename); + if (library->m_displayname.size()) + libRoot.insert("MMC-displayname", library->m_displayname); + return libRoot; } VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder) { - VersionFilePtr out(new VersionFile()); - if (doc.isEmpty() || doc.isNull()) - { - throw JSONValidationError(filename + " is empty or null"); - } - if (!doc.isObject()) - { - throw JSONValidationError(filename + " is not an object"); - } - - QJsonObject root = doc.object(); - - Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); - switch(formatVersion) - { - case Meta::MetadataVersion::InitialRelease: - break; - case Meta::MetadataVersion::Invalid: - throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); - } - - if (requireOrder) - { - if (root.contains("order")) - { - out->order = requireInteger(root.value("order")); - } - else - { - // FIXME: evaluate if we don't want to throw exceptions here instead - qCritical() << filename << "doesn't contain an order field"; - } - } - - out->name = root.value("name").toString(); - - if(root.contains("uid")) - { - out->uid = root.value("uid").toString(); - } - else - { - out->uid = root.value("fileId").toString(); - } - - out->version = root.value("version").toString(); - - MojangVersionFormat::readVersionProperties(root, out.get()); - - // added for legacy Minecraft window embedding, TODO: remove - readString(root, "appletClass", out->appletClass); - - if (root.contains("+tweakers")) - { - for (auto tweakerVal : requireArray(root.value("+tweakers"))) - { - out->addTweakers.append(requireString(tweakerVal)); - } - } - - if (root.contains("+traits")) - { - for (auto tweakerVal : requireArray(root.value("+traits"))) - { - out->traits.insert(requireString(tweakerVal)); - } - } - - - if (root.contains("jarMods")) - { - for (auto libVal : requireArray(root.value("jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename); - // and add to jar mods - out->jarMods.append(lib); - } - } - else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility - { - for (auto libVal : requireArray(root.value("+jarMods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name); - // and add to jar mods - out->jarMods.append(lib); - } - } - - if (root.contains("mods")) - { - for (auto libVal : requireArray(root.value("mods"))) - { - QJsonObject libObj = requireObject(libVal); - // parse the jarmod - auto lib = OneSixVersionFormat::modFromJson(libObj, filename); - // and add to jar mods - out->mods.append(lib); - } - } - - auto readLibs = [&](const char * which) - { - for (auto libVal : requireArray(root.value(which))) - { - QJsonObject libObj = requireObject(libVal); - // parse the library - auto lib = libraryFromJson(libObj, filename); - out->libraries.append(lib); - } - }; - bool hasPlusLibs = root.contains("+libraries"); - bool hasLibs = root.contains("libraries"); - if (hasPlusLibs && hasLibs) - { - out->addProblem(ProblemSeverity::Warning, - QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); - readLibs("libraries"); - readLibs("+libraries"); - } - else if (hasLibs) - { - readLibs("libraries"); - } - else if(hasPlusLibs) - { - readLibs("+libraries"); - } - - // if we have mainJar, just use it - if(root.contains("mainJar")) - { - QJsonObject libObj = requireObject(root, "mainJar"); - out->mainJar = libraryFromJson(libObj, filename); - } - // else reconstruct it from downloads and id ... if that's available - else if(!out->minecraftVersion.isEmpty()) - { - auto lib = std::make_shared(); - lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); - // we have a reliable client download, use it. - if(out->mojangDownloads.contains("client")) - { - auto LibDLInfo = std::make_shared(); - LibDLInfo->artifact = out->mojangDownloads["client"]; - lib->setMojangDownloadInfo(LibDLInfo); - } - // we got nothing... guess based on ancient hardcoded Mojang behaviour - // FIXME: this will eventually break... - else - { - lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion)); - } - out->mainJar = lib; - } - - if (root.contains("requires")) - { - Meta::parseRequires(root, &out->requires); - } - QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); - if(!dependsOnMinecraftVersion.isEmpty()) - { - Meta::Require mcReq; - mcReq.uid = "net.minecraft"; - mcReq.equalsVersion = dependsOnMinecraftVersion; - if (out->requires.count(mcReq) == 0) - { - out->requires.insert(mcReq); - } - } - if (root.contains("conflicts")) - { - Meta::parseRequires(root, &out->conflicts); - } - if (root.contains("volatile")) - { - out->m_volatile = requireBoolean(root, "volatile"); - } - - /* removed features that shouldn't be used */ - if (root.contains("tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); - } - if (root.contains("-libraries")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); - } - if (root.contains("-tweakers")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); - } - if (root.contains("-minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); - } - if (root.contains("+minecraftArguments")) - { - out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); - } - return out; + VersionFilePtr out(new VersionFile()); + if (doc.isEmpty() || doc.isNull()) + { + throw JSONValidationError(filename + " is empty or null"); + } + if (!doc.isObject()) + { + throw JSONValidationError(filename + " is not an object"); + } + + QJsonObject root = doc.object(); + + Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false); + switch(formatVersion) + { + case Meta::MetadataVersion::InitialRelease: + break; + case Meta::MetadataVersion::Invalid: + throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format."); + } + + if (requireOrder) + { + if (root.contains("order")) + { + out->order = requireInteger(root.value("order")); + } + else + { + // FIXME: evaluate if we don't want to throw exceptions here instead + qCritical() << filename << "doesn't contain an order field"; + } + } + + out->name = root.value("name").toString(); + + if(root.contains("uid")) + { + out->uid = root.value("uid").toString(); + } + else + { + out->uid = root.value("fileId").toString(); + } + + out->version = root.value("version").toString(); + + MojangVersionFormat::readVersionProperties(root, out.get()); + + // added for legacy Minecraft window embedding, TODO: remove + readString(root, "appletClass", out->appletClass); + + if (root.contains("+tweakers")) + { + for (auto tweakerVal : requireArray(root.value("+tweakers"))) + { + out->addTweakers.append(requireString(tweakerVal)); + } + } + + if (root.contains("+traits")) + { + for (auto tweakerVal : requireArray(root.value("+traits"))) + { + out->traits.insert(requireString(tweakerVal)); + } + } + + + if (root.contains("jarMods")) + { + for (auto libVal : requireArray(root.value("jarMods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename); + // and add to jar mods + out->jarMods.append(lib); + } + } + else if (root.contains("+jarMods")) // DEPRECATED: old style '+jarMods' are only here for backwards compatibility + { + for (auto libVal : requireArray(root.value("+jarMods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name); + // and add to jar mods + out->jarMods.append(lib); + } + } + + if (root.contains("mods")) + { + for (auto libVal : requireArray(root.value("mods"))) + { + QJsonObject libObj = requireObject(libVal); + // parse the jarmod + auto lib = OneSixVersionFormat::modFromJson(libObj, filename); + // and add to jar mods + out->mods.append(lib); + } + } + + auto readLibs = [&](const char * which) + { + for (auto libVal : requireArray(root.value(which))) + { + QJsonObject libObj = requireObject(libVal); + // parse the library + auto lib = libraryFromJson(libObj, filename); + out->libraries.append(lib); + } + }; + bool hasPlusLibs = root.contains("+libraries"); + bool hasLibs = root.contains("libraries"); + if (hasPlusLibs && hasLibs) + { + out->addProblem(ProblemSeverity::Warning, + QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported.")); + readLibs("libraries"); + readLibs("+libraries"); + } + else if (hasLibs) + { + readLibs("libraries"); + } + else if(hasPlusLibs) + { + readLibs("+libraries"); + } + + // if we have mainJar, just use it + if(root.contains("mainJar")) + { + QJsonObject libObj = requireObject(root, "mainJar"); + out->mainJar = libraryFromJson(libObj, filename); + } + // else reconstruct it from downloads and id ... if that's available + else if(!out->minecraftVersion.isEmpty()) + { + auto lib = std::make_shared(); + lib->setRawName(GradleSpecifier(QString("com.mojang:minecraft:%1:client").arg(out->minecraftVersion))); + // we have a reliable client download, use it. + if(out->mojangDownloads.contains("client")) + { + auto LibDLInfo = std::make_shared(); + LibDLInfo->artifact = out->mojangDownloads["client"]; + lib->setMojangDownloadInfo(LibDLInfo); + } + // we got nothing... guess based on ancient hardcoded Mojang behaviour + // FIXME: this will eventually break... + else + { + lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion)); + } + out->mainJar = lib; + } + + if (root.contains("requires")) + { + Meta::parseRequires(root, &out->requires); + } + QString dependsOnMinecraftVersion = root.value("mcVersion").toString(); + if(!dependsOnMinecraftVersion.isEmpty()) + { + Meta::Require mcReq; + mcReq.uid = "net.minecraft"; + mcReq.equalsVersion = dependsOnMinecraftVersion; + if (out->requires.count(mcReq) == 0) + { + out->requires.insert(mcReq); + } + } + if (root.contains("conflicts")) + { + Meta::parseRequires(root, &out->conflicts); + } + if (root.contains("volatile")) + { + out->m_volatile = requireBoolean(root, "volatile"); + } + + /* removed features that shouldn't be used */ + if (root.contains("tweakers")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element 'tweakers'")); + } + if (root.contains("-libraries")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-libraries'")); + } + if (root.contains("-tweakers")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-tweakers'")); + } + if (root.contains("-minecraftArguments")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '-minecraftArguments'")); + } + if (root.contains("+minecraftArguments")) + { + out->addProblem(ProblemSeverity::Error, QObject::tr("Version file contains unsupported element '+minecraftArguments'")); + } + return out; } QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch) { - QJsonObject root; - writeString(root, "name", patch->name); - - writeString(root, "uid", patch->uid); - - writeString(root, "version", patch->version); - - Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); - - MojangVersionFormat::writeVersionProperties(patch.get(), root); - - if(patch->mainJar) - { - root.insert("mainJar", libraryToJson(patch->mainJar.get())); - } - writeString(root, "appletClass", patch->appletClass); - writeStringList(root, "+tweakers", patch->addTweakers); - writeStringList(root, "+traits", patch->traits.toList()); - if (!patch->libraries.isEmpty()) - { - QJsonArray array; - for (auto value: patch->libraries) - { - array.append(OneSixVersionFormat::libraryToJson(value.get())); - } - root.insert("libraries", array); - } - if (!patch->jarMods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::jarModtoJson(value.get())); - } - root.insert("jarMods", array); - } - if (!patch->mods.isEmpty()) - { - QJsonArray array; - for (auto value: patch->jarMods) - { - array.append(OneSixVersionFormat::modtoJson(value.get())); - } - root.insert("mods", array); - } - if(!patch->requires.empty()) - { - Meta::serializeRequires(root, &patch->requires, "requires"); - } - if(!patch->conflicts.empty()) - { - Meta::serializeRequires(root, &patch->conflicts, "conflicts"); - } - if(patch->m_volatile) - { - root.insert("volatile", true); - } - // write the contents to a json document. - { - QJsonDocument out; - out.setObject(root); - return out; - } + QJsonObject root; + writeString(root, "name", patch->name); + + writeString(root, "uid", patch->uid); + + writeString(root, "version", patch->version); + + Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease); + + MojangVersionFormat::writeVersionProperties(patch.get(), root); + + if(patch->mainJar) + { + root.insert("mainJar", libraryToJson(patch->mainJar.get())); + } + writeString(root, "appletClass", patch->appletClass); + writeStringList(root, "+tweakers", patch->addTweakers); + writeStringList(root, "+traits", patch->traits.toList()); + if (!patch->libraries.isEmpty()) + { + QJsonArray array; + for (auto value: patch->libraries) + { + array.append(OneSixVersionFormat::libraryToJson(value.get())); + } + root.insert("libraries", array); + } + if (!patch->jarMods.isEmpty()) + { + QJsonArray array; + for (auto value: patch->jarMods) + { + array.append(OneSixVersionFormat::jarModtoJson(value.get())); + } + root.insert("jarMods", array); + } + if (!patch->mods.isEmpty()) + { + QJsonArray array; + for (auto value: patch->jarMods) + { + array.append(OneSixVersionFormat::modtoJson(value.get())); + } + root.insert("mods", array); + } + if(!patch->requires.empty()) + { + Meta::serializeRequires(root, &patch->requires, "requires"); + } + if(!patch->conflicts.empty()) + { + Meta::serializeRequires(root, &patch->conflicts, "conflicts"); + } + if(patch->m_volatile) + { + root.insert("volatile", true); + } + // write the contents to a json document. + { + QJsonDocument out; + out.setObject(root); + return out; + } } LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName) { - LibraryPtr out(new Library()); - if (!libObj.contains("name")) - { - throw JSONValidationError(filename + - "contains a jarmod that doesn't have a 'name' field"); - } - - // just make up something unique on the spot for the library name. - auto uuid = QUuid::createUuid(); - QString id = uuid.toString().remove('{').remove('}'); - out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); - - // filename override is the old name - out->setFilename(libObj.value("name").toString()); - - // it needs to be local, it is stored in the instance jarmods folder - out->setHint("local"); - - // read the original name if present - some versions did not set it - // it is the original jar mod filename before it got renamed at the point of addition - auto displayName = libObj.value("originalName").toString(); - if(displayName.isEmpty()) - { - auto fixed = originalName; - fixed.remove(" (jar mod)"); - out->setDisplayName(fixed); - } - else - { - out->setDisplayName(displayName); - } - return out; + LibraryPtr out(new Library()); + if (!libObj.contains("name")) + { + throw JSONValidationError(filename + + "contains a jarmod that doesn't have a 'name' field"); + } + + // just make up something unique on the spot for the library name. + auto uuid = QUuid::createUuid(); + QString id = uuid.toString().remove('{').remove('}'); + out->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1")); + + // filename override is the old name + out->setFilename(libObj.value("name").toString()); + + // it needs to be local, it is stored in the instance jarmods folder + out->setHint("local"); + + // read the original name if present - some versions did not set it + // it is the original jar mod filename before it got renamed at the point of addition + auto displayName = libObj.value("originalName").toString(); + if(displayName.isEmpty()) + { + auto fixed = originalName; + fixed.remove(" (jar mod)"); + out->setDisplayName(fixed); + } + else + { + out->setDisplayName(displayName); + } + return out; } LibraryPtr OneSixVersionFormat::jarModFromJson(const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(libObj, filename); } QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod) { - return libraryToJson(jarmod); + return libraryToJson(jarmod); } LibraryPtr OneSixVersionFormat::modFromJson(const QJsonObject& libObj, const QString& filename) { - return libraryFromJson(libObj, filename); + return libraryFromJson(libObj, filename); } QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod) { - return libraryToJson(jarmod); + return libraryToJson(jarmod); } diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h index 8b782db0..6bbebca0 100644 --- a/api/logic/minecraft/OneSixVersionFormat.h +++ b/api/logic/minecraft/OneSixVersionFormat.h @@ -8,22 +8,22 @@ class OneSixVersionFormat { public: - // version files / profile patches - static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); - static QJsonDocument versionFileToJson(const VersionFilePtr &patch); + // version files / profile patches + static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder); + static QJsonDocument versionFileToJson(const VersionFilePtr &patch); - // libraries - static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject libraryToJson(Library *library); + // libraries + static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject libraryToJson(Library *library); - // DEPRECATED: old 'plus' jar mods generated by the application - static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); + // DEPRECATED: old 'plus' jar mods generated by the application + static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName); - // new jar mods derived from libraries - static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject jarModtoJson(Library * jarmod); + // new jar mods derived from libraries + static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject jarModtoJson(Library * jarmod); - // mods, also derived from libraries - static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename); - static QJsonObject modtoJson(Library * jarmod); + // mods, also derived from libraries + static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename); + static QJsonObject modtoJson(Library * jarmod); }; diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp index 2165fa7f..5e1e6c2b 100644 --- a/api/logic/minecraft/OpSys.cpp +++ b/api/logic/minecraft/OpSys.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,26 @@ 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; + 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"; - } + 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/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h index e44b1e07..58852acb 100644 --- a/api/logic/minecraft/OpSys.h +++ b/api/logic/minecraft/OpSys.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ #include enum OpSys { - Os_Windows, - Os_Linux, - Os_OSX, - Os_Other + Os_Windows, + Os_Linux, + Os_OSX, + Os_Other }; OpSys OpSys_fromString(QString); diff --git a/api/logic/minecraft/ParseUtils.cpp b/api/logic/minecraft/ParseUtils.cpp index ca188432..c9640e77 100644 --- a/api/logic/minecraft/ParseUtils.cpp +++ b/api/logic/minecraft/ParseUtils.cpp @@ -6,29 +6,29 @@ QDateTime timeFromS3Time(QString str) { - return QDateTime::fromString(str, Qt::ISODate); + return QDateTime::fromString(str, Qt::ISODate); } QString timeToS3Time(QDateTime time) { - // this all because Qt can't format timestamps right. - int offsetRaw = time.offsetFromUtc(); - bool negative = offsetRaw < 0; - int offsetAbs = std::abs(offsetRaw); + // this all because Qt can't format timestamps right. + int offsetRaw = time.offsetFromUtc(); + bool negative = offsetRaw < 0; + int offsetAbs = std::abs(offsetRaw); - int offsetSeconds = offsetAbs % 60; - offsetAbs -= offsetSeconds; + int offsetSeconds = offsetAbs % 60; + offsetAbs -= offsetSeconds; - int offsetMinutes = offsetAbs % 3600; - offsetAbs -= offsetMinutes; - offsetMinutes /= 60; - - int offsetHours = offsetAbs / 3600; + int offsetMinutes = offsetAbs % 3600; + offsetAbs -= offsetMinutes; + offsetMinutes /= 60; + + int offsetHours = offsetAbs / 3600; - QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); - raw += (negative ? QChar('-') : QChar('+')); - raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); - raw += ":"; - raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); - return raw; + QString raw = time.toString("yyyy-MM-ddTHH:mm:ss"); + raw += (negative ? QChar('-') : QChar('+')); + raw += QString("%1").arg(offsetHours, 2, 10, QChar('0')); + raw += ":"; + raw += QString("%1").arg(offsetMinutes, 2, 10, QChar('0')); + return raw; } diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp index 1ce96248..fcc137e5 100644 --- a/api/logic/minecraft/ParseUtils_test.cpp +++ b/api/logic/minecraft/ParseUtils_test.cpp @@ -5,37 +5,37 @@ class ParseUtilsTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void test_Through_data() - { - QTest::addColumn("timestamp"); - const char * timestamps[] = - { - "2016-02-29T13:49:54+01:00", - "2016-02-26T15:21:11+00:01", - "2016-02-24T15:52:36+01:13", - "2016-02-18T17:41:00+00:00", - "2016-02-17T15:23:19+00:00", - "2016-02-16T15:22:39+09:22", - "2016-02-10T15:06:41+00:00", - "2016-02-04T15:28:02-05:33" - }; - for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) - { - QTest::newRow(timestamps[i]) << QString(timestamps[i]); - } - } - void test_Through() - { - QFETCH(QString, timestamp); - - auto time_parsed = timeFromS3Time(timestamp); - auto time_serialized = timeToS3Time(time_parsed); - - QCOMPARE(time_serialized, timestamp); - } + void test_Through_data() + { + QTest::addColumn("timestamp"); + const char * timestamps[] = + { + "2016-02-29T13:49:54+01:00", + "2016-02-26T15:21:11+00:01", + "2016-02-24T15:52:36+01:13", + "2016-02-18T17:41:00+00:00", + "2016-02-17T15:23:19+00:00", + "2016-02-16T15:22:39+09:22", + "2016-02-10T15:06:41+00:00", + "2016-02-04T15:28:02-05:33" + }; + for(unsigned i = 0; i < (sizeof(timestamps) / sizeof(const char *)); i++) + { + QTest::newRow(timestamps[i]) << QString(timestamps[i]); + } + } + void test_Through() + { + QFETCH(QString, timestamp); + + auto time_parsed = timeFromS3Time(timestamp); + auto time_serialized = timeToS3Time(time_parsed); + + QCOMPARE(time_serialized, timestamp); + } }; diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp index a6d2028d..8ca24cc8 100644 --- a/api/logic/minecraft/ProfileUtils.cpp +++ b/api/logic/minecraft/ProfileUtils.cpp @@ -16,163 +16,163 @@ static const int currentOrderFileVersion = 1; bool readOverrideOrders(QString path, PatchOrder &order) { - QFile orderFile(path); - if (!orderFile.exists()) - { - qWarning() << "Order file doesn't exist. Ignoring."; - return false; - } - if (!orderFile.open(QFile::ReadOnly)) - { - qCritical() << "Couldn't open" << orderFile.fileName() - << " for reading:" << orderFile.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } + QFile orderFile(path); + if (!orderFile.exists()) + { + qWarning() << "Order file doesn't exist. Ignoring."; + return false; + } + if (!orderFile.open(QFile::ReadOnly)) + { + qCritical() << "Couldn't open" << orderFile.fileName() + << " for reading:" << orderFile.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } - // and it's valid JSON - QJsonParseError error; - QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overriden order"; - return false; - } + // and it's valid JSON + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(orderFile.readAll(), &error); + if (error.error != QJsonParseError::NoError) + { + qCritical() << "Couldn't parse" << orderFile.fileName() << ":" << error.errorString(); + qWarning() << "Ignoring overriden order"; + return false; + } - // and then read it and process it if all above is true. - try - { - auto obj = Json::requireObject(doc); - // check order file version. - auto version = Json::requireInteger(obj.value("version")); - if (version != currentOrderFileVersion) - { - throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") - .arg(currentOrderFileVersion)); - } - auto orderArray = Json::requireArray(obj.value("order")); - for(auto item: orderArray) - { - order.append(Json::requireString(item)); - } - } - catch (JSONValidationError &err) - { - qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; - qWarning() << "Ignoring overriden order"; - order.clear(); - return false; - } - return true; + // and then read it and process it if all above is true. + try + { + auto obj = Json::requireObject(doc); + // check order file version. + auto version = Json::requireInteger(obj.value("version")); + if (version != currentOrderFileVersion) + { + throw JSONValidationError(QObject::tr("Invalid order file version, expected %1") + .arg(currentOrderFileVersion)); + } + auto orderArray = Json::requireArray(obj.value("order")); + for(auto item: orderArray) + { + order.append(Json::requireString(item)); + } + } + catch (const JSONValidationError &err) + { + qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format"; + qWarning() << "Ignoring overriden order"; + order.clear(); + return false; + } + return true; } static VersionFilePtr createErrorVersionFile(QString fileId, QString filepath, QString error) { - auto outError = std::make_shared(); - outError->uid = outError->name = fileId; - // outError->filename = filepath; - outError->addProblem(ProblemSeverity::Error, error); - return outError; + auto outError = std::make_shared(); + outError->uid = outError->name = fileId; + // outError->filename = filepath; + outError->addProblem(ProblemSeverity::Error, error); + return outError; } static VersionFilePtr guardedParseJson(const QJsonDocument & doc,const QString &fileId,const QString &filepath,const bool &requireOrder) { - try - { - return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); - } - catch (Exception & e) - { - return createErrorVersionFile(fileId, filepath, e.cause()); - } + try + { + return OneSixVersionFormat::versionFileFromJson(doc, filepath, requireOrder); + } + catch (const Exception &e) + { + return createErrorVersionFile(fileId, filepath, e.cause()); + } } VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder) { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonParseError error; - auto data = file.readAll(); - QJsonDocument doc = QJsonDocument::fromJson(data, &error); - file.close(); - if (error.error != QJsonParseError::NoError) - { - int line = 1; - int column = 0; - for(int i = 0; i < error.offset; i++) - { - if(data[i] == '\n') - { - line++; - column = 0; - continue; - } - column++; - } - auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") - .arg(fileInfo.fileName(), error.errorString()) - .arg(line).arg(column); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + QJsonParseError error; + auto data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data, &error); + file.close(); + if (error.error != QJsonParseError::NoError) + { + int line = 1; + int column = 0; + for(int i = 0; i < error.offset; i++) + { + if(data[i] == '\n') + { + line++; + column = 0; + continue; + } + column++; + } + auto errorStr = QObject::tr("Unable to process the version file %1: %2 at line %3 column %4.") + .arg(fileInfo.fileName(), error.errorString()) + .arg(line).arg(column); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder); } bool saveJsonFile(const QJsonDocument doc, const QString & filename) { - auto data = doc.toJson(); - QSaveFile jsonFile(filename); - if(!jsonFile.open(QIODevice::WriteOnly)) - { - jsonFile.cancelWriting(); - qWarning() << "Couldn't open" << filename << "for writing"; - return false; - } - jsonFile.write(data); - if(!jsonFile.commit()) - { - qWarning() << "Couldn't save" << filename; - return false; - } - return true; + auto data = doc.toJson(); + QSaveFile jsonFile(filename); + if(!jsonFile.open(QIODevice::WriteOnly)) + { + jsonFile.cancelWriting(); + qWarning() << "Couldn't open" << filename << "for writing"; + return false; + } + jsonFile.write(data); + if(!jsonFile.commit()) + { + qWarning() << "Couldn't save" << filename; + return false; + } + return true; } VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo) { - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QFile::ReadOnly)) - { - auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); - return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); - } - QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); - file.close(); - if (doc.isNull()) - { - file.remove(); - throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); - } - return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) + { + auto errorStr = QObject::tr("Unable to open the version file %1: %2.").arg(fileInfo.fileName(), file.errorString()); + return createErrorVersionFile(fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), errorStr); + } + QJsonDocument doc = QJsonDocument::fromBinaryData(file.readAll()); + file.close(); + if (doc.isNull()) + { + file.remove(); + throw JSONValidationError(QObject::tr("Unable to process the version file %1.").arg(fileInfo.fileName())); + } + return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), false); } void removeLwjglFromPatch(VersionFilePtr patch) { - auto filter = [](QList& libs) - { - QList filteredLibs; - for (auto lib : libs) - { - if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) - { - filteredLibs.append(lib); - } - } - libs = filteredLibs; - }; - filter(patch->libraries); + auto filter = [](QList& libs) + { + QList filteredLibs; + for (auto lib : libs) + { + if (!g_VersionFilterData.lwjglWhitelist.contains(lib->artifactPrefix())) + { + filteredLibs.append(lib); + } + } + libs = filteredLibs; + }; + filter(patch->libraries); } } diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp index 43d673c2..cd10504f 100644 --- a/api/logic/minecraft/Rule.cpp +++ b/api/logic/minecraft/Rule.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,74 +20,74 @@ RuleAction RuleAction_fromString(QString name) { - if (name == "allow") - return Allow; - if (name == "disallow") - return Disallow; - return Defer; + if (name == "allow") + return Allow; + if (name == "disallow") + return Disallow; + return Defer; } QList> rulesFromJsonV4(const QJsonObject &objectWithRules) { - QList> rules; - auto rulesVal = objectWithRules.value("rules"); - if (!rulesVal.isArray()) - return rules; + 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; + 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 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)); - } + 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 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)); - if(!m_version_regexp.isEmpty()) - { - osObj.insert("version", m_version_regexp); - } - } - ruleObj.insert("os", osObj); - return ruleObj; + QJsonObject ruleObj; + ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); + QJsonObject osObj; + { + osObj.insert("name", OpSys_toString(m_system)); + if(!m_version_regexp.isEmpty()) + { + osObj.insert("version", m_version_regexp); + } + } + ruleObj.insert("os", osObj); + return ruleObj; } diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h index dc1eee0b..ef10a6c3 100644 --- a/api/logic/minecraft/Rule.h +++ b/api/logic/minecraft/Rule.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ class Rule; enum RuleAction { - Allow, - Disallow, - Defer + Allow, + Disallow, + Defer }; QList> rulesFromJsonV4(const QJsonObject &objectWithRules); @@ -36,66 +36,66 @@ QList> rulesFromJsonV4(const QJsonObject &objectWithRules) class Rule { protected: - RuleAction m_result; - virtual bool applies(const Library *parent) = 0; + RuleAction m_result; + virtual bool applies(const Library *parent) = 0; public: - Rule(RuleAction result) : m_result(result) - { - } - virtual ~Rule() {}; - virtual QJsonObject toJson() = 0; - RuleAction apply(const Library *parent) - { - if (applies(parent)) - return m_result; - else - return Defer; - } + Rule(RuleAction result) : m_result(result) + { + } + virtual ~Rule() {}; + virtual QJsonObject toJson() = 0; + RuleAction apply(const Library *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; + // the OS + OpSys m_system; + // the OS version regexp + QString m_version_regexp; protected: - virtual bool applies(const Library *) - { - return (m_system == currentSystem); - } - OsRule(RuleAction result, OpSys system, QString version_regexp) - : Rule(result), m_system(system), m_version_regexp(version_regexp) - { - } + virtual bool applies(const Library *) + { + 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)); - } + 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(const Library *) - { - return true; - } - ImplicitRule(RuleAction result) : Rule(result) - { - } + virtual bool applies(const Library *) + { + 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)); - } + virtual QJsonObject toJson(); + static std::shared_ptr create(RuleAction result) + { + return std::shared_ptr(new ImplicitRule(result)); + } }; diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/SkinUpload.cpp index bd246139..83bdf592 100644 --- a/api/logic/minecraft/SkinUpload.cpp +++ b/api/logic/minecraft/SkinUpload.cpp @@ -4,66 +4,66 @@ #include QByteArray getModelString(SkinUpload::Model model) { - switch (model) { - case SkinUpload::STEVE: - return ""; - case SkinUpload::ALEX: - return "slim"; - default: - qDebug() << "Unknown skin type!"; - return ""; - } + switch (model) { + case SkinUpload::STEVE: + return ""; + case SkinUpload::ALEX: + return "slim"; + default: + qDebug() << "Unknown skin type!"; + return ""; + } } SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, SkinUpload::Model model) - : Task(parent), m_model(model), m_skin(skin), m_session(session) + : Task(parent), m_model(model), m_skin(skin), m_session(session) { } void SkinUpload::executeTask() { - QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid))); - request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit()); + QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid))); + request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit()); - QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart model; - model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\"")); - model.setBody(getModelString(m_model)); + QHttpPart model; + model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\"")); + model.setBody(getModelString(m_model)); - QHttpPart skin; - skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); - skin.setHeader(QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); - skin.setBody(m_skin); + QHttpPart skin; + skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png")); + skin.setHeader(QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=\"file\"; filename=\"skin.png\"")); + skin.setBody(m_skin); - multiPart->append(model); - multiPart->append(skin); + multiPart->append(model); + multiPart->append(skin); - QNetworkReply *rep = ENV.qnam().put(request, multiPart); - m_reply = std::shared_ptr(rep); + QNetworkReply *rep = ENV.qnam().put(request, multiPart); + m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading skin")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + setStatus(tr("Uploading skin")); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } void SkinUpload::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + // error happened during download. + qCritical() << "Network error: " << error; + emitFailed(m_reply->errorString()); } void SkinUpload::downloadFinished() { - // if the download failed - if (m_reply->error() != QNetworkReply::NetworkError::NoError) - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + // if the download failed + if (m_reply->error() != QNetworkReply::NetworkError::NoError) + { + emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + m_reply.reset(); + return; + } + emitSucceeded(); } diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/SkinUpload.h index 5b331fa9..c77abb03 100644 --- a/api/logic/minecraft/SkinUpload.h +++ b/api/logic/minecraft/SkinUpload.h @@ -11,30 +11,29 @@ typedef std::shared_ptr SkinUploadPtr; class MULTIMC_LOGIC_EXPORT SkinUpload : public Task { - Q_OBJECT + Q_OBJECT public: - enum Model - { - STEVE, - ALEX - }; + enum Model + { + STEVE, + ALEX + }; - // Note this class takes ownership of the file. - SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); - - virtual ~SkinUpload() {} + // Note this class takes ownership of the file. + SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin, Model model = STEVE); + virtual ~SkinUpload() {} private: - Model m_model; - QByteArray m_skin; - AuthSessionPtr m_session; - std::shared_ptr m_reply; + Model m_model; + QByteArray m_skin; + AuthSessionPtr m_session; + std::shared_ptr m_reply; protected: - virtual void executeTask(); + virtual void executeTask(); public slots: - void downloadError(QNetworkReply::NetworkError); + void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); + void downloadFinished(); }; diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp index 9f485c55..cfb9e504 100644 --- a/api/logic/minecraft/VersionFile.cpp +++ b/api/logic/minecraft/VersionFile.cpp @@ -12,45 +12,45 @@ static bool isMinecraftVersion(const QString &uid) { - return uid == "net.minecraft"; + return uid == "net.minecraft"; } void VersionFile::applyTo(LaunchProfile *profile) { - // Only real Minecraft can set those. Don't let anything override them. - if (isMinecraftVersion(uid)) - { - profile->applyMinecraftVersion(minecraftVersion); - profile->applyMinecraftVersionType(type); - // HACK: ignore assets from other version files than Minecraft - // workaround for stupid assets issue caused by amazon: - // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ - profile->applyMinecraftAssets(mojangAssetIndex); - } - - profile->applyMainJar(mainJar); - profile->applyMainClass(mainClass); - profile->applyAppletClass(appletClass); - profile->applyMinecraftArguments(minecraftArguments); - profile->applyTweakers(addTweakers); - profile->applyJarMods(jarMods); - profile->applyMods(mods); - profile->applyTraits(traits); - - for (auto library : libraries) - { - profile->applyLibrary(library); - } - profile->applyProblemSeverity(getProblemSeverity()); + // Only real Minecraft can set those. Don't let anything override them. + if (isMinecraftVersion(uid)) + { + profile->applyMinecraftVersion(minecraftVersion); + profile->applyMinecraftVersionType(type); + // HACK: ignore assets from other version files than Minecraft + // workaround for stupid assets issue caused by amazon: + // https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/ + profile->applyMinecraftAssets(mojangAssetIndex); + } + + profile->applyMainJar(mainJar); + profile->applyMainClass(mainClass); + profile->applyAppletClass(appletClass); + profile->applyMinecraftArguments(minecraftArguments); + profile->applyTweakers(addTweakers); + profile->applyJarMods(jarMods); + profile->applyMods(mods); + profile->applyTraits(traits); + + for (auto library : libraries) + { + profile->applyLibrary(library); + } + profile->applyProblemSeverity(getProblemSeverity()); } /* - auto theirVersion = profile->getMinecraftVersion(); - if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) - { - if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) - { - throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); - } - } + auto theirVersion = profile->getMinecraftVersion(); + if (!theirVersion.isNull() && !dependsOnMinecraftVersion.isNull()) + { + if (QRegExp(dependsOnMinecraftVersion, Qt::CaseInsensitive, QRegExp::Wildcard).indexIn(theirVersion) == -1) + { + throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion); + } + } */ \ No newline at end of file diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h index 5aea7a7a..3dc9db96 100644 --- a/api/logic/minecraft/VersionFile.h +++ b/api/logic/minecraft/VersionFile.h @@ -21,91 +21,91 @@ struct MojangAssetIndexInfo; using VersionFilePtr = std::shared_ptr; class VersionFile : public ProblemContainer { - friend class MojangVersionFormat; - friend class OneSixVersionFormat; + friend class MojangVersionFormat; + friend class OneSixVersionFormat; public: /* methods */ - void applyTo(LaunchProfile* profile); + void applyTo(LaunchProfile* profile); public: /* data */ - /// MultiMC: order hint for this version file if no explicit order is set - int order = 0; + /// MultiMC: order hint for this version file if no explicit order is set + int order = 0; - /// MultiMC: human readable name of this package - QString name; + /// MultiMC: human readable name of this package + QString name; - /// MultiMC: package ID of this package - QString uid; + /// MultiMC: package ID of this package + QString uid; - /// MultiMC: version of this package - QString version; + /// MultiMC: version of this package + QString version; - /// MultiMC: DEPRECATED dependency on a Minecraft version - QString dependsOnMinecraftVersion; + /// MultiMC: DEPRECATED dependency on a Minecraft version + QString dependsOnMinecraftVersion; - /// Mojang: DEPRECATED used to version the Mojang version format - int minimumLauncherVersion = -1; + /// Mojang: DEPRECATED used to version the Mojang version format + int minimumLauncherVersion = -1; - /// Mojang: DEPRECATED version of Minecraft this is - QString minecraftVersion; + /// Mojang: DEPRECATED version of Minecraft this is + QString minecraftVersion; - /// Mojang: class to launch Minecraft with - QString mainClass; + /// Mojang: class to launch Minecraft with + QString mainClass; - /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) - QString appletClass; + /// MultiMC: class to launch legacy Minecraft with (embed in a custom window) + QString appletClass; - /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) - QString minecraftArguments; + /// Mojang: Minecraft launch arguments (may contain placeholders for variable substitution) + QString minecraftArguments; - /// Mojang: type of the Minecraft version - QString type; + /// Mojang: type of the Minecraft version + QString type; - /// Mojang: the time this version was actually released by Mojang - QDateTime releaseTime; + /// Mojang: the time this version was actually released by Mojang + QDateTime releaseTime; - /// Mojang: DEPRECATED the time this version was last updated by Mojang - QDateTime updateTime; + /// Mojang: DEPRECATED the time this version was last updated by Mojang + QDateTime updateTime; - /// Mojang: DEPRECATED asset group to be used with Minecraft - QString assets; + /// Mojang: DEPRECATED asset group to be used with Minecraft + QString assets; - /// MultiMC: list of tweaker mod arguments for launchwrapper - QStringList addTweakers; + /// MultiMC: list of tweaker mod arguments for launchwrapper + QStringList addTweakers; - /// Mojang: list of libraries to add to the version - QList libraries; + /// Mojang: list of libraries to add to the version + QList libraries; - /// The main jar (Minecraft version library, normally) - LibraryPtr mainJar; + /// The main jar (Minecraft version library, normally) + LibraryPtr mainJar; - /// MultiMC: list of attached traits of this version file - used to enable features - QSet traits; + /// MultiMC: list of attached traits of this version file - used to enable features + QSet traits; - /// MultiMC: list of jar mods added to this version - QList jarMods; + /// MultiMC: list of jar mods added to this version + QList jarMods; - /// MultiMC: list of mods added to this version - QList mods; + /// MultiMC: list of mods added to this version + QList mods; - /** - * MultiMC: set of packages this depends on - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet requires; + /** + * MultiMC: set of packages this depends on + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet requires; - /** - * MultiMC: set of packages this conflicts with - * NOTE: this is shared with the meta format!!! - */ - Meta::RequireSet conflicts; + /** + * MultiMC: set of packages this conflicts with + * NOTE: this is shared with the meta format!!! + */ + Meta::RequireSet conflicts; - /// is volatile -- may be removed as soon as it is no longer needed by something else - bool m_volatile = false; + /// is volatile -- may be removed as soon as it is no longer needed by something else + bool m_volatile = false; public: - // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. - QMap > mojangDownloads; + // Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more. + QMap > mojangDownloads; - // Mojang: extended asset index download information - std::shared_ptr mojangAssetIndex; + // Mojang: extended asset index download information + std::shared_ptr mojangAssetIndex; }; diff --git a/api/logic/minecraft/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp index 5f4ceee6..11f7eea9 100644 --- a/api/logic/minecraft/VersionFilterData.cpp +++ b/api/logic/minecraft/VersionFilterData.cpp @@ -5,64 +5,64 @@ VersionFilterData g_VersionFilterData = VersionFilterData(); VersionFilterData::VersionFilterData() { - // 1.3.* - auto libs13 = - QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; + // 1.3.* + auto libs13 = + QList{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}}; - fmlLibsMapping["1.3.2"] = libs13; + fmlLibsMapping["1.3.2"] = libs13; - // 1.4.* - auto libs14 = QList{ - {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, - {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, - {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, - {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; + // 1.4.* + auto libs14 = QList{ + {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false}, + {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false}, + {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}, + {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}}; - fmlLibsMapping["1.4"] = libs14; - fmlLibsMapping["1.4.1"] = libs14; - fmlLibsMapping["1.4.2"] = libs14; - fmlLibsMapping["1.4.3"] = libs14; - fmlLibsMapping["1.4.4"] = libs14; - fmlLibsMapping["1.4.5"] = libs14; - fmlLibsMapping["1.4.6"] = libs14; - fmlLibsMapping["1.4.7"] = libs14; + fmlLibsMapping["1.4"] = libs14; + fmlLibsMapping["1.4.1"] = libs14; + fmlLibsMapping["1.4.2"] = libs14; + fmlLibsMapping["1.4.3"] = libs14; + fmlLibsMapping["1.4.4"] = libs14; + fmlLibsMapping["1.4.5"] = libs14; + fmlLibsMapping["1.4.6"] = libs14; + fmlLibsMapping["1.4.7"] = libs14; - // 1.5 - fmlLibsMapping["1.5"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5 + fmlLibsMapping["1.5"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // 1.5.1 - fmlLibsMapping["1.5.1"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5.1 + fmlLibsMapping["1.5.1"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // 1.5.2 - fmlLibsMapping["1.5.2"] = QList{ - {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, - {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, - {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, - {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, - {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, - {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; + // 1.5.2 + fmlLibsMapping["1.5.2"] = QList{ + {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false}, + {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false}, + {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false}, + {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true}, + {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false}, + {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}}; - // don't use installers for those. - forgeInstallerBlacklist = QSet({"1.5.2"}); + // don't use installers for those. + forgeInstallerBlacklist = QSet({"1.5.2"}); - // FIXME: remove, used for deciding when core mods should display - legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); - lwjglWhitelist = - QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", - "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", - "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; + // FIXME: remove, used for deciding when core mods should display + legacyCutoffDate = timeFromS3Time("2013-06-25T15:08:56+02:00"); + lwjglWhitelist = + QSet{"net.java.jinput:jinput", "net.java.jinput:jinput-platform", + "net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl", + "org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"}; } diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h index 2408e704..88e91f11 100644 --- a/api/logic/minecraft/VersionFilterData.h +++ b/api/logic/minecraft/VersionFilterData.h @@ -8,21 +8,21 @@ struct FMLlib { - QString filename; - QString checksum; - bool ours; + QString filename; + QString checksum; + bool ours; }; struct VersionFilterData { - VersionFilterData(); - // mapping between minecraft versions and FML libraries required - QMap> fmlLibsMapping; - // set of minecraft versions for which using forge installers is blacklisted - QSet forgeInstallerBlacklist; - // no new versions below this date will be accepted from Mojang servers - QDateTime legacyCutoffDate; - // Libraries that belong to LWJGL - QSet lwjglWhitelist; + VersionFilterData(); + // mapping between minecraft versions and FML libraries required + QMap> fmlLibsMapping; + // set of minecraft versions for which using forge installers is blacklisted + QSet forgeInstallerBlacklist; + // no new versions below this date will be accepted from Mojang servers + QDateTime legacyCutoffDate; + // Libraries that belong to LWJGL + QSet lwjglWhitelist; }; extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData; diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp index 68c0a5cc..17dbf4ae 100644 --- a/api/logic/minecraft/World.cpp +++ b/api/logic/minecraft/World.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,356 +30,402 @@ #include #include +#include + +QString gameTypeToString(GameType type) +{ + switch (type) + { + case GameType::Survival: + return QCoreApplication::translate("GameType", "Survival"); + case GameType::Creative: + return QCoreApplication::translate("GameType", "Creative"); + case GameType::Adventure: + return QCoreApplication::translate("GameType", "Adventure"); + case GameType::Spectator: + return QCoreApplication::translate("GameType", "Spectator"); + default: + break; + } + return QObject::tr("Unknown"); +} + std::unique_ptr parseLevelDat(QByteArray data) { - QByteArray output; - if(!GZip::unzip(data, output)) - { - return nullptr; - } - std::istringstream foo(std::string(output.constData(), output.size())); - auto pair = nbt::io::read_compound(foo); + QByteArray output; + if(!GZip::unzip(data, output)) + { + return nullptr; + } + std::istringstream foo(std::string(output.constData(), output.size())); + auto pair = nbt::io::read_compound(foo); - if(pair.first != "") - return nullptr; + if(pair.first != "") + return nullptr; - if(pair.second == nullptr) - return nullptr; + if(pair.second == nullptr) + return nullptr; - return std::move(pair.second); + return std::move(pair.second); } QByteArray serializeLevelDat(nbt::tag_compound * levelInfo) { - std::ostringstream s; - nbt::io::write_tag("", *levelInfo, s); - QByteArray val( s.str().data(), (int) s.str().size() ); - return val; + std::ostringstream s; + nbt::io::write_tag("", *levelInfo, s); + QByteArray val( s.str().data(), (int) s.str().size() ); + return val; } QString getLevelDatFromFS(const QFileInfo &file) { - QDir worldDir(file.filePath()); - if(!file.isDir() || !worldDir.exists("level.dat")) - { - return QString(); - } - return worldDir.absoluteFilePath("level.dat"); + QDir worldDir(file.filePath()); + if(!file.isDir() || !worldDir.exists("level.dat")) + { + return QString(); + } + return worldDir.absoluteFilePath("level.dat"); } QByteArray getLevelDatDataFromFS(const QFileInfo &file) { - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return QByteArray(); - } - QFile f(fullFilePath); - if(!f.open(QIODevice::ReadOnly)) - { - return QByteArray(); - } - return f.readAll(); + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return QByteArray(); + } + QFile f(fullFilePath); + if(!f.open(QIODevice::ReadOnly)) + { + return QByteArray(); + } + return f.readAll(); } bool putLevelDatDataToFS(const QFileInfo &file, QByteArray & data) { - auto fullFilePath = getLevelDatFromFS(file); - if(fullFilePath.isNull()) - { - return false; - } - QSaveFile f(fullFilePath); - if(!f.open(QIODevice::WriteOnly)) - { - return false; - } - QByteArray compressed; - if(!GZip::zip(data, compressed)) - { - return false; - } - if(f.write(compressed) != compressed.size()) - { - f.cancelWriting(); - return false; - } - return f.commit(); + auto fullFilePath = getLevelDatFromFS(file); + if(fullFilePath.isNull()) + { + return false; + } + QSaveFile f(fullFilePath); + if(!f.open(QIODevice::WriteOnly)) + { + return false; + } + QByteArray compressed; + if(!GZip::zip(data, compressed)) + { + return false; + } + if(f.write(compressed) != compressed.size()) + { + f.cancelWriting(); + return false; + } + return f.commit(); } World::World(const QFileInfo &file) { - repath(file); + repath(file); } void World::repath(const QFileInfo &file) { - m_containerFile = file; - m_folderName = file.fileName(); - if(file.isFile() && file.suffix() == "zip") - { - readFromZip(file); - } - else if(file.isDir()) - { - readFromFS(file); - } + m_containerFile = file; + m_folderName = file.fileName(); + if(file.isFile() && file.suffix() == "zip") + { + readFromZip(file); + } + else if(file.isDir()) + { + readFromFS(file); + } } void World::readFromFS(const QFileInfo &file) { - auto bytes = getLevelDatDataFromFS(file); - if(bytes.isEmpty()) - { - is_valid = false; - return; - } - loadFromLevelDat(bytes); - levelDatTime = file.lastModified(); + auto bytes = getLevelDatDataFromFS(file); + if(bytes.isEmpty()) + { + is_valid = false; + return; + } + loadFromLevelDat(bytes); + levelDatTime = file.lastModified(); } void World::readFromZip(const QFileInfo &file) { - QuaZip zip(file.absoluteFilePath()); - is_valid = zip.open(QuaZip::mdUnzip); - if (!is_valid) - { - return; - } - auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); - is_valid = !location.isEmpty(); - if (!is_valid) - { - return; - } - m_containerOffsetPath = location; - QuaZipFile zippedFile(&zip); - // read the install profile - is_valid = zip.setCurrentFile(location + "level.dat"); - if (!is_valid) - { - return; - } - is_valid = zippedFile.open(QIODevice::ReadOnly); - QuaZipFileInfo64 levelDatInfo; - zippedFile.getFileInfo(&levelDatInfo); - auto modTime = levelDatInfo.getNTFSmTime(); - if(!modTime.isValid()) - { - modTime = levelDatInfo.dateTime; - } - levelDatTime = modTime; - if (!is_valid) - { - return; - } - loadFromLevelDat(zippedFile.readAll()); - zippedFile.close(); + QuaZip zip(file.absoluteFilePath()); + is_valid = zip.open(QuaZip::mdUnzip); + if (!is_valid) + { + return; + } + auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat"); + is_valid = !location.isEmpty(); + if (!is_valid) + { + return; + } + m_containerOffsetPath = location; + QuaZipFile zippedFile(&zip); + // read the install profile + is_valid = zip.setCurrentFile(location + "level.dat"); + if (!is_valid) + { + return; + } + is_valid = zippedFile.open(QIODevice::ReadOnly); + QuaZipFileInfo64 levelDatInfo; + zippedFile.getFileInfo(&levelDatInfo); + auto modTime = levelDatInfo.getNTFSmTime(); + if(!modTime.isValid()) + { + modTime = levelDatInfo.dateTime; + } + levelDatTime = modTime; + if (!is_valid) + { + return; + } + loadFromLevelDat(zippedFile.readAll()); + zippedFile.close(); } bool World::install(const QString &to, const QString &name) { - auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); - if(!FS::ensureFolderPathExists(finalPath)) - { - return false; - } - bool ok = false; - if(m_containerFile.isFile()) - { - QuaZip zip(m_containerFile.absoluteFilePath()); - if (!zip.open(QuaZip::mdUnzip)) - { - return false; - } - ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); - } - else if(m_containerFile.isDir()) - { - QString from = m_containerFile.filePath(); - ok = FS::copy(from, finalPath)(); - } - - if(ok && !name.isEmpty() && m_actualName != name) - { - World newWorld(finalPath); - if(newWorld.isValid()) - { - newWorld.rename(name); - } - } - return ok; + auto finalPath = FS::PathCombine(to, FS::DirNameFromString(m_actualName, to)); + if(!FS::ensureFolderPathExists(finalPath)) + { + return false; + } + bool ok = false; + if(m_containerFile.isFile()) + { + QuaZip zip(m_containerFile.absoluteFilePath()); + if (!zip.open(QuaZip::mdUnzip)) + { + return false; + } + ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty(); + } + else if(m_containerFile.isDir()) + { + QString from = m_containerFile.filePath(); + ok = FS::copy(from, finalPath)(); + } + + if(ok && !name.isEmpty() && m_actualName != name) + { + World newWorld(finalPath); + if(newWorld.isValid()) + { + newWorld.rename(name); + } + } + return ok; } bool World::rename(const QString &newName) { - if(m_containerFile.isFile()) - { - return false; - } - - auto data = getLevelDatDataFromFS(m_containerFile); - if(data.isEmpty()) - { - return false; - } - - auto worldData = parseLevelDat(data); - if(!worldData) - { - return false; - } - auto &val = worldData->at("Data"); - if(val.get_type() != nbt::tag_type::Compound) - { - return false; - } - auto &dataCompound = val.as(); - dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); - data = serializeLevelDat(worldData.get()); - - putLevelDatDataToFS(m_containerFile, data); - - m_actualName = newName; - - QDir parentDir(m_containerFile.absoluteFilePath()); - parentDir.cdUp(); - QFile container(m_containerFile.absoluteFilePath()); - auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); - container.rename(parentDir.absoluteFilePath(dirName)); - - return true; + if(m_containerFile.isFile()) + { + return false; + } + + auto data = getLevelDatDataFromFS(m_containerFile); + if(data.isEmpty()) + { + return false; + } + + auto worldData = parseLevelDat(data); + if(!worldData) + { + return false; + } + auto &val = worldData->at("Data"); + if(val.get_type() != nbt::tag_type::Compound) + { + return false; + } + auto &dataCompound = val.as(); + dataCompound.put("LevelName", nbt::value_initializer(newName.toUtf8().data())); + data = serializeLevelDat(worldData.get()); + + putLevelDatDataToFS(m_containerFile, data); + + m_actualName = newName; + + QDir parentDir(m_containerFile.absoluteFilePath()); + parentDir.cdUp(); + QFile container(m_containerFile.absoluteFilePath()); + auto dirName = FS::DirNameFromString(m_actualName, parentDir.absolutePath()); + container.rename(parentDir.absoluteFilePath(dirName)); + + return true; } static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString()) { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::String) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return QString::fromStdString(tag_str.get()); - } - catch(std::out_of_range e) - { - // fallback for old world formats - qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; - } - catch(std::bad_cast e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; - return fallback; - } -}; + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::String) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return QString::fromStdString(tag_str.get()); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback; + return fallback; + } +} static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0) { - try - { - auto &namedValue = parent.at(name); - if(namedValue.get_type() != nbt::tag_type::Long) - { - return fallback; - } - auto & tag_str = namedValue.as(); - return tag_str.get(); - } - catch(std::out_of_range e) - { - // fallback for old world formats - qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; - return fallback; - } - catch(std::bad_cast e) - { - // type mismatch - qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; - return fallback; - } -}; + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Long) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback; + return fallback; + } +} + +static int read_int (nbt::value& parent, const char * name, const int & fallback = 0) +{ + try + { + auto &namedValue = parent.at(name); + if(namedValue.get_type() != nbt::tag_type::Int) + { + return fallback; + } + auto & tag_str = namedValue.as(); + return tag_str.get(); + } + catch (const std::out_of_range &e) + { + // fallback for old world formats + qWarning() << "Int NBT tag" << name << "could not be found. Defaulting to" << fallback; + return fallback; + } + catch (const std::bad_cast &e) + { + // type mismatch + qWarning() << "NBT tag" << name << "could not be converted to int. Defaulting to" << fallback; + return fallback; + } +} void World::loadFromLevelDat(QByteArray data) { - try - { - auto levelData = parseLevelDat(data); - if(!levelData) - { - is_valid = false; - return; - } - - auto &val = levelData->at("Data"); - is_valid = val.get_type() == nbt::tag_type::Compound; - if(!is_valid) - return; - - m_actualName = read_string(val, "LevelName", m_folderName); - - - int64_t temp = read_long(val, "LastPlayed", 0); - if(temp == 0) - { - m_lastPlayed = levelDatTime; - } - else - { - m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp); - } - - m_randomSeed = read_long(val, "RandomSeed", 0); - - qDebug() << "World Name:" << m_actualName; - qDebug() << "Last Played:" << m_lastPlayed.toString(); - qDebug() << "Seed:" << m_randomSeed; - } - catch (nbt::io::input_error e) - { - qWarning() << "Unable to load" << m_folderName << ":" << e.what(); - is_valid = false; - return; - } + try + { + auto levelData = parseLevelDat(data); + if(!levelData) + { + is_valid = false; + return; + } + + auto &val = levelData->at("Data"); + is_valid = val.get_type() == nbt::tag_type::Compound; + if(!is_valid) + return; + + m_actualName = read_string(val, "LevelName", m_folderName); + + + int64_t temp = read_long(val, "LastPlayed", 0); + if(temp == 0) + { + m_lastPlayed = levelDatTime; + } + else + { + m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp); + } + + int GameType_val = read_int(val, "GameType", 0); + m_gameType = (GameType) GameType_val; + + m_randomSeed = read_long(val, "RandomSeed", 0); + + qDebug() << "World Name:" << m_actualName; + qDebug() << "Last Played:" << m_lastPlayed.toString(); + qDebug() << "Seed:" << m_randomSeed; + qDebug() << "GameMode:" << GameType_val; + } + catch (const nbt::io::input_error &e) + { + qWarning() << "Unable to load" << m_folderName << ":" << e.what(); + is_valid = false; + return; + } } bool World::replace(World &with) { - if (!destroy()) - return false; - bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); - if (success) - { - m_folderName = with.m_folderName; - m_containerFile.refresh(); - } - return success; + if (!destroy()) + return false; + bool success = FS::copy(with.m_containerFile.filePath(), m_containerFile.path())(); + if (success) + { + m_folderName = with.m_folderName; + m_containerFile.refresh(); + } + return success; } bool World::destroy() { - if(!is_valid) return false; - if (m_containerFile.isDir()) - { - QDir d(m_containerFile.filePath()); - return d.removeRecursively(); - } - else if(m_containerFile.isFile()) - { - QFile file(m_containerFile.absoluteFilePath()); - return file.remove(); - } - return true; + if(!is_valid) return false; + if (m_containerFile.isDir()) + { + QDir d(m_containerFile.filePath()); + return d.removeRecursively(); + } + else if(m_containerFile.isFile()) + { + QFile file(m_containerFile.absoluteFilePath()); + return file.remove(); + } + return true; } bool World::operator==(const World &other) const { - return is_valid == other.is_valid && folderName() == other.folderName(); -} -bool World::strongCompare(const World &other) const -{ - return is_valid == other.is_valid && folderName() == other.folderName(); + return is_valid == other.is_valid && folderName() == other.folderName(); } diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h index 7087bf48..818701fa 100644 --- a/api/logic/minecraft/World.h +++ b/api/logic/minecraft/World.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,65 +19,78 @@ #include "multimc_logic_export.h" +enum class GameType +{ + Survival, + Creative, + Adventure, + Spectator +}; +QString MULTIMC_LOGIC_EXPORT gameTypeToString(GameType type); + class MULTIMC_LOGIC_EXPORT World { public: - World(const QFileInfo &file); - QString folderName() const - { - return m_folderName; - } - QString name() const - { - return m_actualName; - } - QDateTime lastPlayed() const - { - return m_lastPlayed; - } - int64_t seed() const - { - return m_randomSeed; - } - bool isValid() const - { - return is_valid; - } - bool isOnFS() const - { - return m_containerFile.isDir(); - } - QFileInfo container() const - { - return m_containerFile; - } - // delete all the files of this world - bool destroy(); - // replace this world with a copy of the other - bool replace(World &with); - // change the world's filesystem path (used by world lists for *MAGIC* purposes) - void repath(const QFileInfo &file); + World(const QFileInfo &file); + QString folderName() const + { + return m_folderName; + } + QString name() const + { + return m_actualName; + } + QDateTime lastPlayed() const + { + return m_lastPlayed; + } + GameType gameType() const + { + return m_gameType; + } + int64_t seed() const + { + return m_randomSeed; + } + bool isValid() const + { + return is_valid; + } + bool isOnFS() const + { + return m_containerFile.isDir(); + } + QFileInfo container() const + { + return m_containerFile; + } + // delete all the files of this world + bool destroy(); + // replace this world with a copy of the other + bool replace(World &with); + // change the world's filesystem path (used by world lists for *MAGIC* purposes) + void repath(const QFileInfo &file); - bool rename(const QString &to); - bool install(const QString &to, const QString &name= QString()); + bool rename(const QString &to); + bool install(const QString &to, const QString &name= QString()); - // WEAK compare operator - used for replacing worlds - bool operator==(const World &other) const; - bool strongCompare(const World &other) const; + // WEAK compare operator - used for replacing worlds + bool operator==(const World &other) const; private: - void readFromZip(const QFileInfo &file); - void readFromFS(const QFileInfo &file); - void loadFromLevelDat(QByteArray data); + void readFromZip(const QFileInfo &file); + void readFromFS(const QFileInfo &file); + void loadFromLevelDat(QByteArray data); protected: - QFileInfo m_containerFile; - QString m_containerOffsetPath; - QString m_folderName; - QString m_actualName; - QDateTime levelDatTime; - QDateTime m_lastPlayed; - int64_t m_randomSeed = 0; - bool is_valid = false; + QFileInfo m_containerFile; + QString m_containerOffsetPath; + QString m_folderName; + QString m_actualName; + QDateTime levelDatTime; + QDateTime m_lastPlayed; + int64_t m_randomSeed = 0; + GameType m_gameType = GameType::Survival; + bool is_valid = false; }; diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp index 4278c2f7..b7a24434 100644 --- a/api/logic/minecraft/WorldList.cpp +++ b/api/logic/minecraft/WorldList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,216 +23,223 @@ #include WorldList::WorldList(const QString &dir) - : QAbstractListModel(), m_dir(dir) + : QAbstractListModel(), m_dir(dir) { - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - is_watching = false; - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | + QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher = new QFileSystemWatcher(this); + is_watching = false; + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); } void WorldList::startWatching() { - if(is_watching) - { - return; - } - update(); - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) - { - qDebug() << "Started watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } + if(is_watching) + { + return; + } + update(); + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) + { + qDebug() << "Started watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } } void WorldList::stopWatching() { - if(!is_watching) - { - return; - } - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) - { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } - else - { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } + if(!is_watching) + { + return; + } + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) + { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } } bool WorldList::update() { - if (!isValid()) - return false; - - QList newWorlds; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - // if there are any untracked files... - for (QFileInfo entry : folderContents) - { - if(!entry.isDir()) - continue; - - World w(entry); - if(w.isValid()) - { - newWorlds.append(w); - } - } - beginResetModel(); - worlds.swap(newWorlds); - endResetModel(); - return true; + if (!isValid()) + return false; + + QList newWorlds; + m_dir.refresh(); + auto folderContents = m_dir.entryInfoList(); + // if there are any untracked files... + for (QFileInfo entry : folderContents) + { + if(!entry.isDir()) + continue; + + World w(entry); + if(w.isValid()) + { + newWorlds.append(w); + } + } + beginResetModel(); + worlds.swap(newWorlds); + endResetModel(); + return true; } void WorldList::directoryChanged(QString path) { - update(); + update(); } bool WorldList::isValid() { - return m_dir.exists() && m_dir.isReadable(); + return m_dir.exists() && m_dir.isReadable(); } bool WorldList::deleteWorld(int index) { - if (index >= worlds.size() || index < 0) - return false; - World &m = worlds[index]; - if (m.destroy()) - { - beginRemoveRows(QModelIndex(), index, index); - worlds.removeAt(index); - endRemoveRows(); - emit changed(); - return true; - } - return false; + if (index >= worlds.size() || index < 0) + return false; + World &m = worlds[index]; + if (m.destroy()) + { + beginRemoveRows(QModelIndex(), index, index); + worlds.removeAt(index); + endRemoveRows(); + emit changed(); + return true; + } + return false; } bool WorldList::deleteWorlds(int first, int last) { - for (int i = first; i <= last; i++) - { - World &m = worlds[i]; - m.destroy(); - } - beginRemoveRows(QModelIndex(), first, last); - worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); - endRemoveRows(); - emit changed(); - return true; + for (int i = first; i <= last; i++) + { + World &m = worlds[i]; + m.destroy(); + } + beginRemoveRows(QModelIndex(), first, last); + worlds.erase(worlds.begin() + first, worlds.begin() + last + 1); + endRemoveRows(); + emit changed(); + return true; } int WorldList::columnCount(const QModelIndex &parent) const { - return 2; + return 3; } QVariant WorldList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - int column = index.column(); - - if (row < 0 || row >= worlds.size()) - return QVariant(); - - auto & world = worlds[row]; - switch (role) - { - case Qt::DisplayRole: - switch (column) - { - case NameColumn: - return world.name(); - - case LastPlayedColumn: - return world.lastPlayed(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - { - return world.folderName(); - } - case ObjectRole: - { - return QVariant::fromValue((void *)&world); - } - case FolderRole: - { - return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); - } - case SeedRole: - { - return qVariantFromValue(world.seed()); - } - case NameRole: - { - return world.name(); - } - case LastPlayedRole: - { - return world.lastPlayed(); - } - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= worlds.size()) + return QVariant(); + + auto & world = worlds[row]; + switch (role) + { + case Qt::DisplayRole: + switch (column) + { + case NameColumn: + return world.name(); + + case GameModeColumn: + return gameTypeToString(world.gameType()); + + case LastPlayedColumn: + return world.lastPlayed(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + { + return world.folderName(); + } + case ObjectRole: + { + return QVariant::fromValue((void *)&world); + } + case FolderRole: + { + return QDir::toNativeSeparators(dir().absoluteFilePath(world.folderName())); + } + case SeedRole: + { + return qVariantFromValue(world.seed()); + } + case NameRole: + { + return world.name(); + } + case LastPlayedRole: + { + return world.lastPlayed(); + } + default: + return QVariant(); + } } QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const { - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case NameColumn: - return tr("Name"); - case LastPlayedColumn: - return tr("Last Played"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the world."); - case LastPlayedColumn: - return tr("Date and time the world was last played."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case NameColumn: + return tr("Name"); + case GameModeColumn: + return tr("Game Mode"); + case LastPlayedColumn: + return tr("Last Played"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return tr("The name of the world."); + case GameModeColumn: + return tr("Game mode of the world."); + case LastPlayedColumn: + return tr("Date and time the world was last played."); + default: + return QVariant(); + } + default: + return QVariant(); + } + return QVariant(); } QStringList WorldList::mimeTypes() const { - QStringList types; - types << "text/uri-list"; - return types; + QStringList types; + types << "text/uri-list"; + return types; } class WorldMimeData : public QMimeData @@ -240,124 +247,124 @@ class WorldMimeData : public QMimeData Q_OBJECT public: - WorldMimeData(QList worlds) - { - m_worlds = worlds; + WorldMimeData(QList worlds) + { + m_worlds = worlds; - } - QStringList formats() const - { - return QMimeData::formats() << "text/uri-list"; - } + } + QStringList formats() const + { + return QMimeData::formats() << "text/uri-list"; + } protected: - QVariant retrieveData(const QString &mimetype, QVariant::Type type) const - { - QList urls; - for(auto &world: m_worlds) - { - if(!world.isValid() || !world.isOnFS()) - continue; - QString worldPath = world.container().absoluteFilePath(); - qDebug() << worldPath; - urls.append(QUrl::fromLocalFile(worldPath)); - } - const_cast(this)->setUrls(urls); - return QMimeData::retrieveData(mimetype, type); - } + QVariant retrieveData(const QString &mimetype, QVariant::Type type) const + { + QList urls; + for(auto &world: m_worlds) + { + if(!world.isValid() || !world.isOnFS()) + continue; + QString worldPath = world.container().absoluteFilePath(); + qDebug() << worldPath; + urls.append(QUrl::fromLocalFile(worldPath)); + } + const_cast(this)->setUrls(urls); + return QMimeData::retrieveData(mimetype, type); + } private: - QList m_worlds; + QList m_worlds; }; QMimeData *WorldList::mimeData(const QModelIndexList &indexes) const { - if (indexes.size() == 0) - return new QMimeData(); - - QList worlds; - for(auto idx : indexes) - { - if(idx.column() != 0) - continue; - int row = idx.row(); - if (row < 0 || row >= this->worlds.size()) - continue; - worlds.append(this->worlds[row]); - } - if(!worlds.size()) - { - return new QMimeData(); - } - return new WorldMimeData(worlds); + if (indexes.size() == 0) + return new QMimeData(); + + QList worlds; + for(auto idx : indexes) + { + if(idx.column() != 0) + continue; + int row = idx.row(); + if (row < 0 || row >= this->worlds.size()) + continue; + worlds.append(this->worlds[row]); + } + if(!worlds.size()) + { + return new QMimeData(); + } + return new WorldMimeData(worlds); } Qt::ItemFlags WorldList::flags(const QModelIndex &index) const { - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - if (index.isValid()) - return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | - defaultFlags; - else - return Qt::ItemIsDropEnabled | defaultFlags; + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + if (index.isValid()) + return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | + defaultFlags; + else + return Qt::ItemIsDropEnabled | defaultFlags; } Qt::DropActions WorldList::supportedDragActions() const { - // move to other mod lists or VOID - return Qt::MoveAction; + // move to other mod lists or VOID + return Qt::MoveAction; } Qt::DropActions WorldList::supportedDropActions() const { - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; } void WorldList::installWorld(QFileInfo filename) { - qDebug() << "installing: " << filename.absoluteFilePath(); - World w(filename); - if(!w.isValid()) - { - return; - } - w.install(m_dir.absolutePath()); + qDebug() << "installing: " << filename.absoluteFilePath(); + World w(filename); + if(!w.isValid()) + { + return; + } + w.install(m_dir.absolutePath()); } bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, - const QModelIndex &parent) + const QModelIndex &parent) { - if (action == Qt::IgnoreAction) - return true; - // check if the action is supported - if (!data || !(action & supportedDropActions())) - return false; - // files dropped from outside? - if (data->hasUrls()) - { - bool was_watching = is_watching; - if (was_watching) - stopWatching(); - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - continue; - QString filename = url.toLocalFile(); - - QFileInfo worldInfo(filename); - - if(!m_dir.entryInfoList().contains(worldInfo)) - { - installWorld(worldInfo); - } - } - if (was_watching) - startWatching(); - return true; - } - return false; + if (action == Qt::IgnoreAction) + return true; + // check if the action is supported + if (!data || !(action & supportedDropActions())) + return false; + // files dropped from outside? + if (data->hasUrls()) + { + bool was_watching = is_watching; + if (was_watching) + stopWatching(); + auto urls = data->urls(); + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + continue; + QString filename = url.toLocalFile(); + + QFileInfo worldInfo(filename); + + if(!m_dir.entryInfoList().contains(worldInfo)) + { + installWorld(worldInfo); + } + } + if (was_watching) + startWatching(); + return true; + } + return false; } #include "WorldList.moc" diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h index 811393cd..37ad330a 100644 --- a/api/logic/minecraft/WorldList.h +++ b/api/logic/minecraft/WorldList.h @@ -1,4 +1,4 @@ -/* Copyright 2015-2018 MultiMC Contributors +/* Copyright 2015-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,98 +28,100 @@ class QFileSystemWatcher; class MULTIMC_LOGIC_EXPORT WorldList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum Columns - { - NameColumn, - LastPlayedColumn - }; - - enum Roles - { - ObjectRole = Qt::UserRole + 1, - FolderRole, - SeedRole, - NameRole, - LastPlayedRole - }; - - WorldList(const QString &dir); - - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const - { - return size(); - }; - virtual QVariant headerData(int section, Qt::Orientation orientation, - int role = Qt::DisplayRole) const; - virtual int columnCount(const QModelIndex &parent) const; - - size_t size() const - { - return worlds.size(); - }; - bool empty() const - { - return size() == 0; - } - World &operator[](size_t index) - { - return worlds[index]; - } - - /// Reloads the mod list and returns true if the list changed. - virtual bool update(); - - /// Install a world from location - void installWorld(QFileInfo filename); - - /// Deletes the mod at the given index. - virtual bool deleteWorld(int index); - - /// Deletes all the selected mods - virtual bool deleteWorlds(int first, int last); - - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - /// get data for drag action - virtual QMimeData *mimeData(const QModelIndexList &indexes) const; - /// get the supported mime types - virtual QStringList mimeTypes() const; - /// process data from drop action - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - /// what drag actions do we support? - virtual Qt::DropActions supportedDragActions() const; - - /// what drop actions do we support? - virtual Qt::DropActions supportedDropActions() const; - - void startWatching(); - void stopWatching(); - - virtual bool isValid(); - - QDir dir() const - { - return m_dir; - } - - const QList &allWorlds() const - { - return worlds; - } + enum Columns + { + NameColumn, + GameModeColumn, + LastPlayedColumn + }; + + enum Roles + { + ObjectRole = Qt::UserRole + 1, + FolderRole, + SeedRole, + NameRole, + GameModeRole, + LastPlayedRole + }; + + WorldList(const QString &dir); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const + { + return size(); + }; + virtual QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + virtual int columnCount(const QModelIndex &parent) const; + + size_t size() const + { + return worlds.size(); + }; + bool empty() const + { + return size() == 0; + } + World &operator[](size_t index) + { + return worlds[index]; + } + + /// Reloads the mod list and returns true if the list changed. + virtual bool update(); + + /// Install a world from location + void installWorld(QFileInfo filename); + + /// Deletes the mod at the given index. + virtual bool deleteWorld(int index); + + /// Deletes all the selected mods + virtual bool deleteWorlds(int first, int last); + + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + /// get data for drag action + virtual QMimeData *mimeData(const QModelIndexList &indexes) const; + /// get the supported mime types + virtual QStringList mimeTypes() const; + /// process data from drop action + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + /// what drag actions do we support? + virtual Qt::DropActions supportedDragActions() const; + + /// what drop actions do we support? + virtual Qt::DropActions supportedDropActions() const; + + void startWatching(); + void stopWatching(); + + virtual bool isValid(); + + QDir dir() const + { + return m_dir; + } + + const QList &allWorlds() const + { + return worlds; + } private slots: - void directoryChanged(QString path); + void directoryChanged(QString path); signals: - void changed(); + void changed(); protected: - QFileSystemWatcher *m_watcher; - bool is_watching; - QDir m_dir; - QList worlds; + QFileSystemWatcher *m_watcher; + bool is_watching; + QDir m_dir; + QList worlds; }; diff --git a/api/logic/minecraft/auth/AuthSession.cpp b/api/logic/minecraft/auth/AuthSession.cpp index 8758bfbd..4e858796 100644 --- a/api/logic/minecraft/auth/AuthSession.cpp +++ b/api/logic/minecraft/auth/AuthSession.cpp @@ -6,25 +6,25 @@ QString AuthSession::serializeUserProperties() { - QJsonObject userAttrs; - for (auto key : u.properties.keys()) - { - auto array = QJsonArray::fromStringList(u.properties.values(key)); - userAttrs.insert(key, array); - } - QJsonDocument value(userAttrs); - return value.toJson(QJsonDocument::Compact); + QJsonObject userAttrs; + for (auto key : u.properties.keys()) + { + auto array = QJsonArray::fromStringList(u.properties.values(key)); + userAttrs.insert(key, array); + } + QJsonDocument value(userAttrs); + return value.toJson(QJsonDocument::Compact); } bool AuthSession::MakeOffline(QString offline_playername) { - if (status != PlayableOffline && status != PlayableOnline) - { - return false; - } - session = "-"; - player_name = offline_playername; - status = PlayableOffline; - return true; + if (status != PlayableOffline && status != PlayableOnline) + { + return false; + } + session = "-"; + player_name = offline_playername; + status = PlayableOffline; + return true; } diff --git a/api/logic/minecraft/auth/AuthSession.h b/api/logic/minecraft/auth/AuthSession.h index d2f66db8..b397d9a1 100644 --- a/api/logic/minecraft/auth/AuthSession.h +++ b/api/logic/minecraft/auth/AuthSession.h @@ -10,45 +10,45 @@ class MojangAccount; struct User { - QString id; - QMultiMap properties; + QString id; + QMultiMap properties; }; struct MULTIMC_LOGIC_EXPORT AuthSession { - bool MakeOffline(QString offline_playername); - - QString serializeUserProperties(); - - enum Status - { - Undetermined, - RequiresPassword, - PlayableOffline, - PlayableOnline - } status = Undetermined; - - User u; - - // client token - QString client_token; - // account user name - QString username; - // combined session ID - QString session; - // volatile auth token - QString access_token; - // profile name - QString player_name; - // profile ID - QString uuid; - // 'legacy' or 'mojang', depending on account type - QString user_type; - // Did the auth server reply? - bool auth_server_online = false; - // Did the user request online mode? - bool wants_online = true; - std::shared_ptr m_accountPtr; + bool MakeOffline(QString offline_playername); + + QString serializeUserProperties(); + + enum Status + { + Undetermined, + RequiresPassword, + PlayableOffline, + PlayableOnline + } status = Undetermined; + + User u; + + // client token + QString client_token; + // account user name + QString username; + // combined session ID + QString session; + // volatile auth token + QString access_token; + // profile name + QString player_name; + // profile ID + QString uuid; + // 'legacy' or 'mojang', depending on account type + QString user_type; + // Did the auth server reply? + bool auth_server_online = false; + // Did the user request online mode? + bool wants_online = true; + std::shared_ptr m_accountPtr; }; typedef std::shared_ptr AuthSessionPtr; diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp index edad4344..657e0009 100644 --- a/api/logic/minecraft/auth/MojangAccount.cpp +++ b/api/logic/minecraft/auth/MojangAccount.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Authors: Orochimarufan * @@ -30,286 +30,286 @@ MojangAccountPtr MojangAccount::loadFromJson(const QJsonObject &object) { - // The JSON object must at least have a username for it to be valid. - if (!object.value("username").isString()) - { - qCritical() << "Can't load Mojang account info from JSON object. Username field is " - "missing or of the wrong type."; - return nullptr; - } + // The JSON object must at least have a username for it to be valid. + if (!object.value("username").isString()) + { + qCritical() << "Can't load Mojang account info from JSON object. Username field is " + "missing or of the wrong type."; + return nullptr; + } - QString username = object.value("username").toString(""); - QString clientToken = object.value("clientToken").toString(""); - QString accessToken = object.value("accessToken").toString(""); + QString username = object.value("username").toString(""); + QString clientToken = object.value("clientToken").toString(""); + QString accessToken = object.value("accessToken").toString(""); - QJsonArray profileArray = object.value("profiles").toArray(); - if (profileArray.size() < 1) - { - qCritical() << "Can't load Mojang account with username \"" << username - << "\". No profiles found."; - return nullptr; - } + QJsonArray profileArray = object.value("profiles").toArray(); + if (profileArray.size() < 1) + { + qCritical() << "Can't load Mojang account with username \"" << username + << "\". No profiles found."; + return nullptr; + } - QList profiles; - for (QJsonValue profileVal : profileArray) - { - QJsonObject profileObject = profileVal.toObject(); - QString id = profileObject.value("id").toString(""); - QString name = profileObject.value("name").toString(""); - bool legacy = profileObject.value("legacy").toBool(false); - if (id.isEmpty() || name.isEmpty()) - { - qWarning() << "Unable to load a profile because it was missing an ID or a name."; - continue; - } - profiles.append({id, name, legacy}); - } + QList profiles; + for (QJsonValue profileVal : profileArray) + { + QJsonObject profileObject = profileVal.toObject(); + QString id = profileObject.value("id").toString(""); + QString name = profileObject.value("name").toString(""); + bool legacy = profileObject.value("legacy").toBool(false); + if (id.isEmpty() || name.isEmpty()) + { + qWarning() << "Unable to load a profile because it was missing an ID or a name."; + continue; + } + profiles.append({id, name, legacy}); + } - MojangAccountPtr account(new MojangAccount()); - if (object.value("user").isObject()) - { - User u; - QJsonObject userStructure = object.value("user").toObject(); - u.id = userStructure.value("id").toString(); - /* - QJsonObject propMap = userStructure.value("properties").toObject(); - for(auto key: propMap.keys()) - { - auto values = propMap.operator[](key).toArray(); - for(auto value: values) - u.properties.insert(key, value.toString()); - } - */ - account->m_user = u; - } - account->m_username = username; - account->m_clientToken = clientToken; - account->m_accessToken = accessToken; - account->m_profiles = profiles; + MojangAccountPtr account(new MojangAccount()); + if (object.value("user").isObject()) + { + User u; + QJsonObject userStructure = object.value("user").toObject(); + u.id = userStructure.value("id").toString(); + /* + QJsonObject propMap = userStructure.value("properties").toObject(); + for(auto key: propMap.keys()) + { + auto values = propMap.operator[](key).toArray(); + for(auto value: values) + u.properties.insert(key, value.toString()); + } + */ + account->m_user = u; + } + account->m_username = username; + account->m_clientToken = clientToken; + account->m_accessToken = accessToken; + account->m_profiles = profiles; - // Get the currently selected profile. - QString currentProfile = object.value("activeProfile").toString(""); - if (!currentProfile.isEmpty()) - account->setCurrentProfile(currentProfile); + // Get the currently selected profile. + QString currentProfile = object.value("activeProfile").toString(""); + if (!currentProfile.isEmpty()) + account->setCurrentProfile(currentProfile); - return account; + return account; } MojangAccountPtr MojangAccount::createFromUsername(const QString &username) { - MojangAccountPtr account(new MojangAccount()); - account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - account->m_username = username; - return account; + MojangAccountPtr account(new MojangAccount()); + account->m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + account->m_username = username; + return account; } QJsonObject MojangAccount::saveToJson() const { - QJsonObject json; - json.insert("username", m_username); - json.insert("clientToken", m_clientToken); - json.insert("accessToken", m_accessToken); + QJsonObject json; + json.insert("username", m_username); + json.insert("clientToken", m_clientToken); + json.insert("accessToken", m_accessToken); - QJsonArray profileArray; - for (AccountProfile profile : m_profiles) - { - QJsonObject profileObj; - profileObj.insert("id", profile.id); - profileObj.insert("name", profile.name); - profileObj.insert("legacy", profile.legacy); - profileArray.append(profileObj); - } - json.insert("profiles", profileArray); + QJsonArray profileArray; + for (AccountProfile profile : m_profiles) + { + QJsonObject profileObj; + profileObj.insert("id", profile.id); + profileObj.insert("name", profile.name); + profileObj.insert("legacy", profile.legacy); + profileArray.append(profileObj); + } + json.insert("profiles", profileArray); - QJsonObject userStructure; - { - userStructure.insert("id", m_user.id); - /* - QJsonObject userAttrs; - for(auto key: m_user.properties.keys()) - { - auto array = QJsonArray::fromStringList(m_user.properties.values(key)); - userAttrs.insert(key, array); - } - userStructure.insert("properties", userAttrs); - */ - } - json.insert("user", userStructure); + QJsonObject userStructure; + { + userStructure.insert("id", m_user.id); + /* + QJsonObject userAttrs; + for(auto key: m_user.properties.keys()) + { + auto array = QJsonArray::fromStringList(m_user.properties.values(key)); + userAttrs.insert(key, array); + } + userStructure.insert("properties", userAttrs); + */ + } + json.insert("user", userStructure); - if (m_currentProfile != -1) - json.insert("activeProfile", currentProfile()->id); + if (m_currentProfile != -1) + json.insert("activeProfile", currentProfile()->id); - return json; + return json; } bool MojangAccount::setCurrentProfile(const QString &profileId) { - for (int i = 0; i < m_profiles.length(); i++) - { - if (m_profiles[i].id == profileId) - { - m_currentProfile = i; - return true; - } - } - return false; + for (int i = 0; i < m_profiles.length(); i++) + { + if (m_profiles[i].id == profileId) + { + m_currentProfile = i; + return true; + } + } + return false; } const AccountProfile *MojangAccount::currentProfile() const { - if (m_currentProfile == -1) - return nullptr; - return &m_profiles[m_currentProfile]; + if (m_currentProfile == -1) + return nullptr; + return &m_profiles[m_currentProfile]; } AccountStatus MojangAccount::accountStatus() const { - if (m_accessToken.isEmpty()) - return NotVerified; - else - return Verified; + if (m_accessToken.isEmpty()) + return NotVerified; + else + return Verified; } std::shared_ptr MojangAccount::login(AuthSessionPtr session, QString password) { - Q_ASSERT(m_currentTask.get() == nullptr); + Q_ASSERT(m_currentTask.get() == nullptr); - // take care of the true offline status - if (accountStatus() == NotVerified && password.isEmpty()) - { - if (session) - { - session->status = AuthSession::RequiresPassword; - fillSession(session); - } - return nullptr; - } + // take care of the true offline status + if (accountStatus() == NotVerified && password.isEmpty()) + { + if (session) + { + session->status = AuthSession::RequiresPassword; + fillSession(session); + } + return nullptr; + } - if(accountStatus() == Verified && !session->wants_online) - { - session->status = AuthSession::PlayableOffline; - session->auth_server_online = false; - fillSession(session); - return nullptr; - } - else - { - if (password.isEmpty()) - { - m_currentTask.reset(new RefreshTask(this)); - } - else - { - m_currentTask.reset(new AuthenticateTask(this, password)); - } - m_currentTask->assignSession(session); + if(accountStatus() == Verified && !session->wants_online) + { + session->status = AuthSession::PlayableOffline; + session->auth_server_online = false; + fillSession(session); + return nullptr; + } + else + { + if (password.isEmpty()) + { + m_currentTask.reset(new RefreshTask(this)); + } + else + { + m_currentTask.reset(new AuthenticateTask(this, password)); + } + m_currentTask->assignSession(session); - connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); - connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); - } - return m_currentTask; + connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded())); + connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString))); + } + return m_currentTask; } void MojangAccount::authSucceeded() { - auto session = m_currentTask->getAssignedSession(); - if (session) - { - session->status = - session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; - fillSession(session); - session->auth_server_online = true; - } - m_currentTask.reset(); - emit changed(); + auto session = m_currentTask->getAssignedSession(); + if (session) + { + session->status = + session->wants_online ? AuthSession::PlayableOnline : AuthSession::PlayableOffline; + fillSession(session); + session->auth_server_online = true; + } + m_currentTask.reset(); + emit changed(); } void MojangAccount::authFailed(QString reason) { - auto session = m_currentTask->getAssignedSession(); - // This is emitted when the yggdrasil tasks time out or are cancelled. - // -> we treat the error as no-op - if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) - { - if (session) - { - session->status = accountStatus() == Verified ? AuthSession::PlayableOffline - : AuthSession::RequiresPassword; - session->auth_server_online = false; - fillSession(session); - } - } - else - { - m_accessToken = QString(); - emit changed(); - if (session) - { - session->status = AuthSession::RequiresPassword; - session->auth_server_online = true; - fillSession(session); - } - } - m_currentTask.reset(); + auto session = m_currentTask->getAssignedSession(); + // This is emitted when the yggdrasil tasks time out or are cancelled. + // -> we treat the error as no-op + if (m_currentTask->state() == YggdrasilTask::STATE_FAILED_SOFT) + { + if (session) + { + session->status = accountStatus() == Verified ? AuthSession::PlayableOffline + : AuthSession::RequiresPassword; + session->auth_server_online = false; + fillSession(session); + } + } + else + { + m_accessToken = QString(); + emit changed(); + if (session) + { + session->status = AuthSession::RequiresPassword; + session->auth_server_online = true; + fillSession(session); + } + } + m_currentTask.reset(); } void MojangAccount::fillSession(AuthSessionPtr session) { - // the user name. you have to have an user name - session->username = m_username; - // volatile auth token - session->access_token = m_accessToken; - // the semi-permanent client token - session->client_token = m_clientToken; - if (currentProfile()) - { - // profile name - session->player_name = currentProfile()->name; - // profile ID - session->uuid = currentProfile()->id; - // 'legacy' or 'mojang', depending on account type - session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; - if (!session->access_token.isEmpty()) - { - session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; - } - else - { - session->session = "-"; - } - } - else - { - session->player_name = "Player"; - session->session = "-"; - } - session->u = user(); - session->m_accountPtr = shared_from_this(); + // the user name. you have to have an user name + session->username = m_username; + // volatile auth token + session->access_token = m_accessToken; + // the semi-permanent client token + session->client_token = m_clientToken; + if (currentProfile()) + { + // profile name + session->player_name = currentProfile()->name; + // profile ID + session->uuid = currentProfile()->id; + // 'legacy' or 'mojang', depending on account type + session->user_type = currentProfile()->legacy ? "legacy" : "mojang"; + if (!session->access_token.isEmpty()) + { + session->session = "token:" + m_accessToken + ":" + m_profiles[m_currentProfile].id; + } + else + { + session->session = "-"; + } + } + else + { + session->player_name = "Player"; + session->session = "-"; + } + session->u = user(); + session->m_accountPtr = shared_from_this(); } void MojangAccount::decrementUses() { - Usable::decrementUses(); - if(!isInUse()) - { - emit changed(); - qWarning() << "Account" << m_username << "is no longer in use."; - } + Usable::decrementUses(); + if(!isInUse()) + { + emit changed(); + qWarning() << "Account" << m_username << "is no longer in use."; + } } void MojangAccount::incrementUses() { - bool wasInUse = isInUse(); - Usable::incrementUses(); - if(!wasInUse) - { - emit changed(); - qWarning() << "Account" << m_username << "is now in use."; - } + bool wasInUse = isInUse(); + Usable::incrementUses(); + if(!wasInUse) + { + emit changed(); + qWarning() << "Account" << m_username << "is now in use."; + } } void MojangAccount::invalidateClientToken() { - m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); - emit changed(); + m_clientToken = QUuid::createUuid().toString().remove(QRegExp("[{}-]")); + emit changed(); } diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h index b2bbc357..7006435e 100644 --- a/api/logic/minecraft/auth/MojangAccount.h +++ b/api/logic/minecraft/auth/MojangAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,15 +44,15 @@ Q_DECLARE_METATYPE(MojangAccountPtr) */ struct AccountProfile { - QString id; - QString name; - bool legacy; + QString id; + QString name; + bool legacy; }; enum AccountStatus { - NotVerified, - Verified + NotVerified, + Verified }; /** @@ -62,121 +62,121 @@ enum AccountStatus * token if the user chose to stay logged in. */ class MULTIMC_LOGIC_EXPORT MojangAccount : - public QObject, - public Usable, - public std::enable_shared_from_this + public QObject, + public Usable, + public std::enable_shared_from_this { - Q_OBJECT + Q_OBJECT public: /* construction */ - //! Do not copy accounts. ever. - explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; + //! Do not copy accounts. ever. + explicit MojangAccount(const MojangAccount &other, QObject *parent) = delete; - //! Default constructor - explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; + //! Default constructor + explicit MojangAccount(QObject *parent = 0) : QObject(parent) {}; - //! Creates an empty account for the specified user name. - static MojangAccountPtr createFromUsername(const QString &username); + //! Creates an empty account for the specified user name. + static MojangAccountPtr createFromUsername(const QString &username); - //! Loads a MojangAccount from the given JSON object. - static MojangAccountPtr loadFromJson(const QJsonObject &json); + //! Loads a MojangAccount from the given JSON object. + static MojangAccountPtr loadFromJson(const QJsonObject &json); - //! Saves a MojangAccount to a JSON object and returns it. - QJsonObject saveToJson() const; + //! Saves a MojangAccount to a JSON object and returns it. + QJsonObject saveToJson() const; public: /* manipulation */ - /** - * Sets the currently selected profile to the profile with the given ID string. - * If profileId is not in the list of available profiles, the function will simply return - * false. - */ - bool setCurrentProfile(const QString &profileId); - - /** - * Attempt to login. Empty password means we use the token. - * If the attempt fails because we already are performing some task, it returns false. - */ - std::shared_ptr login(AuthSessionPtr session, QString password = QString()); - void invalidateClientToken(); + /** + * Sets the currently selected profile to the profile with the given ID string. + * If profileId is not in the list of available profiles, the function will simply return + * false. + */ + bool setCurrentProfile(const QString &profileId); + + /** + * Attempt to login. Empty password means we use the token. + * If the attempt fails because we already are performing some task, it returns false. + */ + std::shared_ptr login(AuthSessionPtr session, QString password = QString()); + void invalidateClientToken(); public: /* queries */ - const QString &username() const - { - return m_username; - } + const QString &username() const + { + return m_username; + } - const QString &clientToken() const - { - return m_clientToken; - } + const QString &clientToken() const + { + return m_clientToken; + } - const QString &accessToken() const - { - return m_accessToken; - } + const QString &accessToken() const + { + return m_accessToken; + } - const QList &profiles() const - { - return m_profiles; - } + const QList &profiles() const + { + return m_profiles; + } - const User &user() - { - return m_user; - } + const User &user() + { + return m_user; + } - //! Returns the currently selected profile (if none, returns nullptr) - const AccountProfile *currentProfile() const; + //! Returns the currently selected profile (if none, returns nullptr) + const AccountProfile *currentProfile() const; - //! Returns whether the account is NotVerified, Verified or Online - AccountStatus accountStatus() const; + //! Returns whether the account is NotVerified, Verified or Online + AccountStatus accountStatus() const; signals: - /** - * This signal is emitted when the account changes - */ - void changed(); + /** + * This signal is emitted when the account changes + */ + void changed(); - // TODO: better signalling for the various possible state changes - especially errors + // TODO: better signalling for the various possible state changes - especially errors protected: /* variables */ - QString m_username; + QString m_username; - // Used to identify the client - the user can have multiple clients for the same account - // Think: different launchers, all connecting to the same account/profile - QString m_clientToken; + // Used to identify the client - the user can have multiple clients for the same account + // Think: different launchers, all connecting to the same account/profile + QString m_clientToken; - // Blank if not logged in. - QString m_accessToken; + // Blank if not logged in. + QString m_accessToken; - // Index of the selected profile within the list of available - // profiles. -1 if nothing is selected. - int m_currentProfile = -1; + // Index of the selected profile within the list of available + // profiles. -1 if nothing is selected. + int m_currentProfile = -1; - // List of available profiles. - QList m_profiles; + // List of available profiles. + QList m_profiles; - // the user structure, whatever it is. - User m_user; + // the user structure, whatever it is. + User m_user; - // current task we are executing here - std::shared_ptr m_currentTask; + // current task we are executing here + std::shared_ptr m_currentTask; protected: /* methods */ - void incrementUses() override; - void decrementUses() override; + void incrementUses() override; + void decrementUses() override; private slots: - void authSucceeded(); - void authFailed(QString reason); + void authSucceeded(); + void authFailed(QString reason); private: - void fillSession(AuthSessionPtr session); + void fillSession(AuthSessionPtr session); public: - friend class YggdrasilTask; - friend class AuthenticateTask; - friend class ValidateTask; - friend class RefreshTask; + friend class YggdrasilTask; + friend class AuthenticateTask; + friend class ValidateTask; + friend class RefreshTask; }; diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp index 21ae188a..b594c652 100644 --- a/api/logic/minecraft/auth/MojangAccountList.cpp +++ b/api/logic/minecraft/auth/MojangAccountList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,432 +37,432 @@ MojangAccountList::MojangAccountList(QObject *parent) : QAbstractListModel(paren MojangAccountPtr MojangAccountList::findAccount(const QString &username) const { - for (int i = 0; i < count(); i++) - { - MojangAccountPtr account = at(i); - if (account->username() == username) - return account; - } - return nullptr; + for (int i = 0; i < count(); i++) + { + MojangAccountPtr account = at(i); + if (account->username() == username) + return account; + } + return nullptr; } const MojangAccountPtr MojangAccountList::at(int i) const { - return MojangAccountPtr(m_accounts.at(i)); + return MojangAccountPtr(m_accounts.at(i)); } void MojangAccountList::addAccount(const MojangAccountPtr account) { - int row = m_accounts.count(); - beginInsertRows(QModelIndex(), row, row); - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - endInsertRows(); - onListChanged(); + int row = m_accounts.count(); + beginInsertRows(QModelIndex(), row, row); + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + endInsertRows(); + onListChanged(); } void MojangAccountList::removeAccount(const QString &username) { - int idx = 0; - for (auto account : m_accounts) - { - if (account->username() == username) - { - beginRemoveRows(QModelIndex(), idx, idx); - m_accounts.removeOne(account); - endRemoveRows(); - return; - } - idx++; - } - onListChanged(); + int idx = 0; + for (auto account : m_accounts) + { + if (account->username() == username) + { + beginRemoveRows(QModelIndex(), idx, idx); + m_accounts.removeOne(account); + endRemoveRows(); + return; + } + idx++; + } + onListChanged(); } void MojangAccountList::removeAccount(QModelIndex index) { - int row = index.row(); - if(index.isValid() && row >= 0 && row < m_accounts.size()) - { - auto & account = m_accounts[row]; - if(account == m_activeAccount) - { - m_activeAccount = nullptr; - onActiveChanged(); - } - beginRemoveRows(QModelIndex(), row, row); - m_accounts.removeAt(index.row()); - endRemoveRows(); - onListChanged(); - } + int row = index.row(); + if(index.isValid() && row >= 0 && row < m_accounts.size()) + { + auto & account = m_accounts[row]; + if(account == m_activeAccount) + { + m_activeAccount = nullptr; + onActiveChanged(); + } + beginRemoveRows(QModelIndex(), row, row); + m_accounts.removeAt(index.row()); + endRemoveRows(); + onListChanged(); + } } MojangAccountPtr MojangAccountList::activeAccount() const { - return m_activeAccount; + return m_activeAccount; } void MojangAccountList::setActiveAccount(const QString &username) { - if (username.isEmpty() && m_activeAccount) - { - int idx = 0; - auto prevActiveAcc = m_activeAccount; - m_activeAccount = nullptr; - for (MojangAccountPtr account : m_accounts) - { - if (account == prevActiveAcc) - { - emit dataChanged(index(idx), index(idx)); - } - idx ++; - } - onActiveChanged(); - } - else - { - auto currentActiveAccount = m_activeAccount; - int currentActiveAccountIdx = -1; - auto newActiveAccount = m_activeAccount; - int newActiveAccountIdx = -1; - int idx = 0; - for (MojangAccountPtr account : m_accounts) - { - if (account->username() == username) - { - newActiveAccount = account; - newActiveAccountIdx = idx; - } - if(currentActiveAccount == account) - { - currentActiveAccountIdx = idx; - } - idx++; - } - if(currentActiveAccount != newActiveAccount) - { - emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); - emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); - m_activeAccount = newActiveAccount; - onActiveChanged(); - } - } + if (username.isEmpty() && m_activeAccount) + { + int idx = 0; + auto prevActiveAcc = m_activeAccount; + m_activeAccount = nullptr; + for (MojangAccountPtr account : m_accounts) + { + if (account == prevActiveAcc) + { + emit dataChanged(index(idx), index(idx)); + } + idx ++; + } + onActiveChanged(); + } + else + { + auto currentActiveAccount = m_activeAccount; + int currentActiveAccountIdx = -1; + auto newActiveAccount = m_activeAccount; + int newActiveAccountIdx = -1; + int idx = 0; + for (MojangAccountPtr account : m_accounts) + { + if (account->username() == username) + { + newActiveAccount = account; + newActiveAccountIdx = idx; + } + if(currentActiveAccount == account) + { + currentActiveAccountIdx = idx; + } + idx++; + } + if(currentActiveAccount != newActiveAccount) + { + emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx)); + emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx)); + m_activeAccount = newActiveAccount; + onActiveChanged(); + } + } } void MojangAccountList::accountChanged() { - // the list changed. there is no doubt. - onListChanged(); + // the list changed. there is no doubt. + onListChanged(); } void MojangAccountList::onListChanged() { - if (m_autosave) - // TODO: Alert the user if this fails. - saveList(); + if (m_autosave) + // TODO: Alert the user if this fails. + saveList(); - emit listChanged(); + emit listChanged(); } void MojangAccountList::onActiveChanged() { - if (m_autosave) - saveList(); + if (m_autosave) + saveList(); - emit activeAccountChanged(); + emit activeAccountChanged(); } int MojangAccountList::count() const { - return m_accounts.count(); + return m_accounts.count(); } QVariant MojangAccountList::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); - - if (index.row() > count()) - return QVariant(); - - MojangAccountPtr account = at(index.row()); - - switch (role) - { - case Qt::DisplayRole: - switch (index.column()) - { - case NameColumn: - return account->username(); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - return account->username(); - - case PointerRole: - return qVariantFromValue(account); - - case Qt::CheckStateRole: - switch (index.column()) - { - case ActiveColumn: - return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; - } - - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + if (index.row() > count()) + return QVariant(); + + MojangAccountPtr account = at(index.row()); + + switch (role) + { + case Qt::DisplayRole: + switch (index.column()) + { + case NameColumn: + return account->username(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return account->username(); + + case PointerRole: + return qVariantFromValue(account); + + case Qt::CheckStateRole: + switch (index.column()) + { + case ActiveColumn: + return account == m_activeAccount ? Qt::Checked : Qt::Unchecked; + } + + default: + return QVariant(); + } } QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation, int role) const { - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return tr("Active?"); - - case NameColumn: - return tr("Name"); - - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case NameColumn: - return tr("The name of the version."); - - default: - return QVariant(); - } - - default: - return QVariant(); - } + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return tr("Active?"); + + case NameColumn: + return tr("Name"); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case NameColumn: + return tr("The name of the version."); + + default: + return QVariant(); + } + + default: + return QVariant(); + } } int MojangAccountList::rowCount(const QModelIndex &) const { - // Return count - return count(); + // Return count + return count(); } int MojangAccountList::columnCount(const QModelIndex &) const { - return 2; + return 2; } Qt::ItemFlags MojangAccountList::flags(const QModelIndex &index) const { - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return Qt::NoItemFlags; - } + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return Qt::NoItemFlags; + } - return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool MojangAccountList::setData(const QModelIndex &index, const QVariant &value, int role) { - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if(role == Qt::CheckStateRole) - { - if(value == Qt::Checked) - { - MojangAccountPtr account = this->at(index.row()); - this->setActiveAccount(account->username()); - } - } - - emit dataChanged(index, index); - return true; + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if(role == Qt::CheckStateRole) + { + if(value == Qt::Checked) + { + MojangAccountPtr account = this->at(index.row()); + this->setActiveAccount(account->username()); + } + } + + emit dataChanged(index, index); + return true; } void MojangAccountList::updateListData(QList versions) { - beginResetModel(); - m_accounts = versions; - endResetModel(); + beginResetModel(); + m_accounts = versions; + endResetModel(); } bool MojangAccountList::loadList(const QString &filePath) { - QString path = filePath; - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't load Mojang account list. No file path given and no default set."; - return false; - } - - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::ReadOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Read the file and close it. - QByteArray jsonData = file.readAll(); - file.close(); - - QJsonParseError parseError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); - - // Fail if the JSON is invalid. - if (parseError.error != QJsonParseError::NoError) - { - qCritical() << QString("Failed to parse account list file: %1 at offset %2") - .arg(parseError.errorString(), QString::number(parseError.offset)) - .toUtf8(); - return false; - } - - // Make sure the root is an object. - if (!jsonDoc.isObject()) - { - qCritical() << "Invalid account list JSON: Root should be an array."; - return false; - } - - QJsonObject root = jsonDoc.object(); - - // Make sure the format version matches. - if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) - { - QString newName = "accounts-old.json"; - qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" - << newName; - - // Attempt to rename the old version. - file.rename(newName); - return false; - } - - // Now, load the accounts array. - beginResetModel(); - QJsonArray accounts = root.value("accounts").toArray(); - for (QJsonValue accountVal : accounts) - { - QJsonObject accountObj = accountVal.toObject(); - MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); - if (account.get() != nullptr) - { - connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); - m_accounts.append(account); - } - else - { - qWarning() << "Failed to load an account."; - } - } - // Load the active account. - m_activeAccount = findAccount(root.value("activeAccount").toString("")); - endResetModel(); - return true; + QString path = filePath; + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + qCritical() << "Can't load Mojang account list. No file path given and no default set."; + return false; + } + + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::ReadOnly)) + { + qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Read the file and close it. + QByteArray jsonData = file.readAll(); + file.close(); + + QJsonParseError parseError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); + + // Fail if the JSON is invalid. + if (parseError.error != QJsonParseError::NoError) + { + qCritical() << QString("Failed to parse account list file: %1 at offset %2") + .arg(parseError.errorString(), QString::number(parseError.offset)) + .toUtf8(); + return false; + } + + // Make sure the root is an object. + if (!jsonDoc.isObject()) + { + qCritical() << "Invalid account list JSON: Root should be an array."; + return false; + } + + QJsonObject root = jsonDoc.object(); + + // Make sure the format version matches. + if (root.value("formatVersion").toVariant().toInt() != ACCOUNT_LIST_FORMAT_VERSION) + { + QString newName = "accounts-old.json"; + qWarning() << "Format version mismatch when loading account list. Existing one will be renamed to" + << newName; + + // Attempt to rename the old version. + file.rename(newName); + return false; + } + + // Now, load the accounts array. + beginResetModel(); + QJsonArray accounts = root.value("accounts").toArray(); + for (QJsonValue accountVal : accounts) + { + QJsonObject accountObj = accountVal.toObject(); + MojangAccountPtr account = MojangAccount::loadFromJson(accountObj); + if (account.get() != nullptr) + { + connect(account.get(), SIGNAL(changed()), SLOT(accountChanged())); + m_accounts.append(account); + } + else + { + qWarning() << "Failed to load an account."; + } + } + // Load the active account. + m_activeAccount = findAccount(root.value("activeAccount").toString("")); + endResetModel(); + return true; } bool MojangAccountList::saveList(const QString &filePath) { - QString path(filePath); - if (path.isEmpty()) - path = m_listFilePath; - if (path.isEmpty()) - { - qCritical() << "Can't save Mojang account list. No file path given and no default set."; - return false; - } - - // make sure the parent folder exists - if(!FS::ensureFilePathExists(path)) - return false; - - // make sure the file wasn't overwritten with a folder before (fixes a bug) - QFileInfo finfo(path); - if(finfo.isDir()) - { - QDir badDir(path); - badDir.removeRecursively(); - } - - qDebug() << "Writing account list to" << path; - - qDebug() << "Building JSON data structure."; - // Build the JSON document to write to the list file. - QJsonObject root; - - root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); - - // Build a list of accounts. - qDebug() << "Building account array."; - QJsonArray accounts; - for (MojangAccountPtr account : m_accounts) - { - QJsonObject accountObj = account->saveToJson(); - accounts.append(accountObj); - } - - // Insert the account list into the root object. - root.insert("accounts", accounts); - - if(m_activeAccount) - { - // Save the active account. - root.insert("activeAccount", m_activeAccount->username()); - } - - // Create a JSON document object to convert our JSON to bytes. - QJsonDocument doc(root); - - // Now that we're done building the JSON object, we can write it to the file. - qDebug() << "Writing account list to file."; - QFile file(path); - - // Try to open the file and fail if we can't. - // TODO: We should probably report this error to the user. - if (!file.open(QIODevice::WriteOnly)) - { - qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); - return false; - } - - // Write the JSON to the file. - file.write(doc.toJson()); - file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); - file.close(); - - qDebug() << "Saved account list to" << path; - - return true; + QString path(filePath); + if (path.isEmpty()) + path = m_listFilePath; + if (path.isEmpty()) + { + qCritical() << "Can't save Mojang account list. No file path given and no default set."; + return false; + } + + // make sure the parent folder exists + if(!FS::ensureFilePathExists(path)) + return false; + + // make sure the file wasn't overwritten with a folder before (fixes a bug) + QFileInfo finfo(path); + if(finfo.isDir()) + { + QDir badDir(path); + badDir.removeRecursively(); + } + + qDebug() << "Writing account list to" << path; + + qDebug() << "Building JSON data structure."; + // Build the JSON document to write to the list file. + QJsonObject root; + + root.insert("formatVersion", ACCOUNT_LIST_FORMAT_VERSION); + + // Build a list of accounts. + qDebug() << "Building account array."; + QJsonArray accounts; + for (MojangAccountPtr account : m_accounts) + { + QJsonObject accountObj = account->saveToJson(); + accounts.append(accountObj); + } + + // Insert the account list into the root object. + root.insert("accounts", accounts); + + if(m_activeAccount) + { + // Save the active account. + root.insert("activeAccount", m_activeAccount->username()); + } + + // Create a JSON document object to convert our JSON to bytes. + QJsonDocument doc(root); + + // Now that we're done building the JSON object, we can write it to the file. + qDebug() << "Writing account list to file."; + QFile file(path); + + // Try to open the file and fail if we can't. + // TODO: We should probably report this error to the user. + if (!file.open(QIODevice::WriteOnly)) + { + qCritical() << QString("Failed to read the account list file (%1).").arg(path).toUtf8(); + return false; + } + + // Write the JSON to the file. + file.write(doc.toJson()); + file.setPermissions(QFile::ReadOwner|QFile::WriteOwner|QFile::ReadUser|QFile::WriteUser); + file.close(); + + qDebug() << "Saved account list to" << path; + + return true; } void MojangAccountList::setListFilePath(QString path, bool autosave) { - m_listFilePath = path; - m_autosave = autosave; + m_listFilePath = path; + m_autosave = autosave; } bool MojangAccountList::anyAccountIsValid() { - for(auto account:m_accounts) - { - if(account->accountStatus() != NotVerified) - return true; - } - return false; + for(auto account:m_accounts) + { + if(account->accountStatus() != NotVerified) + return true; + } + return false; } diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h index dd6c07e8..7760b32f 100644 --- a/api/logic/minecraft/auth/MojangAccountList.h +++ b/api/logic/minecraft/auth/MojangAccountList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,167 +35,167 @@ */ class MULTIMC_LOGIC_EXPORT MojangAccountList : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - enum ModelRoles - { - PointerRole = 0x34B1CB48 - }; - - enum VListColumns - { - // TODO: Add icon column. - - // First column - Active? - ActiveColumn = 0, - - // Second column - Name - NameColumn, - }; - - explicit MojangAccountList(QObject *parent = 0); - - //! Gets the account at the given index. - virtual const MojangAccountPtr at(int i) const; - - //! Returns the number of accounts in the list. - virtual int count() const; - - //////// 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; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); - - /*! - * Adds a the given Mojang account to the account list. - */ - virtual void addAccount(const MojangAccountPtr account); - - /*! - * Removes the mojang account with the given username from the account list. - */ - virtual void removeAccount(const QString &username); - - /*! - * Removes the account at the given QModelIndex. - */ - virtual void removeAccount(QModelIndex index); - - /*! - * \brief Finds an account by its username. - * \param The username of the account to find. - * \return A const pointer to the account with the given username. NULL if - * one doesn't exist. - */ - virtual MojangAccountPtr findAccount(const QString &username) const; - - /*! - * Sets the default path to save the list file to. - * If autosave is true, this list will automatically save to the given path whenever it changes. - * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately - * after calling this function to ensure an autosaved change doesn't overwrite the list you intended - * to load. - */ - virtual void setListFilePath(QString path, bool autosave = false); - - /*! - * \brief Loads the account list from the given file path. - * If the given file is an empty string (default), will load from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool loadList(const QString &file = ""); - - /*! - * \brief Saves the account list to the given file. - * If the given file is an empty string (default), will save from the default account list file. - * \return True if successful, otherwise false. - */ - virtual bool saveList(const QString &file = ""); - - /*! - * \brief Gets a pointer to the account that the user has selected as their "active" account. - * Which account is active can be overridden on a per-instance basis, but this will return the one that - * is set as active globally. - * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. - */ - virtual MojangAccountPtr activeAccount() const; - - /*! - * Sets the given account as the current active account. - * If the username given is an empty string, sets the active account to nothing. - */ - virtual void setActiveAccount(const QString &username); - - /*! - * Returns true if any of the account is at least Validated - */ - bool anyAccountIsValid(); + enum ModelRoles + { + PointerRole = 0x34B1CB48 + }; + + enum VListColumns + { + // TODO: Add icon column. + + // First column - Active? + ActiveColumn = 0, + + // Second column - Name + NameColumn, + }; + + explicit MojangAccountList(QObject *parent = 0); + + //! Gets the account at the given index. + virtual const MojangAccountPtr at(int i) const; + + //! Returns the number of accounts in the list. + virtual int count() const; + + //////// 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; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role); + + /*! + * Adds a the given Mojang account to the account list. + */ + virtual void addAccount(const MojangAccountPtr account); + + /*! + * Removes the mojang account with the given username from the account list. + */ + virtual void removeAccount(const QString &username); + + /*! + * Removes the account at the given QModelIndex. + */ + virtual void removeAccount(QModelIndex index); + + /*! + * \brief Finds an account by its username. + * \param The username of the account to find. + * \return A const pointer to the account with the given username. NULL if + * one doesn't exist. + */ + virtual MojangAccountPtr findAccount(const QString &username) const; + + /*! + * Sets the default path to save the list file to. + * If autosave is true, this list will automatically save to the given path whenever it changes. + * THIS FUNCTION DOES NOT LOAD THE LIST. If you set autosave, be sure to call loadList() immediately + * after calling this function to ensure an autosaved change doesn't overwrite the list you intended + * to load. + */ + virtual void setListFilePath(QString path, bool autosave = false); + + /*! + * \brief Loads the account list from the given file path. + * If the given file is an empty string (default), will load from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool loadList(const QString &file = ""); + + /*! + * \brief Saves the account list to the given file. + * If the given file is an empty string (default), will save from the default account list file. + * \return True if successful, otherwise false. + */ + virtual bool saveList(const QString &file = ""); + + /*! + * \brief Gets a pointer to the account that the user has selected as their "active" account. + * Which account is active can be overridden on a per-instance basis, but this will return the one that + * is set as active globally. + * \return The currently active MojangAccount. If there isn't an active account, returns a null pointer. + */ + virtual MojangAccountPtr activeAccount() const; + + /*! + * Sets the given account as the current active account. + * If the username given is an empty string, sets the active account to nothing. + */ + virtual void setActiveAccount(const QString &username); + + /*! + * Returns true if any of the account is at least Validated + */ + bool anyAccountIsValid(); signals: - /*! - * Signal emitted to indicate that the account list has changed. - * This will also fire if the value of an element in the list changes (will be implemented - * later). - */ - void listChanged(); - - /*! - * Signal emitted to indicate that the active account has changed. - */ - void activeAccountChanged(); + /*! + * Signal emitted to indicate that the account list has changed. + * This will also fire if the value of an element in the list changes (will be implemented + * later). + */ + void listChanged(); + + /*! + * Signal emitted to indicate that the active account has changed. + */ + void activeAccountChanged(); public slots: - /** - * This is called when one of the accounts changes and the list needs to be updated - */ - void accountChanged(); + /** + * This is called when one of the accounts changes and the list needs to be updated + */ + void accountChanged(); protected: - /*! - * Called whenever the list changes. - * This emits the listChanged() signal and autosaves the list (if autosave is enabled). - */ - void onListChanged(); - - /*! - * Called whenever the active account changes. - * Emits the activeAccountChanged() signal and autosaves the list if enabled. - */ - void onActiveChanged(); - - QList m_accounts; - - /*! - * Account that is currently active. - */ - MojangAccountPtr m_activeAccount; - - //! Path to the account list file. Empty string if there isn't one. - QString m_listFilePath; - - /*! - * If true, the account list will automatically save to the account list path when it changes. - * Ignored if m_listFilePath is blank. - */ - bool m_autosave = false; + /*! + * Called whenever the list changes. + * This emits the listChanged() signal and autosaves the list (if autosave is enabled). + */ + void onListChanged(); + + /*! + * Called whenever the active account changes. + * Emits the activeAccountChanged() signal and autosaves the list if enabled. + */ + void onActiveChanged(); + + QList m_accounts; + + /*! + * Account that is currently active. + */ + MojangAccountPtr m_activeAccount; + + //! Path to the account list file. Empty string if there isn't one. + QString m_listFilePath; + + /*! + * If true, the account list will automatically save to the account list path when it changes. + * Ignored if m_listFilePath is blank. + */ + bool m_autosave = false; protected slots: - /*! - * Updates this list with the given list of accounts. - * This is done by copying each account in the given list and inserting it - * into this one. - * We need to do this so that we can set the parents of the accounts are set to this - * account list. This can't be done in the load task, because the accounts 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 accounts and sets their parents correctly. - * \param accounts List of accounts whose parents should be set. - */ - virtual void updateListData(QList versions); + /*! + * Updates this list with the given list of accounts. + * This is done by copying each account in the given list and inserting it + * into this one. + * We need to do this so that we can set the parents of the accounts are set to this + * account list. This can't be done in the load task, because the accounts 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 accounts and sets their parents correctly. + * \param accounts List of accounts whose parents should be set. + */ + virtual void updateListData(QList versions); }; diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp index f17d803f..71f5d950 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.cpp +++ b/api/logic/minecraft/auth/YggdrasilTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,226 +30,226 @@ #include YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent) - : Task(parent), m_account(account) + : Task(parent), m_account(account) { - changeState(STATE_CREATED); + changeState(STATE_CREATED); } void YggdrasilTask::executeTask() { - changeState(STATE_SENDING_REQUEST); - - // Get the content of the request we're going to send to the server. - QJsonDocument doc(getRequestContent()); - - QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint()); - QNetworkRequest netRequest(reqUrl); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QByteArray requestData = doc.toJson(); - m_netReply = ENV.qnam().post(netRequest, requestData); - connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); - connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); - connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); - timeout_keeper.setSingleShot(true); - timeout_keeper.start(timeout_max); - counter.setSingleShot(false); - counter.start(time_step); - progress(0, timeout_max); - connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); - connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); + changeState(STATE_SENDING_REQUEST); + + // Get the content of the request we're going to send to the server. + QJsonDocument doc(getRequestContent()); + + QUrl reqUrl(URLConstants::AUTH_BASE + getEndpoint()); + QNetworkRequest netRequest(reqUrl); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QByteArray requestData = doc.toJson(); + m_netReply = ENV.qnam().post(netRequest, requestData); + connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply); + connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers); + connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors); + timeout_keeper.setSingleShot(true); + timeout_keeper.start(timeout_max); + counter.setSingleShot(false); + counter.start(time_step); + progress(0, timeout_max); + connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout); + connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat); } void YggdrasilTask::refreshTimers(qint64, qint64) { - timeout_keeper.stop(); - timeout_keeper.start(timeout_max); - progress(count = 0, timeout_max); + timeout_keeper.stop(); + timeout_keeper.start(timeout_max); + progress(count = 0, timeout_max); } void YggdrasilTask::heartbeat() { - count += time_step; - progress(count, timeout_max); + count += time_step; + progress(count, timeout_max); } bool YggdrasilTask::abort() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_USER; - m_netReply->abort(); - return true; + progress(timeout_max, timeout_max); + // TODO: actually use this in a meaningful way + m_aborted = YggdrasilTask::BY_USER; + m_netReply->abort(); + return true; } void YggdrasilTask::abortByTimeout() { - progress(timeout_max, timeout_max); - // TODO: actually use this in a meaningful way - m_aborted = YggdrasilTask::BY_TIMEOUT; - m_netReply->abort(); + progress(timeout_max, timeout_max); + // TODO: actually use this in a meaningful way + m_aborted = YggdrasilTask::BY_TIMEOUT; + m_netReply->abort(); } void YggdrasilTask::sslErrors(QList errors) { - int i = 1; - for (auto error : errors) - { - qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } + int i = 1; + for (auto error : errors) + { + qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } } void YggdrasilTask::processReply() { - changeState(STATE_PROCESSING_RESPONSE); - - switch (m_netReply->error()) - { - case QNetworkReply::NoError: - break; - case QNetworkReply::TimeoutError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); - return; - case QNetworkReply::OperationCanceledError: - changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); - return; - case QNetworkReply::SslHandshakeFailedError: - changeState( - STATE_FAILED_SOFT, - tr("SSL Handshake failed.
There might be a few causes for it:
" - "
    " - "
  • You use Windows XP and need to update " - "your root certificates
  • " - "
  • Some device on your network is interfering with SSL traffic. In that case, " - "you have bigger worries than Minecraft not starting.
  • " - "
  • Possibly something else. Check the MultiMC log file for details
  • " - "
")); - return; - // used for invalid credentials and similar errors. Fall through. - case QNetworkReply::ContentAccessDenied: - case QNetworkReply::ContentOperationNotPermittedError: - break; - default: - changeState(STATE_FAILED_SOFT, - tr("Authentication operation failed due to a network error: %1 (%2)") - .arg(m_netReply->errorString()).arg(m_netReply->error())); - return; - } - - // Try to parse the response regardless of the response code. - // Sometimes the auth server will give more information and an error code. - QJsonParseError jsonError; - QByteArray replyData = m_netReply->readAll(); - QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); - // Check the response code. - int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (responseCode == 200) - { - // If the response code was 200, then there shouldn't be an error. Make sure - // anyways. - // Also, sometimes an empty reply indicates success. If there was no data received, - // pass an empty json object to the processResponse function. - if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) - { - processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); - return; - } - else - { - changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " - "JSON response: %1 at offset %2.") - .arg(jsonError.errorString()) - .arg(jsonError.offset)); - qCritical() << replyData; - } - return; - } - - // If the response code was not 200, then Yggdrasil may have given us information - // about the error. - // If we can parse the response, then get information from it. Otherwise just say - // there was an unknown error. - if (jsonError.error == QJsonParseError::NoError) - { - // We were able to parse the server's response. Woo! - // Call processError. If a subclass has overridden it then they'll handle their - // stuff there. - qDebug() << "The request failed, but the server gave us an error message. " - "Processing error."; - processError(doc.object()); - } - else - { - // The server didn't say anything regarding the error. Give the user an unknown - // error. - qDebug() - << "The request failed and the server gave no error message. Unknown error."; - changeState(STATE_FAILED_SOFT, - tr("An unknown error occurred when trying to communicate with the " - "authentication server: %1").arg(m_netReply->errorString())); - } + changeState(STATE_PROCESSING_RESPONSE); + + switch (m_netReply->error()) + { + case QNetworkReply::NoError: + break; + case QNetworkReply::TimeoutError: + changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out.")); + return; + case QNetworkReply::OperationCanceledError: + changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled.")); + return; + case QNetworkReply::SslHandshakeFailedError: + changeState( + STATE_FAILED_SOFT, + tr("SSL Handshake failed.
There might be a few causes for it:
" + "
    " + "
  • You use Windows XP and need to update " + "your root certificates
  • " + "
  • Some device on your network is interfering with SSL traffic. In that case, " + "you have bigger worries than Minecraft not starting.
  • " + "
  • Possibly something else. Check the MultiMC log file for details
  • " + "
")); + return; + // used for invalid credentials and similar errors. Fall through. + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + break; + default: + changeState(STATE_FAILED_SOFT, + tr("Authentication operation failed due to a network error: %1 (%2)") + .arg(m_netReply->errorString()).arg(m_netReply->error())); + return; + } + + // Try to parse the response regardless of the response code. + // Sometimes the auth server will give more information and an error code. + QJsonParseError jsonError; + QByteArray replyData = m_netReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError); + // Check the response code. + int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (responseCode == 200) + { + // If the response code was 200, then there shouldn't be an error. Make sure + // anyways. + // Also, sometimes an empty reply indicates success. If there was no data received, + // pass an empty json object to the processResponse function. + if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) + { + processResponse(replyData.size() > 0 ? doc.object() : QJsonObject()); + return; + } + else + { + changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response " + "JSON response: %1 at offset %2.") + .arg(jsonError.errorString()) + .arg(jsonError.offset)); + qCritical() << replyData; + } + return; + } + + // If the response code was not 200, then Yggdrasil may have given us information + // about the error. + // If we can parse the response, then get information from it. Otherwise just say + // there was an unknown error. + if (jsonError.error == QJsonParseError::NoError) + { + // We were able to parse the server's response. Woo! + // Call processError. If a subclass has overridden it then they'll handle their + // stuff there. + qDebug() << "The request failed, but the server gave us an error message. " + "Processing error."; + processError(doc.object()); + } + else + { + // The server didn't say anything regarding the error. Give the user an unknown + // error. + qDebug() + << "The request failed and the server gave no error message. Unknown error."; + changeState(STATE_FAILED_SOFT, + tr("An unknown error occurred when trying to communicate with the " + "authentication server: %1").arg(m_netReply->errorString())); + } } void YggdrasilTask::processError(QJsonObject responseData) { - QJsonValue errorVal = responseData.value("error"); - QJsonValue errorMessageValue = responseData.value("errorMessage"); - QJsonValue causeVal = responseData.value("cause"); - - if (errorVal.isString() && errorMessageValue.isString()) - { - m_error = std::shared_ptr(new Error{ - errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); - changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); - } - else - { - // Error is not in standard format. Don't set m_error and return unknown error. - changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); - } + QJsonValue errorVal = responseData.value("error"); + QJsonValue errorMessageValue = responseData.value("errorMessage"); + QJsonValue causeVal = responseData.value("cause"); + + if (errorVal.isString() && errorMessageValue.isString()) + { + m_error = std::shared_ptr(new Error{ + errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")}); + changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose); + } + else + { + // Error is not in standard format. Don't set m_error and return unknown error. + changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred.")); + } } QString YggdrasilTask::getStateMessage() const { - switch (m_state) - { - case STATE_CREATED: - return "Waiting..."; - case STATE_SENDING_REQUEST: - return tr("Sending request to auth servers..."); - case STATE_PROCESSING_RESPONSE: - return tr("Processing response from servers..."); - case STATE_SUCCEEDED: - return tr("Authentication task succeeded."); - case STATE_FAILED_SOFT: - return tr("Failed to contact the authentication server."); - case STATE_FAILED_HARD: - return tr("Failed to authenticate."); - default: - return tr("..."); - } + switch (m_state) + { + case STATE_CREATED: + return "Waiting..."; + case STATE_SENDING_REQUEST: + return tr("Sending request to auth servers..."); + case STATE_PROCESSING_RESPONSE: + return tr("Processing response from servers..."); + case STATE_SUCCEEDED: + return tr("Authentication task succeeded."); + case STATE_FAILED_SOFT: + return tr("Failed to contact the authentication server."); + case STATE_FAILED_HARD: + return tr("Failed to authenticate."); + default: + return tr("..."); + } } void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason) { - m_state = newState; - setStatus(getStateMessage()); - if (newState == STATE_SUCCEEDED) - { - emitSucceeded(); - } - else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) - { - emitFailed(reason); - } + m_state = newState; + setStatus(getStateMessage()); + if (newState == STATE_SUCCEEDED) + { + emitSucceeded(); + } + else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT) + { + emitFailed(reason); + } } YggdrasilTask::State YggdrasilTask::state() { - return m_state; + return m_state; } diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h index b165d3e9..c5352386 100644 --- a/api/logic/minecraft/auth/YggdrasilTask.h +++ b/api/logic/minecraft/auth/YggdrasilTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,120 +31,121 @@ class QNetworkReply; */ class YggdrasilTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); - - /** - * assign a session to this task. the session will be filled with required infomration - * upon completion - */ - void assignSession(AuthSessionPtr session) - { - m_session = session; - } - - /// get the assigned session for filling with information. - AuthSessionPtr getAssignedSession() - { - return m_session; - } - - /** - * Class describing a Yggdrasil error response. - */ - struct Error - { - QString m_errorMessageShort; - QString m_errorMessageVerbose; - QString m_cause; - }; - - enum AbortedBy - { - BY_NOTHING, - BY_USER, - BY_TIMEOUT - } m_aborted = BY_NOTHING; - - /** - * Enum for describing the state of the current task. - * Used by the getStateMessage function to determine what the status message should be. - */ - enum State - { - STATE_CREATED, - STATE_SENDING_REQUEST, - STATE_PROCESSING_RESPONSE, - STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated - STATE_FAILED_HARD, //!< hard failure. auth is invalid - STATE_SUCCEEDED - } m_state = STATE_CREATED; + explicit YggdrasilTask(MojangAccount * account, QObject *parent = 0); + virtual ~YggdrasilTask() {}; + + /** + * assign a session to this task. the session will be filled with required infomration + * upon completion + */ + void assignSession(AuthSessionPtr session) + { + m_session = session; + } + + /// get the assigned session for filling with information. + AuthSessionPtr getAssignedSession() + { + return m_session; + } + + /** + * Class describing a Yggdrasil error response. + */ + struct Error + { + QString m_errorMessageShort; + QString m_errorMessageVerbose; + QString m_cause; + }; + + enum AbortedBy + { + BY_NOTHING, + BY_USER, + BY_TIMEOUT + } m_aborted = BY_NOTHING; + + /** + * Enum for describing the state of the current task. + * Used by the getStateMessage function to determine what the status message should be. + */ + enum State + { + STATE_CREATED, + STATE_SENDING_REQUEST, + STATE_PROCESSING_RESPONSE, + STATE_FAILED_SOFT, //!< soft failure. this generally means the user auth details haven't been invalidated + STATE_FAILED_HARD, //!< hard failure. auth is invalid + STATE_SUCCEEDED + } m_state = STATE_CREATED; protected: - virtual void executeTask() override; - - /** - * Gets the JSON object that will be sent to the authentication server. - * Should be overridden by subclasses. - */ - virtual QJsonObject getRequestContent() const = 0; - - /** - * Gets the endpoint to POST to. - * No leading slash. - */ - virtual QString getEndpoint() const = 0; - - /** - * Processes the response received from the server. - * If an error occurred, this should emit a failed signal and return false. - * If Yggdrasil gave an error response, it should call setError() first, and then return false. - * Otherwise, it should return true. - * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with - * an empty QJsonObject. - */ - virtual void processResponse(QJsonObject responseData) = 0; - - /** - * Processes an error response received from the server. - * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. - * \returns a QString error message that will be passed to emitFailed. - */ - virtual void processError(QJsonObject responseData); - - /** - * Returns the state message for the given state. - * Used to set the status message for the task. - * Should be overridden by subclasses that want to change messages for a given state. - */ - virtual QString getStateMessage() const; + virtual void executeTask() override; + + /** + * Gets the JSON object that will be sent to the authentication server. + * Should be overridden by subclasses. + */ + virtual QJsonObject getRequestContent() const = 0; + + /** + * Gets the endpoint to POST to. + * No leading slash. + */ + virtual QString getEndpoint() const = 0; + + /** + * Processes the response received from the server. + * If an error occurred, this should emit a failed signal and return false. + * If Yggdrasil gave an error response, it should call setError() first, and then return false. + * Otherwise, it should return true. + * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with + * an empty QJsonObject. + */ + virtual void processResponse(QJsonObject responseData) = 0; + + /** + * Processes an error response received from the server. + * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error. + * \returns a QString error message that will be passed to emitFailed. + */ + virtual void processError(QJsonObject responseData); + + /** + * Returns the state message for the given state. + * Used to set the status message for the task. + * Should be overridden by subclasses that want to change messages for a given state. + */ + virtual QString getStateMessage() const; protected slots: - void processReply(); - void refreshTimers(qint64, qint64); - void heartbeat(); - void sslErrors(QList); + void processReply(); + void refreshTimers(qint64, qint64); + void heartbeat(); + void sslErrors(QList); - void changeState(State newState, QString reason=QString()); + void changeState(State newState, QString reason=QString()); public slots: - virtual bool abort() override; - void abortByTimeout(); - State state(); + virtual bool abort() override; + void abortByTimeout(); + State state(); protected: - // FIXME: segfault disaster waiting to happen - MojangAccount *m_account = nullptr; - QNetworkReply *m_netReply = nullptr; - std::shared_ptr m_error; - QTimer timeout_keeper; - QTimer counter; - int count = 0; // num msec since time reset - - const int timeout_max = 30000; - const int time_step = 50; - - AuthSessionPtr m_session; + // FIXME: segfault disaster waiting to happen + MojangAccount *m_account = nullptr; + QNetworkReply *m_netReply = nullptr; + std::shared_ptr m_error; + QTimer timeout_keeper; + QTimer counter; + int count = 0; // num msec since time reset + + const int timeout_max = 30000; + const int time_step = 50; + + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp index ff4e7d47..8286c7a4 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,177 +26,177 @@ #include AuthenticateTask::AuthenticateTask(MojangAccount * account, const QString &password, - QObject *parent) - : YggdrasilTask(account, parent), m_password(password) + QObject *parent) + : YggdrasilTask(account, parent), m_password(password) { } QJsonObject AuthenticateTask::getRequestContent() const { - /* - * { - * "agent": { // optional - * "name": "Minecraft", // So far this is the only encountered value - * "version": 1 // This number might be increased - * // by the vanilla client in the future - * }, - * "username": "mojang account name", // Can be an email address or player name for - // unmigrated accounts - * "password": "mojang account password", - * "clientToken": "client identifier" // optional - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - - { - QJsonObject agent; - // C++ makes string literals void* for some stupid reason, so we have to tell it - // QString... Thanks Obama. - agent.insert("name", QString("Minecraft")); - agent.insert("version", 1); - req.insert("agent", agent); - } - - req.insert("username", m_account->username()); - req.insert("password", m_password); - req.insert("requestUser", true); - - // If we already have a client token, give it to the server. - // Otherwise, let the server give us one. - - if(m_account->m_clientToken.isEmpty()) - { - auto uuid = QUuid::createUuid(); - auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); - m_account->m_clientToken = uuidString; - } - req.insert("clientToken", m_account->m_clientToken); - - return req; + /* + * { + * "agent": { // optional + * "name": "Minecraft", // So far this is the only encountered value + * "version": 1 // This number might be increased + * // by the vanilla client in the future + * }, + * "username": "mojang account name", // Can be an email address or player name for + // unmigrated accounts + * "password": "mojang account password", + * "clientToken": "client identifier" // optional + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + + { + QJsonObject agent; + // C++ makes string literals void* for some stupid reason, so we have to tell it + // QString... Thanks Obama. + agent.insert("name", QString("Minecraft")); + agent.insert("version", 1); + req.insert("agent", agent); + } + + req.insert("username", m_account->username()); + req.insert("password", m_password); + req.insert("requestUser", true); + + // If we already have a client token, give it to the server. + // Otherwise, let the server give us one. + + if(m_account->m_clientToken.isEmpty()) + { + auto uuid = QUuid::createUuid(); + auto uuidString = uuid.toString().remove('{').remove('-').remove('}'); + m_account->m_clientToken = uuidString; + } + req.insert("clientToken", m_account->m_clientToken); + + return req; } void AuthenticateTask::processResponse(QJsonObject responseData) { - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - qDebug() << "Getting client token."; - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } - // Set the client token. - m_account->m_clientToken = clientToken; - - // Now, we set the access token. - qDebug() << "Getting access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } - // Set the access token. - m_account->m_accessToken = accessToken; - - // Now we load the list of available profiles. - // Mojang hasn't yet implemented the profile system, - // but we might as well support what's there so we - // don't have trouble implementing it later. - qDebug() << "Loading profile list."; - QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); - QList loadedProfiles; - for (auto iter : availableProfiles) - { - QJsonObject profile = iter.toObject(); - // Profiles are easy, we just need their ID and name. - QString id = profile.value("id").toString(""); - QString name = profile.value("name").toString(""); - bool legacy = profile.value("legacy").toBool(false); - - if (id.isEmpty() || name.isEmpty()) - { - // This should never happen, but we might as well - // warn about it if it does so we can debug it easily. - // You never know when Mojang might do something truly derpy. - qWarning() << "Found entry in available profiles list with missing ID or name " - "field. Ignoring it."; - } - - // Now, add a new AccountProfile entry to the list. - loadedProfiles.append({id, name, legacy}); - } - // Put the list of profiles we loaded into the MojangAccount object. - m_account->m_profiles = loadedProfiles; - - // Finally, we set the current profile to the correct value. This is pretty simple. - // We do need to make sure that the current profile that the server gave us - // is actually in the available profiles list. - // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). - qDebug() << "Setting current profile."; - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (currentProfileId.isEmpty()) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); - return; - } - if (!m_account->setCurrentProfile(currentProfileId)) - { - changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); - return; - } - - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } - - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading authentication response."; - changeState(STATE_SUCCEEDED); + // Read the response data. We need to get the client token, access token, and the selected + // profile. + qDebug() << "Processing authentication response."; + // qDebug() << responseData; + // If we already have a client token, make sure the one the server gave us matches our + // existing one. + qDebug() << "Getting client token."; + QString clientToken = responseData.value("clientToken").toString(""); + if (clientToken.isEmpty()) + { + // Fail if the server gave us an empty client token + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); + return; + } + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) + { + changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); + return; + } + // Set the client token. + m_account->m_clientToken = clientToken; + + // Now, we set the access token. + qDebug() << "Getting access token."; + QString accessToken = responseData.value("accessToken").toString(""); + if (accessToken.isEmpty()) + { + // Fail if the server didn't give us an access token. + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); + return; + } + // Set the access token. + m_account->m_accessToken = accessToken; + + // Now we load the list of available profiles. + // Mojang hasn't yet implemented the profile system, + // but we might as well support what's there so we + // don't have trouble implementing it later. + qDebug() << "Loading profile list."; + QJsonArray availableProfiles = responseData.value("availableProfiles").toArray(); + QList loadedProfiles; + for (auto iter : availableProfiles) + { + QJsonObject profile = iter.toObject(); + // Profiles are easy, we just need their ID and name. + QString id = profile.value("id").toString(""); + QString name = profile.value("name").toString(""); + bool legacy = profile.value("legacy").toBool(false); + + if (id.isEmpty() || name.isEmpty()) + { + // This should never happen, but we might as well + // warn about it if it does so we can debug it easily. + // You never know when Mojang might do something truly derpy. + qWarning() << "Found entry in available profiles list with missing ID or name " + "field. Ignoring it."; + } + + // Now, add a new AccountProfile entry to the list. + loadedProfiles.append({id, name, legacy}); + } + // Put the list of profiles we loaded into the MojangAccount object. + m_account->m_profiles = loadedProfiles; + + // Finally, we set the current profile to the correct value. This is pretty simple. + // We do need to make sure that the current profile that the server gave us + // is actually in the available profiles list. + // If it isn't, we'll just fail horribly (*shouldn't* ever happen, but you never know). + qDebug() << "Setting current profile."; + QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); + QString currentProfileId = currentProfile.value("id").toString(""); + if (currentProfileId.isEmpty()) + { + changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify a currently selected profile. The account exists, but likely isn't premium.")); + return; + } + if (!m_account->setCurrentProfile(currentProfileId)) + { + changeState(STATE_FAILED_HARD, tr("Authentication server specified a selected profile that wasn't in the available profiles list.")); + return; + } + + // this is what the vanilla launcher passes to the userProperties launch param + if (responseData.contains("user")) + { + User u; + auto obj = responseData.value("user").toObject(); + u.id = obj.value("id").toString(); + auto propArray = obj.value("properties").toArray(); + for (auto prop : propArray) + { + auto propTuple = prop.toObject(); + auto name = propTuple.value("name").toString(); + auto value = propTuple.value("value").toString(); + u.properties.insert(name, value); + } + m_account->m_user = u; + } + + // We've made it through the minefield of possible errors. Return true to indicate that + // we've succeeded. + qDebug() << "Finished reading authentication response."; + changeState(STATE_SUCCEEDED); } QString AuthenticateTask::getEndpoint() const { - return "authenticate"; + return "authenticate"; } QString AuthenticateTask::getStateMessage() const { - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Authenticating: Sending request..."); - case STATE_PROCESSING_RESPONSE: - return tr("Authenticating: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case STATE_SENDING_REQUEST: + return tr("Authenticating: Sending request..."); + case STATE_PROCESSING_RESPONSE: + return tr("Authenticating: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h index ab7a6e64..e95837a9 100644 --- a/api/logic/minecraft/auth/flows/AuthenticateTask.h +++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,19 +28,19 @@ */ class AuthenticateTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); + AuthenticateTask(MojangAccount *account, const QString &password, QObject *parent = 0); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; private: - QString m_password; + QString m_password; }; diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp index 73050907..7a789f42 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.cpp +++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,116 +29,116 @@ RefreshTask::RefreshTask(MojangAccount *account) : YggdrasilTask(account) QJsonObject RefreshTask::getRequestContent() const { - /* - * { - * "clientToken": "client identifier" - * "accessToken": "current access token to be refreshed" - * "selectedProfile": // specifying this causes errors - * { - * "id": "profile ID" - * "name": "profile name" - * } - * "requestUser": true/false // request the user structure - * } - */ - QJsonObject req; - req.insert("clientToken", m_account->m_clientToken); - req.insert("accessToken", m_account->m_accessToken); - /* - { - auto currentProfile = m_account->currentProfile(); - QJsonObject profile; - profile.insert("id", currentProfile->id()); - profile.insert("name", currentProfile->name()); - req.insert("selectedProfile", profile); - } - */ - req.insert("requestUser", true); + /* + * { + * "clientToken": "client identifier" + * "accessToken": "current access token to be refreshed" + * "selectedProfile": // specifying this causes errors + * { + * "id": "profile ID" + * "name": "profile name" + * } + * "requestUser": true/false // request the user structure + * } + */ + QJsonObject req; + req.insert("clientToken", m_account->m_clientToken); + req.insert("accessToken", m_account->m_accessToken); + /* + { + auto currentProfile = m_account->currentProfile(); + QJsonObject profile; + profile.insert("id", currentProfile->id()); + profile.insert("name", currentProfile->name()); + req.insert("selectedProfile", profile); + } + */ + req.insert("requestUser", true); - return req; + return req; } void RefreshTask::processResponse(QJsonObject responseData) { - // Read the response data. We need to get the client token, access token, and the selected - // profile. - qDebug() << "Processing authentication response."; + // Read the response data. We need to get the client token, access token, and the selected + // profile. + qDebug() << "Processing authentication response."; - // qDebug() << responseData; - // If we already have a client token, make sure the one the server gave us matches our - // existing one. - QString clientToken = responseData.value("clientToken").toString(""); - if (clientToken.isEmpty()) - { - // Fail if the server gave us an empty client token - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); - return; - } - if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) - { - changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); - return; - } + // qDebug() << responseData; + // If we already have a client token, make sure the one the server gave us matches our + // existing one. + QString clientToken = responseData.value("clientToken").toString(""); + if (clientToken.isEmpty()) + { + // Fail if the server gave us an empty client token + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send a client token.")); + return; + } + if (!m_account->m_clientToken.isEmpty() && clientToken != m_account->m_clientToken) + { + changeState(STATE_FAILED_HARD, tr("Authentication server attempted to change the client token. This isn't supported.")); + return; + } - // Now, we set the access token. - qDebug() << "Getting new access token."; - QString accessToken = responseData.value("accessToken").toString(""); - if (accessToken.isEmpty()) - { - // Fail if the server didn't give us an access token. - changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); - return; - } + // Now, we set the access token. + qDebug() << "Getting new access token."; + QString accessToken = responseData.value("accessToken").toString(""); + if (accessToken.isEmpty()) + { + // Fail if the server didn't give us an access token. + changeState(STATE_FAILED_HARD, tr("Authentication server didn't send an access token.")); + return; + } - // we validate that the server responded right. (our current profile = returned current - // profile) - QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); - QString currentProfileId = currentProfile.value("id").toString(""); - if (m_account->currentProfile()->id != currentProfileId) - { - changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); - return; - } + // we validate that the server responded right. (our current profile = returned current + // profile) + QJsonObject currentProfile = responseData.value("selectedProfile").toObject(); + QString currentProfileId = currentProfile.value("id").toString(""); + if (m_account->currentProfile()->id != currentProfileId) + { + changeState(STATE_FAILED_HARD, tr("Authentication server didn't specify the same prefile as expected.")); + return; + } - // this is what the vanilla launcher passes to the userProperties launch param - if (responseData.contains("user")) - { - User u; - auto obj = responseData.value("user").toObject(); - u.id = obj.value("id").toString(); - auto propArray = obj.value("properties").toArray(); - for (auto prop : propArray) - { - auto propTuple = prop.toObject(); - auto name = propTuple.value("name").toString(); - auto value = propTuple.value("value").toString(); - u.properties.insert(name, value); - } - m_account->m_user = u; - } + // this is what the vanilla launcher passes to the userProperties launch param + if (responseData.contains("user")) + { + User u; + auto obj = responseData.value("user").toObject(); + u.id = obj.value("id").toString(); + auto propArray = obj.value("properties").toArray(); + for (auto prop : propArray) + { + auto propTuple = prop.toObject(); + auto name = propTuple.value("name").toString(); + auto value = propTuple.value("value").toString(); + u.properties.insert(name, value); + } + m_account->m_user = u; + } - // We've made it through the minefield of possible errors. Return true to indicate that - // we've succeeded. - qDebug() << "Finished reading refresh response."; - // Reset the access token. - m_account->m_accessToken = accessToken; - changeState(STATE_SUCCEEDED); + // We've made it through the minefield of possible errors. Return true to indicate that + // we've succeeded. + qDebug() << "Finished reading refresh response."; + // Reset the access token. + m_account->m_accessToken = accessToken; + changeState(STATE_SUCCEEDED); } QString RefreshTask::getEndpoint() const { - return "refresh"; + return "refresh"; } QString RefreshTask::getStateMessage() const { - switch (m_state) - { - case STATE_SENDING_REQUEST: - return tr("Refreshing login token..."); - case STATE_PROCESSING_RESPONSE: - return tr("Refreshing login token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case STATE_SENDING_REQUEST: + return tr("Refreshing login token..."); + case STATE_PROCESSING_RESPONSE: + return tr("Refreshing login token: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h index a97b54e6..b2dfd261 100644 --- a/api/logic/minecraft/auth/flows/RefreshTask.h +++ b/api/logic/minecraft/auth/flows/RefreshTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,17 +28,17 @@ */ class RefreshTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - RefreshTask(MojangAccount * account); + RefreshTask(MojangAccount * account); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; }; diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp index b6d6c823..01f0f819 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.cpp +++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp @@ -1,5 +1,5 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,37 +25,37 @@ #include ValidateTask::ValidateTask(MojangAccount * account, QObject *parent) - : YggdrasilTask(account, parent) + : YggdrasilTask(account, parent) { } QJsonObject ValidateTask::getRequestContent() const { - QJsonObject req; - req.insert("accessToken", m_account->m_accessToken); - return req; + QJsonObject req; + req.insert("accessToken", m_account->m_accessToken); + return req; } void ValidateTask::processResponse(QJsonObject responseData) { - // Assume that if processError wasn't called, then the request was successful. - changeState(YggdrasilTask::STATE_SUCCEEDED); + // Assume that if processError wasn't called, then the request was successful. + changeState(YggdrasilTask::STATE_SUCCEEDED); } QString ValidateTask::getEndpoint() const { - return "validate"; + return "validate"; } QString ValidateTask::getStateMessage() const { - switch (m_state) - { - case YggdrasilTask::STATE_SENDING_REQUEST: - return tr("Validating access token: Sending request..."); - case YggdrasilTask::STATE_PROCESSING_RESPONSE: - return tr("Validating access token: Processing response..."); - default: - return YggdrasilTask::getStateMessage(); - } + switch (m_state) + { + case YggdrasilTask::STATE_SENDING_REQUEST: + return tr("Validating access token: Sending request..."); + case YggdrasilTask::STATE_PROCESSING_RESPONSE: + return tr("Validating access token: Processing response..."); + default: + return YggdrasilTask::getStateMessage(); + } } diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h index 77de24c7..dcb76b2b 100644 --- a/api/logic/minecraft/auth/flows/ValidateTask.h +++ b/api/logic/minecraft/auth/flows/ValidateTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,18 +30,18 @@ */ class ValidateTask : public YggdrasilTask { - Q_OBJECT + Q_OBJECT public: - ValidateTask(MojangAccount *account, QObject *parent = 0); + ValidateTask(MojangAccount *account, QObject *parent = 0); protected: - virtual QJsonObject getRequestContent() const override; + virtual QJsonObject getRequestContent() const override; - virtual QString getEndpoint() const override; + virtual QString getEndpoint() const override; - virtual void processResponse(QJsonObject responseData) override; + virtual void processResponse(QJsonObject responseData) override; - virtual QString getStateMessage() const override; + virtual QString getStateMessage() const override; private: }; diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp index a05d0f8d..c6465469 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.cpp +++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,140 +23,140 @@ #include #include -ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction() +ForgeXzDownload::ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry) : NetAction() { - m_entry = entry; - m_target_path = entry->getFullPath(); - m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX"); - m_status = Job_NotStarted; - m_url_path = relative_path; - m_url = "http://files.minecraftforge.net/maven/" + m_url_path + ".pack.xz"; + m_entry = entry; + m_target_path = entry->getFullPath(); + m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX"); + m_status = Job_NotStarted; + m_url_path = relative_path; + m_url = url + ".pack.xz"; } void ForgeXzDownload::start() { - if(m_status == Job_Aborted) - { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); - return; - } - m_status = Job_InProgress; - if (!m_entry->isStale()) - { - m_status = Job_Finished; - emit succeeded(m_index_within_job); - return; - } - // can we actually create the real, final file? - if (!FS::ensureFilePathExists(m_target_path)) - { - m_status = Job_Failed; - emit failed(m_index_within_job); - return; - } - - qDebug() << "Downloading " << m_url.toString(); - QNetworkRequest request(m_url); - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); - - QNetworkReply *rep = ENV.qnam().get(request); - - m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } + m_status = Job_InProgress; + if (!m_entry->isStale()) + { + m_status = Job_Finished; + emit succeeded(m_index_within_job); + return; + } + // can we actually create the real, final file? + if (!FS::ensureFilePathExists(m_target_path)) + { + m_status = Job_Failed; + emit failed(m_index_within_job); + return; + } + + qDebug() << "Downloading " << m_url.toString(); + QNetworkRequest request(m_url); + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)"); + + QNetworkReply *rep = ENV.qnam().get(request); + + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead())); } void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { - qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; - } - else - { - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; - } + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } } void ForgeXzDownload::failAndTryNextMirror() { - m_status = Job_Failed; - emit failed(m_index_within_job); + m_status = Job_Failed; + emit failed(m_index_within_job); } void ForgeXzDownload::downloadFinished() { - // if the download succeeded - if (m_status != Job_Failed && m_status != Job_Aborted) - { - // nothing went wrong... - m_status = Job_Finished; - if (m_pack200_xz_file.isOpen()) - { - // we actually downloaded something! process and isntall it - decompressAndInstall(); - return; - } - else - { - // something bad happened -- on the local machine! - m_status = Job_Failed; - m_pack200_xz_file.remove(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - } - else if(m_status == Job_Aborted) - { - m_pack200_xz_file.remove(); - m_reply.reset(); - emit failed(m_index_within_job); - emit aborted(m_index_within_job); - return; - } - // else the download failed - else - { - m_status = Job_Failed; - m_pack200_xz_file.close(); - m_pack200_xz_file.remove(); - m_reply.reset(); - failAndTryNextMirror(); - return; - } + // if the download succeeded + if (m_status != Job_Failed && m_status != Job_Aborted) + { + // nothing went wrong... + m_status = Job_Finished; + if (m_pack200_xz_file.isOpen()) + { + // we actually downloaded something! process and isntall it + decompressAndInstall(); + return; + } + else + { + // something bad happened -- on the local machine! + m_status = Job_Failed; + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + } + else if(m_status == Job_Aborted) + { + m_pack200_xz_file.remove(); + m_reply.reset(); + emit failed(m_index_within_job); + emit aborted(m_index_within_job); + return; + } + // else the download failed + else + { + m_status = Job_Failed; + m_pack200_xz_file.close(); + m_pack200_xz_file.remove(); + m_reply.reset(); + failAndTryNextMirror(); + return; + } } void ForgeXzDownload::downloadReadyRead() { - if (!m_pack200_xz_file.isOpen()) - { - if (!m_pack200_xz_file.open()) - { - /* - * Can't open the file... the job failed - */ - m_reply->abort(); - emit failed(m_index_within_job); - return; - } - } - m_pack200_xz_file.write(m_reply->readAll()); + if (!m_pack200_xz_file.isOpen()) + { + if (!m_pack200_xz_file.open()) + { + /* + * Can't open the file... the job failed + */ + m_reply->abort(); + emit failed(m_index_within_job); + return; + } + } + m_pack200_xz_file.write(m_reply->readAll()); } #include "xz.h" @@ -169,225 +169,225 @@ const size_t buffer_size = 8196; // NOTE: once this gets here, it can't be aborted anymore. we don't care. void ForgeXzDownload::decompressAndInstall() { - // rewind the downloaded temp file - m_pack200_xz_file.seek(0); - // de-xz'd file - QTemporaryFile pack200_file("./dl_temp.XXXXXX"); - pack200_file.open(); - - bool xz_success = false; - // first, de-xz - { - uint8_t in[buffer_size]; - uint8_t out[buffer_size]; - struct xz_buf b; - struct xz_dec *s; - enum xz_ret ret; - xz_crc32_init(); - xz_crc64_init(); - s = xz_dec_init(XZ_DYNALLOC, 1 << 26); - if (s == nullptr) - { - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - b.in = in; - b.in_pos = 0; - b.in_size = 0; - b.out = out; - b.out_pos = 0; - b.out_size = buffer_size; - while (!xz_success) - { - if (b.in_pos == b.in_size) - { - b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in)); - b.in_pos = 0; - } - - ret = xz_dec_run(s, &b); - - if (b.out_pos == sizeof(out)) - { - auto wresult = pack200_file.write((char *)out, b.out_pos); - if (wresult < 0 || size_t(wresult) != b.out_pos) - { - // msg = "Write error\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - - b.out_pos = 0; - } - - if (ret == XZ_OK) - continue; - - if (ret == XZ_UNSUPPORTED_CHECK) - { - // unsupported check. this is OK, but we should log this - continue; - } - - auto wresult = pack200_file.write((char *)out, b.out_pos); - if (wresult < 0 || size_t(wresult) != b.out_pos) - { - // write error - pack200_file.close(); - xz_dec_end(s); - return; - } - - switch (ret) - { - case XZ_STREAM_END: - xz_dec_end(s); - xz_success = true; - break; - - case XZ_MEM_ERROR: - qCritical() << "Memory allocation failed\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_MEMLIMIT_ERROR: - qCritical() << "Memory usage limit reached\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_FORMAT_ERROR: - qCritical() << "Not a .xz file\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_OPTIONS_ERROR: - qCritical() << "Unsupported options in the .xz headers\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - case XZ_DATA_ERROR: - case XZ_BUF_ERROR: - qCritical() << "File is corrupt\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - - default: - qCritical() << "Bug!\n"; - xz_dec_end(s); - failAndTryNextMirror(); - return; - } - } - } - m_pack200_xz_file.remove(); - - // revert pack200 - pack200_file.seek(0); - int handle_in = pack200_file.handle(); - // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects. - if(handle_in == -1) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - int handle_in_dup = dup (handle_in); - if(handle_in_dup == -1) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - FILE *file_in = fdopen (handle_in_dup, "rb"); - if(!file_in) - { - qCritical() << "Error reopening " << pack200_file.fileName(); - failAndTryNextMirror(); - return; - } - QFile qfile_out(m_target_path); - if(!qfile_out.open(QIODevice::WriteOnly)) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - int handle_out = qfile_out.handle(); - if(handle_out == -1) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - int handle_out_dup = dup (handle_out); - if(handle_out_dup == -1) - { - qCritical() << "Error reopening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - FILE *file_out = fdopen (handle_out_dup, "wb"); - if(!file_out) - { - qCritical() << "Error opening " << qfile_out.fileName(); - failAndTryNextMirror(); - return; - } - try - { - // NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above. - unpack_200(file_in, file_out); - } - catch (std::runtime_error &err) - { - m_status = Job_Failed; - qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what(); - QFile f(m_target_path); - if (f.exists()) - f.remove(); - failAndTryNextMirror(); - return; - } - pack200_file.remove(); - - QFile jar_file(m_target_path); - - if (!jar_file.open(QIODevice::ReadOnly)) - { - jar_file.remove(); - failAndTryNextMirror(); - return; - } - auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5); - m_entry->setMD5Sum(hash.toHex().constData()); - jar_file.close(); - - QFileInfo output_file_info(m_target_path); - m_entry->setETag(m_reply->rawHeader("ETag").constData()); - m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); - m_entry->setStale(false); - ENV.metacache()->updateEntry(m_entry); - - m_reply.reset(); - emit succeeded(m_index_within_job); + // rewind the downloaded temp file + m_pack200_xz_file.seek(0); + // de-xz'd file + QTemporaryFile pack200_file("./dl_temp.XXXXXX"); + pack200_file.open(); + + bool xz_success = false; + // first, de-xz + { + uint8_t in[buffer_size]; + uint8_t out[buffer_size]; + struct xz_buf b; + struct xz_dec *s; + enum xz_ret ret; + xz_crc32_init(); + xz_crc64_init(); + s = xz_dec_init(XZ_DYNALLOC, 1 << 26); + if (s == nullptr) + { + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + b.in = in; + b.in_pos = 0; + b.in_size = 0; + b.out = out; + b.out_pos = 0; + b.out_size = buffer_size; + while (!xz_success) + { + if (b.in_pos == b.in_size) + { + b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in)); + b.in_pos = 0; + } + + ret = xz_dec_run(s, &b); + + if (b.out_pos == sizeof(out)) + { + auto wresult = pack200_file.write((char *)out, b.out_pos); + if (wresult < 0 || size_t(wresult) != b.out_pos) + { + // msg = "Write error\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + + b.out_pos = 0; + } + + if (ret == XZ_OK) + continue; + + if (ret == XZ_UNSUPPORTED_CHECK) + { + // unsupported check. this is OK, but we should log this + continue; + } + + auto wresult = pack200_file.write((char *)out, b.out_pos); + if (wresult < 0 || size_t(wresult) != b.out_pos) + { + // write error + pack200_file.close(); + xz_dec_end(s); + return; + } + + switch (ret) + { + case XZ_STREAM_END: + xz_dec_end(s); + xz_success = true; + break; + + case XZ_MEM_ERROR: + qCritical() << "Memory allocation failed\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_MEMLIMIT_ERROR: + qCritical() << "Memory usage limit reached\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_FORMAT_ERROR: + qCritical() << "Not a .xz file\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_OPTIONS_ERROR: + qCritical() << "Unsupported options in the .xz headers\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + case XZ_DATA_ERROR: + case XZ_BUF_ERROR: + qCritical() << "File is corrupt\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + + default: + qCritical() << "Bug!\n"; + xz_dec_end(s); + failAndTryNextMirror(); + return; + } + } + } + m_pack200_xz_file.remove(); + + // revert pack200 + pack200_file.seek(0); + int handle_in = pack200_file.handle(); + // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects. + if(handle_in == -1) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + int handle_in_dup = dup (handle_in); + if(handle_in_dup == -1) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + FILE *file_in = fdopen (handle_in_dup, "rb"); + if(!file_in) + { + qCritical() << "Error reopening " << pack200_file.fileName(); + failAndTryNextMirror(); + return; + } + QFile qfile_out(m_target_path); + if(!qfile_out.open(QIODevice::WriteOnly)) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + int handle_out = qfile_out.handle(); + if(handle_out == -1) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + int handle_out_dup = dup (handle_out); + if(handle_out_dup == -1) + { + qCritical() << "Error reopening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + FILE *file_out = fdopen (handle_out_dup, "wb"); + if(!file_out) + { + qCritical() << "Error opening " << qfile_out.fileName(); + failAndTryNextMirror(); + return; + } + try + { + // NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above. + unpack_200(file_in, file_out); + } + catch (const std::runtime_error &err) + { + m_status = Job_Failed; + qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what(); + QFile f(m_target_path); + if (f.exists()) + f.remove(); + failAndTryNextMirror(); + return; + } + pack200_file.remove(); + + QFile jar_file(m_target_path); + + if (!jar_file.open(QIODevice::ReadOnly)) + { + jar_file.remove(); + failAndTryNextMirror(); + return; + } + auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5); + m_entry->setMD5Sum(hash.toHex().constData()); + jar_file.close(); + + QFileInfo output_file_info(m_target_path); + m_entry->setETag(m_reply->rawHeader("ETag").constData()); + m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); + m_entry->setStale(false); + ENV.metacache()->updateEntry(m_entry); + + m_reply.reset(); + emit succeeded(m_index_within_job); } bool ForgeXzDownload::abort() { - if(m_reply) - m_reply->abort(); - m_status = Job_Aborted; - return true; + if(m_reply) + m_reply->abort(); + m_status = Job_Aborted; + return true; } bool ForgeXzDownload::canAbort() { - return true; + return true; } diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h index cee402ef..63e75f71 100644 --- a/api/logic/minecraft/forge/ForgeXzDownload.h +++ b/api/logic/minecraft/forge/ForgeXzDownload.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,38 +24,38 @@ typedef std::shared_ptr ForgeXzDownloadPtr; class ForgeXzDownload : public NetAction { - Q_OBJECT + Q_OBJECT public: - MetaEntryPtr m_entry; - /// if saving to file, use the one specified in this string - QString m_target_path; - /// this is the output file, if any - QTemporaryFile m_pack200_xz_file; - /// path relative to the mirror base - QString m_url_path; + MetaEntryPtr m_entry; + /// if saving to file, use the one specified in this string + QString m_target_path; + /// this is the output file, if any + QTemporaryFile m_pack200_xz_file; + /// path relative to the mirror base + QString m_url_path; public: - explicit ForgeXzDownload(QString relative_path, MetaEntryPtr entry); - static ForgeXzDownloadPtr make(QString relative_path, MetaEntryPtr entry) - { - return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry)); - } - virtual ~ForgeXzDownload(){}; - bool canAbort() override; + explicit ForgeXzDownload(QString url, QString relative_path, MetaEntryPtr entry); + static ForgeXzDownloadPtr make(QString url, QString relative_path, MetaEntryPtr entry) + { + return ForgeXzDownloadPtr(new ForgeXzDownload(url, relative_path, entry)); + } + virtual ~ForgeXzDownload(){}; + bool canAbort() override; protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; - void downloadFinished() override; - void downloadReadyRead() override; + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; + void downloadFinished() override; + void downloadReadyRead() override; public slots: - void start() override; - bool abort() override; + void start() override; + bool abort() override; private: - void decompressAndInstall(); - void failAndTryNextMirror(); + void decompressAndInstall(); + void failAndTryNextMirror(); }; diff --git a/api/logic/minecraft/gameoptions/GameOptions.cpp b/api/logic/minecraft/gameoptions/GameOptions.cpp new file mode 100644 index 00000000..e547b32a --- /dev/null +++ b/api/logic/minecraft/gameoptions/GameOptions.cpp @@ -0,0 +1,144 @@ +#include "GameOptions.h" +#include "FileSystem.h" +#include +#include + +namespace { +bool load(const QString& path, std::vector &contents, int & version) +{ + contents.clear(); + QFile file(path); + if (!file.open(QFile::ReadOnly)) + { + qWarning() << "Failed to read options file."; + return false; + } + version = 0; + while(!file.atEnd()) + { + auto line = file.readLine(); + if(line.endsWith('\n')) + { + line.chop(1); + } + auto separatorIndex = line.indexOf(':'); + if(separatorIndex == -1) + { + continue; + } + auto key = QString::fromUtf8(line.data(), separatorIndex); + auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex); + qDebug() << "!!" << key << "!!"; + if(key == "version") + { + version = value.toInt(); + continue; + } + contents.emplace_back(GameOptionItem{key, value}); + } + qDebug() << "Loaded" << path << "with version:" << version; + return true; +} +bool save(const QString& path, std::vector &mapping, int version) +{ + QSaveFile out(path); + if(!out.open(QIODevice::WriteOnly)) + { + return false; + } + if(version != 0) + { + QString versionLine = QString("version:%1\n").arg(version); + out.write(versionLine.toUtf8()); + } + auto iter = mapping.begin(); + while (iter != mapping.end()) + { + out.write(iter->key.toUtf8()); + out.write(":"); + out.write(iter->value.toUtf8()); + out.write("\n"); + iter++; + } + return out.commit(); +} +} + +GameOptions::GameOptions(const QString& path): + path(path) +{ + reload(); +} + +QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(role != Qt::DisplayRole) + { + return QAbstractListModel::headerData(section, orientation, role); + } + switch(section) + { + case 0: + return tr("Key"); + case 1: + return tr("Value"); + default: + return QVariant(); + } +} + +QVariant GameOptions::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= int(contents.size())) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + if(column == 0) + { + return contents[row].key; + } + else + { + return contents[row].value; + } + default: + return QVariant(); + } + return QVariant(); +} + +int GameOptions::rowCount(const QModelIndex&) const +{ + return contents.size(); +} + +int GameOptions::columnCount(const QModelIndex&) const +{ + return 2; +} + +bool GameOptions::isLoaded() const +{ + return loaded; +} + +bool GameOptions::reload() +{ + beginResetModel(); + loaded = load(path, contents, version); + endResetModel(); + return loaded; +} + +bool GameOptions::save() +{ + return ::save(path, contents, version); +} diff --git a/api/logic/minecraft/gameoptions/GameOptions.h b/api/logic/minecraft/gameoptions/GameOptions.h new file mode 100644 index 00000000..c6d25492 --- /dev/null +++ b/api/logic/minecraft/gameoptions/GameOptions.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +struct GameOptionItem +{ + QString key; + QString value; +}; + +class GameOptions : public QAbstractListModel +{ + Q_OBJECT +public: + explicit GameOptions(const QString& path); + virtual ~GameOptions() = default; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & parent) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + bool isLoaded() const; + bool reload(); + bool save(); + +private: + std::vector contents; + bool loaded = false; + QString path; + int version = 0; +}; diff --git a/api/logic/minecraft/launch/ClaimAccount.cpp b/api/logic/minecraft/launch/ClaimAccount.cpp index 71670b4f..a1180f0a 100644 --- a/api/logic/minecraft/launch/ClaimAccount.cpp +++ b/api/logic/minecraft/launch/ClaimAccount.cpp @@ -3,22 +3,22 @@ ClaimAccount::ClaimAccount(LaunchTask* parent, AuthSessionPtr session): LaunchStep(parent) { - if(session->status == AuthSession::Status::PlayableOnline) - { - m_account = session->m_accountPtr; - } + if(session->status == AuthSession::Status::PlayableOnline) + { + m_account = session->m_accountPtr; + } } void ClaimAccount::executeTask() { - if(m_account) - { - lock.reset(new UseLock(m_account)); - emitSucceeded(); - } + if(m_account) + { + lock.reset(new UseLock(m_account)); + emitSucceeded(); + } } void ClaimAccount::finalize() { - lock.reset(); + lock.reset(); } diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h index de9007d1..d0892981 100644 --- a/api/logic/minecraft/launch/ClaimAccount.h +++ b/api/logic/minecraft/launch/ClaimAccount.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,18 @@ class ClaimAccount: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); - void executeTask() override; - void finalize() override; - bool canAbort() const override - { - return false; - } + explicit ClaimAccount(LaunchTask *parent, AuthSessionPtr session); + virtual ~ClaimAccount() {}; + + void executeTask() override; + void finalize() override; + bool canAbort() const override + { + return false; + } private: - std::unique_ptr lock; - MojangAccountPtr m_account; + std::unique_ptr lock; + MojangAccountPtr m_account; }; diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp index 5398f7d0..ae426e31 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp @@ -9,11 +9,11 @@ CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* par void CreateServerResourcePacksFolder::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->minecraftRoot(), "server-resource-packs"))) - { - emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error); - } - emitSucceeded(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) + { + emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error); + } + emitSucceeded(); } diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h index 92026ecb..00ab2f2d 100644 --- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h +++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,14 +22,16 @@ // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created. class CreateServerResourcePacksFolder: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit CreateServerResourcePacksFolder(LaunchTask *parent); - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + explicit CreateServerResourcePacksFolder(LaunchTask *parent); + virtual ~CreateServerResourcePacksFolder() {}; + + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } }; diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp index 4ccc5c3c..5c2bb91e 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp +++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,128 +22,128 @@ DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent) { - connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); + connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state); } void DirectJavaLaunch::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - QStringList args = minecraftInstance->javaArguments(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + QStringList args = minecraftInstance->javaArguments(); - args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); + args.append("-Djava.library.path=" + minecraftInstance->getNativePath()); - auto classPathEntries = minecraftInstance->getClassPath(); - args.append("-cp"); - QString classpath; + auto classPathEntries = minecraftInstance->getClassPath(); + args.append("-cp"); + QString classpath; #ifdef Q_OS_WIN32 - classpath = classPathEntries.join(';'); + classpath = classPathEntries.join(';'); #else - classpath = classPathEntries.join(':'); + classpath = classPathEntries.join(':'); #endif - args.append(classpath); - args.append(minecraftInstance->getMainClass()); - - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - - m_process.setProcessEnvironment(instance->createEnvironment()); - - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); - - auto mcArgs = minecraftInstance->processMinecraftArgs(m_session); - args.append(mcArgs); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } + args.append(classpath); + args.append(minecraftInstance->getMainClass()); + + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); + + auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); + + m_process.setProcessEnvironment(instance->createEnvironment()); + + // make detachable - this will keep the process running even if the object is destroyed + m_process.setDetachable(true); + + auto mcArgs = minecraftInstance->processMinecraftArgs(m_session); + args.append(mcArgs); + + QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); + if(!wrapperCommandStr.isEmpty()) + { + auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); + auto wrapperCommand = wrapperArgs.takeFirst(); + auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); + if (realWrapperCommand.isEmpty()) + { + QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); + args.prepend(javaPath); + m_process.start(wrapperCommand, wrapperArgs + args); + } + else + { + m_process.start(javaPath, args); + } } void DirectJavaLaunch::on_state(LoggedProcess::State state) { - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - - { - m_parent->setPid(-1); - emitFailed("Game crashed."); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed("Game crashed."); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - break; - default: - break; - } + switch(state) + { + case LoggedProcess::FailedToStart: + { + //: Error message displayed if instace can't start + QString reason = tr("Could not launch minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + + { + m_parent->setPid(-1); + emitFailed("Game crashed."); + return; + } + case LoggedProcess::Finished: + { + m_parent->setPid(-1); + // if the exit code wasn't 0, report this as a crash + auto exitCode = m_process.exitCode(); + if(exitCode != 0) + { + emitFailed("Game crashed."); + return; + } + //FIXME: make this work again + // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + // run post-exit + emitSucceeded(); + break; + } + case LoggedProcess::Running: + emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + m_parent->setPid(m_process.processId()); + m_parent->instance()->setLastLaunch(); + break; + default: + break; + } } void DirectJavaLaunch::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } void DirectJavaLaunch::proceed() { - // nil + // nil } bool DirectJavaLaunch::abort() { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - return true; + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + return true; } diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h index 19087b50..9f57272c 100644 --- a/api/logic/minecraft/launch/DirectJavaLaunch.h +++ b/api/logic/minecraft/launch/DirectJavaLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,27 +21,29 @@ class DirectJavaLaunch: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit DirectJavaLaunch(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } + explicit DirectJavaLaunch(LaunchTask *parent); + virtual ~DirectJavaLaunch() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual void proceed(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); + void setAuthSession(AuthSessionPtr session) + { + m_session = session; + } private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; + LoggedProcess m_process; + QString m_command; + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp index 7ddde374..253d13bc 100644 --- a/api/logic/minecraft/launch/ExtractNatives.cpp +++ b/api/logic/minecraft/launch/ExtractNatives.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,76 +25,76 @@ static QString replaceSuffix (QString target, const QString &suffix, const QString &replacement) { - if (!target.endsWith(suffix)) - { - return target; - } - target.resize(target.length() - suffix.length()); - return target + replacement; + if (!target.endsWith(suffix)) + { + return target; + } + target.resize(target.length() - suffix.length()); + return target + replacement; } static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack) { - QuaZip zip(source); - if(!zip.open(QuaZip::mdUnzip)) - { - return false; - } - QDir directory(targetFolder); - if (!zip.goToFirstFile()) - { - return false; - } - do - { - QString name = zip.getCurrentFileName(); - if(applyJnilibHack) - { - name = replaceSuffix(name, ".jnilib", ".dylib"); - } - QString absFilePath = directory.absoluteFilePath(name); - if (!JlCompress::extractFile(&zip, "", absFilePath)) - { - return false; - } - } while (zip.goToNextFile()); - zip.close(); - if(zip.getZipError()!=0) - { - return false; - } - return true; + QuaZip zip(source); + if(!zip.open(QuaZip::mdUnzip)) + { + return false; + } + QDir directory(targetFolder); + if (!zip.goToFirstFile()) + { + return false; + } + do + { + QString name = zip.getCurrentFileName(); + if(applyJnilibHack) + { + name = replaceSuffix(name, ".jnilib", ".dylib"); + } + QString absFilePath = directory.absoluteFilePath(name); + if (!JlCompress::extractFile(&zip, "", absFilePath)) + { + return false; + } + } while (zip.goToNextFile()); + zip.close(); + if(zip.getZipError()!=0) + { + return false; + } + return true; } void ExtractNatives::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - auto toExtract = minecraftInstance->getNativeJars(); - if(toExtract.isEmpty()) - { - emitSucceeded(); - return; - } - auto outputPath = minecraftInstance->getNativePath(); - auto javaVersion = minecraftInstance->getJavaVersion(); - bool jniHackEnabled = javaVersion.major() >= 8; - for(const auto &source: toExtract) - { - if(!unzipNatives(source, outputPath, jniHackEnabled)) - { - auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - } - } - emitSucceeded(); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto toExtract = minecraftInstance->getNativeJars(); + if(toExtract.isEmpty()) + { + emitSucceeded(); + return; + } + auto outputPath = minecraftInstance->getNativePath(); + auto javaVersion = minecraftInstance->getJavaVersion(); + bool jniHackEnabled = javaVersion.major() >= 8; + for(const auto &source: toExtract) + { + if(!unzipNatives(source, outputPath, jniHackEnabled)) + { + auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } + } + emitSucceeded(); } void ExtractNatives::finalize() { - auto instance = m_parent->instance(); - QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); - QDir dir(target_dir); - dir.removeRecursively(); + auto instance = m_parent->instance(); + QString target_dir = FS::PathCombine(instance->instanceRoot(), "natives/"); + QDir dir(target_dir); + dir.removeRecursively(); } diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h index 6e1e7cd4..abb3bd7a 100644 --- a/api/logic/minecraft/launch/ExtractNatives.h +++ b/api/logic/minecraft/launch/ExtractNatives.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,17 @@ // FIXME: temporary wrapper for existing task. class ExtractNatives: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; - virtual ~ExtractNatives(){}; + explicit ExtractNatives(LaunchTask *parent) : LaunchStep(parent){}; + virtual ~ExtractNatives(){}; - void executeTask() override; - bool canAbort() const override - { - return false; - } - void finalize() override; + void executeTask() override; + bool canAbort() const override + { + return false; + } + void finalize() override; }; diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp index 1fe9c323..e854a3a6 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp +++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) { - connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); - connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); + connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines); + connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state); } #ifdef Q_OS_WIN @@ -33,187 +33,187 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) #include QString shortPathName(const QString & file) { - auto input = file.toStdWString(); - std::wstring output; - long length = GetShortPathNameW(input.c_str(), NULL, 0); - // NOTE: this resizing might seem weird... - // when GetShortPathNameW fails, it returns length including null character - // when it succeeds, it returns length excluding null character - // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx - output.resize(length); - GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); - output.resize(length-1); - QString ret = QString::fromStdWString(output); - return ret; + auto input = file.toStdWString(); + std::wstring output; + long length = GetShortPathNameW(input.c_str(), NULL, 0); + // NOTE: this resizing might seem weird... + // when GetShortPathNameW fails, it returns length including null character + // when it succeeds, it returns length excluding null character + // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx + output.resize(length); + GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length); + output.resize(length-1); + QString ret = QString::fromStdWString(output); + return ret; } #endif // if the string survives roundtrip through local 8bit encoding... bool fitsInLocal8bit(const QString & string) { - return string == QString::fromLocal8Bit(string.toLocal8Bit()); + return string == QString::fromLocal8Bit(string.toLocal8Bit()); } void LauncherPartLaunch::executeTask() { - auto instance = m_parent->instance(); - std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); - m_launchScript = minecraftInstance->createLaunchScript(m_session); - QStringList args = minecraftInstance->javaArguments(); - QString allArgs = args.join(", "); - emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); + m_launchScript = minecraftInstance->createLaunchScript(m_session); + QStringList args = minecraftInstance->javaArguments(); + QString allArgs = args.join(", "); + emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC); - auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); + auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); - m_process.setProcessEnvironment(instance->createEnvironment()); + m_process.setProcessEnvironment(instance->createEnvironment()); - // make detachable - this will keep the process running even if the object is destroyed - m_process.setDetachable(true); + // make detachable - this will keep the process running even if the object is destroyed + m_process.setDetachable(true); - auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); + auto classPath = minecraftInstance->getClassPath(); + classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar")); - auto natPath = minecraftInstance->getNativePath(); + auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN - if (!fitsInLocal8bit(natPath)) - { - args << "-Djava.library.path=" + shortPathName(natPath); - } - else - { - args << "-Djava.library.path=" + natPath; - } + if (!fitsInLocal8bit(natPath)) + { + args << "-Djava.library.path=" + shortPathName(natPath); + } + else + { + args << "-Djava.library.path=" + natPath; + } #else - args << "-Djava.library.path=" + natPath; + args << "-Djava.library.path=" + natPath; #endif - args << "-cp"; + args << "-cp"; #ifdef Q_OS_WIN - QStringList processed; - for(auto & item: classPath) - { - if (!fitsInLocal8bit(item)) - { - processed << shortPathName(item); - } - else - { - processed << item; - } - } - args << processed.join(';'); + QStringList processed; + for(auto & item: classPath) + { + if (!fitsInLocal8bit(item)) + { + processed << shortPathName(item); + } + else + { + processed << item; + } + } + args << processed.join(';'); #else - args << classPath.join(':'); + args << classPath.join(':'); #endif - args << "org.multimc.EntryPoint"; - - qDebug() << args.join(' '); - - QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); - if(!wrapperCommandStr.isEmpty()) - { - auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); - auto wrapperCommand = wrapperArgs.takeFirst(); - auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); - if (realWrapperCommand.isEmpty()) - { - QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); - args.prepend(javaPath); - m_process.start(wrapperCommand, wrapperArgs + args); - } - else - { - m_process.start(javaPath, args); - } + args << "org.multimc.EntryPoint"; + + qDebug() << args.join(' '); + + QString wrapperCommandStr = instance->getWrapperCommand().trimmed(); + if(!wrapperCommandStr.isEmpty()) + { + auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr); + auto wrapperCommand = wrapperArgs.takeFirst(); + auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand); + if (realWrapperCommand.isEmpty()) + { + QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC); + args.prepend(javaPath); + m_process.start(wrapperCommand, wrapperArgs + args); + } + else + { + m_process.start(javaPath, args); + } } void LauncherPartLaunch::on_state(LoggedProcess::State state) { - switch(state) - { - case LoggedProcess::FailedToStart: - { - //: Error message displayed if instace can't start - QString reason = tr("Could not launch minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - return; - } - case LoggedProcess::Aborted: - case LoggedProcess::Crashed: - - { - m_parent->setPid(-1); - emitFailed("Game crashed."); - return; - } - case LoggedProcess::Finished: - { - m_parent->setPid(-1); - // if the exit code wasn't 0, report this as a crash - auto exitCode = m_process.exitCode(); - if(exitCode != 0) - { - emitFailed("Game crashed."); - return; - } - //FIXME: make this work again - // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); - // run post-exit - emitSucceeded(); - break; - } - case LoggedProcess::Running: - emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); - m_parent->setPid(m_process.processId()); - m_parent->instance()->setLastLaunch(); - // send the launch script to the launcher part - m_process.write(m_launchScript.toUtf8()); - - mayProceed = true; - emit readyForLaunch(); - break; - default: - break; - } + switch(state) + { + case LoggedProcess::FailedToStart: + { + //: Error message displayed if instace can't start + QString reason = tr("Could not launch minecraft!"); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + return; + } + case LoggedProcess::Aborted: + case LoggedProcess::Crashed: + + { + m_parent->setPid(-1); + emitFailed("Game crashed."); + return; + } + case LoggedProcess::Finished: + { + m_parent->setPid(-1); + // if the exit code wasn't 0, report this as a crash + auto exitCode = m_process.exitCode(); + if(exitCode != 0) + { + emitFailed("Game crashed."); + return; + } + //FIXME: make this work again + // m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode)); + // run post-exit + emitSucceeded(); + break; + } + case LoggedProcess::Running: + emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC); + m_parent->setPid(m_process.processId()); + m_parent->instance()->setLastLaunch(); + // send the launch script to the launcher part + m_process.write(m_launchScript.toUtf8()); + + mayProceed = true; + emit readyForLaunch(); + break; + default: + break; + } } void LauncherPartLaunch::setWorkingDirectory(const QString &wd) { - m_process.setWorkingDirectory(wd); + m_process.setWorkingDirectory(wd); } void LauncherPartLaunch::proceed() { - if(mayProceed) - { - QString launchString("launch\n"); - m_process.write(launchString.toUtf8()); - mayProceed = false; - } + if(mayProceed) + { + QString launchString("launch\n"); + m_process.write(launchString.toUtf8()); + mayProceed = false; + } } bool LauncherPartLaunch::abort() { - if(mayProceed) - { - mayProceed = false; - QString launchString("abort\n"); - m_process.write(launchString.toUtf8()); - } - else - { - auto state = m_process.state(); - if (state == LoggedProcess::Running || state == LoggedProcess::Starting) - { - m_process.kill(); - } - } - return true; + if(mayProceed) + { + mayProceed = false; + QString launchString("abort\n"); + m_process.write(launchString.toUtf8()); + } + else + { + auto state = m_process.state(); + if (state == LoggedProcess::Running || state == LoggedProcess::Starting) + { + m_process.kill(); + } + } + return true; } diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h index d384c2d1..1bc4a5f8 100644 --- a/api/logic/minecraft/launch/LauncherPartLaunch.h +++ b/api/logic/minecraft/launch/LauncherPartLaunch.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,29 +21,31 @@ class LauncherPartLaunch: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit LauncherPartLaunch(LaunchTask *parent); - virtual void executeTask(); - virtual bool abort(); - virtual void proceed(); - virtual bool canAbort() const - { - return true; - } - void setWorkingDirectory(const QString &wd); - void setAuthSession(AuthSessionPtr session) - { - m_session = session; - } + explicit LauncherPartLaunch(LaunchTask *parent); + virtual ~LauncherPartLaunch() {}; + + virtual void executeTask(); + virtual bool abort(); + virtual void proceed(); + virtual bool canAbort() const + { + return true; + } + void setWorkingDirectory(const QString &wd); + void setAuthSession(AuthSessionPtr session) + { + m_session = session; + } private slots: - void on_state(LoggedProcess::State state); + void on_state(LoggedProcess::State state); private: - LoggedProcess m_process; - QString m_command; - AuthSessionPtr m_session; - QString m_launchScript; - bool mayProceed = false; + LoggedProcess m_process; + QString m_command; + AuthSessionPtr m_session; + QString m_launchScript; + bool mayProceed = false; }; diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp index 34825b45..ceaad175 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.cpp +++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,60 +23,60 @@ void ModMinecraftJar::executeTask() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); - if(!m_inst->getJarMods().size()) - { - emitSucceeded(); - return; - } - // nuke obsolete stripped jar(s) if needed - if(!FS::ensureFolderPathExists(m_inst->binRoot())) - { - emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); - } + if(!m_inst->getJarMods().size()) + { + emitSucceeded(); + return; + } + // nuke obsolete stripped jar(s) if needed + if(!FS::ensureFolderPathExists(m_inst->binRoot())) + { + emitFailed(tr("Couldn't create the bin folder for Minecraft.jar")); + } - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - if(!removeJar()) - { - emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); - } + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + if(!removeJar()) + { + emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath)); + } - // create temporary modded jar, if needed - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto jarMods = m_inst->getJarMods(); - if(jarMods.size()) - { - auto mainJar = profile->getMainJar(); - QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); - auto sourceJarPath = jars[0]; - if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - } - emitSucceeded(); + // create temporary modded jar, if needed + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto jarMods = m_inst->getJarMods(); + if(jarMods.size()) + { + auto mainJar = profile->getMainJar(); + QStringList jars, temp1, temp2, temp3, temp4; + mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); + auto sourceJarPath = jars[0]; + if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) + { + emitFailed(tr("Failed to create the custom Minecraft jar file.")); + return; + } + } + emitSucceeded(); } void ModMinecraftJar::finalize() { - removeJar(); + removeJar(); } bool ModMinecraftJar::removeJar() { - auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); - auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); - QFile finalJar(finalJarPath); - if(finalJar.exists()) - { - if(!finalJar.remove()) - { - return false; - } - } - return true; + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar"); + QFile finalJar(finalJarPath); + if(finalJar.exists()) + { + if(!finalJar.remove()) + { + return false; + } + } + return true; } diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h index b9a2d35b..bed5079f 100644 --- a/api/logic/minecraft/launch/ModMinecraftJar.h +++ b/api/logic/minecraft/launch/ModMinecraftJar.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,17 @@ class ModMinecraftJar: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; - virtual ~ModMinecraftJar(){}; + explicit ModMinecraftJar(LaunchTask *parent) : LaunchStep(parent) {}; + virtual ~ModMinecraftJar(){}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } - void finalize() override; + virtual void executeTask() override; + virtual bool canAbort() const override + { + return false; + } + void finalize() override; private: - bool removeJar(); + bool removeJar(); }; diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp index 83bf584f..4cc180fd 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp +++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,67 +19,88 @@ #include "PrintInstanceInfo.h" #include -void PrintInstanceInfo::executeTask() -{ - auto instance = m_parent->instance(); - auto lines = instance->verboseDescription(m_session); - #ifdef Q_OS_LINUX +namespace { +void probeProcCpuinfo(QStringList &log) +{ std::ifstream cpuin("/proc/cpuinfo"); for (std::string line; std::getline(cpuin, line);) { if (strncmp(line.c_str(), "model name", 10) == 0) { - QStringList clines = (QStringList() << QString::fromStdString(line.substr(13, std::string::npos))); - logLines(clines, MessageLevel::MultiMC); + log << QString::fromStdString(line.substr(13, std::string::npos)); break; } } +} +void runLspci(QStringList &log) +{ + // FIXME: fixed size buffers... char buff[512]; int gpuline = -1; int cline = 0; - FILE *fp = popen("lspci -k", "r"); - if (fp != NULL) + FILE * lspci = popen("lspci -k", "r"); + + if (!lspci) + return; + + while (fgets(buff, 512, lspci) != NULL) { - while (fgets(buff, 512, fp) != NULL) + std::string str(buff); + if (str.length() < 9) + continue; + if (str.substr(8, 3) == "VGA") { - std::string str(buff); - if (str.length() < 9) - continue; - if (str.substr(8, 3) == "VGA") - { - gpuline = cline; - QStringList glines = (QStringList() << QString::fromStdString(str.substr(35, std::string::npos))); - logLines(glines, MessageLevel::MultiMC); - } - if (gpuline > -1 && gpuline != cline) + gpuline = cline; + log << QString::fromStdString(str.substr(35, std::string::npos)); + } + if (gpuline > -1 && gpuline != cline) + { + if (cline - gpuline < 3) { - if (cline - gpuline < 3) - { - QStringList alines = (QStringList() << QString::fromStdString(str.substr(1, std::string::npos))); - logLines(alines, MessageLevel::MultiMC); - } + log << QString::fromStdString(str.substr(1, std::string::npos)); } - cline++; } + cline++; } - - FILE *fp2 = popen("glxinfo", "r"); - if (fp2 != NULL) + pclose(lspci); +} + +void runGlxinfo(QStringList & log) +{ + // FIXME: fixed size buffers... + char buff[512]; + FILE *glxinfo = popen("glxinfo", "r"); + if (!glxinfo) + return; + + while (fgets(buff, 512, glxinfo) != NULL) { - while (fgets(buff, 512, fp2) != NULL) + if (strncmp(buff, "OpenGL version string:", 22) == 0) { - if (strncmp(buff, "OpenGL version string:", 22) == 0) - { - QStringList drlines = (QStringList() << QString::fromUtf8(buff)); - logLines(drlines, MessageLevel::MultiMC); - break; - } + log << QString::fromUtf8(buff); + break; } } + pclose(glxinfo); +} + +} +#endif + +void PrintInstanceInfo::executeTask() +{ + auto instance = m_parent->instance(); + QStringList log; + +#ifdef Q_OS_LINUX + ::probeProcCpuinfo(log); + ::runLspci(log); + ::runGlxinfo(log); #endif - logLines(lines, MessageLevel::MultiMC); + logLines(log, MessageLevel::MultiMC); + logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC); emitSucceeded(); } diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h index 61615ba1..1d081f3e 100644 --- a/api/logic/minecraft/launch/PrintInstanceInfo.h +++ b/api/logic/minecraft/launch/PrintInstanceInfo.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,17 @@ // FIXME: temporary wrapper for existing task. class PrintInstanceInfo: public LaunchStep { - Q_OBJECT + Q_OBJECT public: - explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {}; - virtual ~PrintInstanceInfo(){}; + explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {}; + virtual ~PrintInstanceInfo(){}; - virtual void executeTask(); - virtual bool canAbort() const - { - return false; - } + virtual void executeTask(); + virtual bool canAbort() const + { + return false; + } private: - AuthSessionPtr m_session; + AuthSessionPtr m_session; }; diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp new file mode 100644 index 00000000..af9af127 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.cpp @@ -0,0 +1,36 @@ +/* Copyright 2013-2019 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 "ReconstructAssets.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/ComponentList.h" +#include "minecraft/AssetsUtils.h" +#include "launch/LaunchTask.h" + +void ReconstructAssets::executeTask() +{ + auto instance = m_parent->instance(); + std::shared_ptr minecraftInstance = std::dynamic_pointer_cast(instance); + auto components = minecraftInstance->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); + + if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) + { + emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error); + } + + emitSucceeded(); +} diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h new file mode 100644 index 00000000..228fa083 --- /dev/null +++ b/api/logic/minecraft/launch/ReconstructAssets.h @@ -0,0 +1,33 @@ +/* Copyright 2013-2019 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 + +class ReconstructAssets: public LaunchStep +{ + Q_OBJECT +public: + explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){}; + virtual ~ReconstructAssets(){}; + + void executeTask() override; + bool canAbort() const override + { + return false; + } +}; diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp new file mode 100644 index 00000000..485c3dc4 --- /dev/null +++ b/api/logic/minecraft/launch/ScanModFolders.cpp @@ -0,0 +1,54 @@ +/* Copyright 2013-2019 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 "ScanModFolders.h" +#include "launch/LaunchTask.h" +#include "MMCZip.h" +#include "minecraft/OpSys.h" +#include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/mod/ModFolderModel.h" + +void ScanModFolders::executeTask() +{ + auto m_inst = std::dynamic_pointer_cast(m_parent->instance()); + + auto loaders = m_inst->loaderModList(); + connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone); + loaders->update(); + + auto cores = m_inst->coreModList(); + connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone); + cores->update(); +} + +void ScanModFolders::modsDone() +{ + m_modsDone = true; + checkDone(); +} + +void ScanModFolders::coreModsDone() +{ + m_coreModsDone = true; + checkDone(); +} + +void ScanModFolders::checkDone() +{ + if(m_modsDone && m_coreModsDone) { + emitSucceeded(); + } +} diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h new file mode 100644 index 00000000..360df98d --- /dev/null +++ b/api/logic/minecraft/launch/ScanModFolders.h @@ -0,0 +1,42 @@ +/* Copyright 2013-2019 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 + +class ScanModFolders: public LaunchStep +{ + Q_OBJECT +public: + explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {}; + virtual ~ScanModFolders(){}; + + virtual void executeTask() override; + virtual bool canAbort() const override + { + return false; + } +private slots: + void coreModsDone(); + void modsDone(); +private: + void checkDone(); + +private: // DATA + bool m_modsDone = false; + bool m_coreModsDone = false; +}; diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp index 5338763c..b9d68a9c 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.cpp +++ b/api/logic/minecraft/legacy/LegacyInstance.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,322 +21,236 @@ #include "LegacyInstance.h" #include "minecraft/legacy/LegacyModList.h" -#include "minecraft/ModList.h" #include "minecraft/WorldList.h" #include #include LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) - : BaseInstance(globalSettings, settings, rootDir) + : BaseInstance(globalSettings, settings, rootDir) { - settings->registerSetting("NeedsRebuild", true); - settings->registerSetting("ShouldUpdate", false); - settings->registerSetting("JarVersion", QString()); - settings->registerSetting("IntendedJarVersion", QString()); - /* - * custom base jar has no default. it is determined in code... see the accessor methods for - *it - * - * for instances that DO NOT have the CustomBaseJar setting (legacy instances), - * [.]minecraft/bin/mcbackup.jar is the default base jar - */ - settings->registerSetting("UseCustomBaseJar", true); - settings->registerSetting("CustomBaseJar", ""); + settings->registerSetting("NeedsRebuild", true); + settings->registerSetting("ShouldUpdate", false); + settings->registerSetting("JarVersion", QString()); + settings->registerSetting("IntendedJarVersion", QString()); + /* + * custom base jar has no default. it is determined in code... see the accessor methods for + *it + * + * for instances that DO NOT have the CustomBaseJar setting (legacy instances), + * [.]minecraft/bin/mcbackup.jar is the default base jar + */ + settings->registerSetting("UseCustomBaseJar", true); + settings->registerSetting("CustomBaseJar", ""); } QString LegacyInstance::mainJarToPreserve() const { - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if(customJar) - { - auto base = baseJar(); - if(QFile::exists(base)) - { - return base; - } - } - auto runnable = runnableJar(); - if(QFile::exists(runnable)) - { - return runnable; - } - return QString(); + bool customJar = m_settings->get("UseCustomBaseJar").toBool(); + if(customJar) + { + auto base = baseJar(); + if(QFile::exists(base)) + { + return base; + } + } + auto runnable = runnableJar(); + if(QFile::exists(runnable)) + { + return runnable; + } + return QString(); } QString LegacyInstance::baseJar() const { - bool customJar = m_settings->get("UseCustomBaseJar").toBool(); - if (customJar) - { - return customBaseJar(); - } - else - return defaultBaseJar(); + bool customJar = m_settings->get("UseCustomBaseJar").toBool(); + if (customJar) + { + return customBaseJar(); + } + else + return defaultBaseJar(); } QString LegacyInstance::customBaseJar() const { - QString value = m_settings->get("CustomBaseJar").toString(); - if (value.isNull() || value.isEmpty()) - { - return defaultCustomBaseJar(); - } - return value; + QString value = m_settings->get("CustomBaseJar").toString(); + if (value.isNull() || value.isEmpty()) + { + return defaultCustomBaseJar(); + } + return value; } bool LegacyInstance::shouldUseCustomBaseJar() const { - return m_settings->get("UseCustomBaseJar").toBool(); + return m_settings->get("UseCustomBaseJar").toBool(); } shared_qobject_ptr LegacyInstance::createUpdateTask(Net::Mode) { - return nullptr; + return nullptr; } -/* -class LegacyJarModTask : public Task -{ - //Q_OBJECT -public: - explicit LegacyJarModTask(std::shared_ptr inst) : Task(nullptr), m_inst(inst) - { - } - virtual void executeTask() - { - if (!m_inst->shouldRebuild()) - { - emitSucceeded(); - return; - } - - // Get the mod list - auto modList = m_inst->getJarMods(); - - QFileInfo runnableJar(m_inst->runnableJar()); - QFileInfo baseJar(m_inst->baseJar()); - bool base_is_custom = m_inst->shouldUseCustomBaseJar(); - - // Nothing to do if there are no jar mods to install, no backup and just the mc jar - if (base_is_custom) - { - // yes, this can happen if the instance only has the runnable jar and not the base jar - // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe - // because that's not something mmc4 guarantees - if (runnableJar.isFile() && !baseJar.exists() && modList.empty()) - { - m_inst->setShouldRebuild(false); - emitSucceeded(); - return; - } - - setStatus(tr("Installing mods: Backing up minecraft.jar ...")); - if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath())) - { - emitFailed("It seems both the active and base jar are gone. A fresh base jar will " - "be used on next run."); - m_inst->setShouldRebuild(true); - m_inst->setShouldUpdate(true); - m_inst->setShouldUseCustomBaseJar(false); - return; - } - } - - if (!baseJar.exists()) - { - emitFailed("The base jar " + baseJar.filePath() + " does not exist"); - return; - } - - if (runnableJar.exists() && !QFile::remove(runnableJar.filePath())) - { - emitFailed("Failed to delete old minecraft.jar"); - return; - } - - setStatus(tr("Installing mods: Opening minecraft.jar ...")); - - QString outputJarPath = runnableJar.filePath(); - QString inputJarPath = baseJar.filePath(); - - if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList)) - { - emitFailed(tr("Failed to create the custom Minecraft jar file.")); - return; - } - m_inst->setShouldRebuild(false); - // inst->UpdateVersion(true); - emitSucceeded(); - return; - - } - std::shared_ptr m_inst; -}; -*/ - std::shared_ptr LegacyInstance::jarModList() const { - if (!jar_mod_list) - { - auto list = new LegacyModList(jarModsDir(), modListFile()); - jar_mod_list.reset(list); - } - jar_mod_list->update(); - return jar_mod_list; -} - -QList LegacyInstance::getJarMods() const -{ - return jarModList()->allMods(); + if (!jar_mod_list) + { + auto list = new LegacyModList(jarModsDir(), modListFile()); + jar_mod_list.reset(list); + } + jar_mod_list->update(); + return jar_mod_list; } -QString LegacyInstance::minecraftRoot() const +QString LegacyInstance::gameRoot() const { - QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); - QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); + QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); + QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft")); - if (mcDir.exists() && !dotMCDir.exists()) - return mcDir.filePath(); - else - return dotMCDir.filePath(); + if (mcDir.exists() && !dotMCDir.exists()) + return mcDir.filePath(); + else + return dotMCDir.filePath(); } QString LegacyInstance::binRoot() const { - return FS::PathCombine(minecraftRoot(), "bin"); + return FS::PathCombine(gameRoot(), "bin"); } QString LegacyInstance::jarModsDir() const { - return FS::PathCombine(instanceRoot(), "instMods"); + return FS::PathCombine(instanceRoot(), "instMods"); } QString LegacyInstance::libDir() const { - return FS::PathCombine(minecraftRoot(), "lib"); + return FS::PathCombine(gameRoot(), "lib"); } QString LegacyInstance::savesDir() const { - return FS::PathCombine(minecraftRoot(), "saves"); + return FS::PathCombine(gameRoot(), "saves"); } QString LegacyInstance::loaderModsDir() const { - return FS::PathCombine(minecraftRoot(), "mods"); + return FS::PathCombine(gameRoot(), "mods"); } QString LegacyInstance::coreModsDir() const { - return FS::PathCombine(minecraftRoot(), "coremods"); + return FS::PathCombine(gameRoot(), "coremods"); } QString LegacyInstance::resourceDir() const { - return FS::PathCombine(minecraftRoot(), "resources"); + return FS::PathCombine(gameRoot(), "resources"); } QString LegacyInstance::texturePacksDir() const { - return FS::PathCombine(minecraftRoot(), "texturepacks"); + return FS::PathCombine(gameRoot(), "texturepacks"); } QString LegacyInstance::runnableJar() const { - return FS::PathCombine(binRoot(), "minecraft.jar"); + return FS::PathCombine(binRoot(), "minecraft.jar"); } QString LegacyInstance::modListFile() const { - return FS::PathCombine(instanceRoot(), "modlist"); + return FS::PathCombine(instanceRoot(), "modlist"); } QString LegacyInstance::instanceConfigFolder() const { - return FS::PathCombine(minecraftRoot(), "config"); + return FS::PathCombine(gameRoot(), "config"); } bool LegacyInstance::shouldRebuild() const { - return m_settings->get("NeedsRebuild").toBool(); + return m_settings->get("NeedsRebuild").toBool(); } QString LegacyInstance::currentVersionId() const { - return m_settings->get("JarVersion").toString(); + return m_settings->get("JarVersion").toString(); } QString LegacyInstance::intendedVersionId() const { - return m_settings->get("IntendedJarVersion").toString(); + return m_settings->get("IntendedJarVersion").toString(); } bool LegacyInstance::shouldUpdate() const { - QVariant var = settings()->get("ShouldUpdate"); - if (!var.isValid() || var.toBool() == false) - { - return intendedVersionId() != currentVersionId(); - } - return true; + QVariant var = settings()->get("ShouldUpdate"); + if (!var.isValid() || var.toBool() == false) + { + return intendedVersionId() != currentVersionId(); + } + return true; } QString LegacyInstance::defaultBaseJar() const { - return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; + return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar"; } QString LegacyInstance::defaultCustomBaseJar() const { - return FS::PathCombine(binRoot(), "mcbackup.jar"); + return FS::PathCombine(binRoot(), "mcbackup.jar"); } std::shared_ptr LegacyInstance::worldList() const { - if (!m_world_list) - { - m_world_list.reset(new WorldList(savesDir())); - } - return m_world_list; + if (!m_world_list) + { + m_world_list.reset(new WorldList(savesDir())); + } + return m_world_list; } QString LegacyInstance::typeName() const { - return tr("Legacy"); + return tr("Legacy"); } QString LegacyInstance::getStatusbarDescription() { - return tr("Instance from previous versions."); + return tr("Instance from previous versions."); } QStringList LegacyInstance::verboseDescription(AuthSessionPtr session) { - QStringList out; - - auto alltraits = traits(); - if(alltraits.size()) - { - out << "Traits:"; - for (auto trait : alltraits) - { - out << " " + trait; - } - out << ""; - } - - QString windowParams; - if (settings()->get("LaunchMaximized").toBool()) - { - out << "Window size: max (if available)"; - } - else - { - auto width = settings()->get("MinecraftWinWidth").toInt(); - auto height = settings()->get("MinecraftWinHeight").toInt(); - out << "Window size: " + QString::number(width) + " x " + QString::number(height); - } - out << ""; - return out; + QStringList out; + + auto alltraits = traits(); + if(alltraits.size()) + { + out << "Traits:"; + for (auto trait : alltraits) + { + out << " " + trait; + } + out << ""; + } + + QString windowParams; + if (settings()->get("LaunchMaximized").toBool()) + { + out << "Window size: max (if available)"; + } + else + { + auto width = settings()->get("MinecraftWinWidth").toInt(); + auto height = settings()->get("MinecraftWinHeight").toInt(); + out << "Window size: " + QString::number(width) + " x " + QString::number(height); + } + out << ""; + return out; } diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h index 7ab89509..7c0b94e8 100644 --- a/api/logic/minecraft/legacy/LegacyInstance.h +++ b/api/logic/minecraft/legacy/LegacyInstance.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ #pragma once #include "BaseInstance.h" -#include "minecraft/Mod.h" +#include "launch/LaunchTask.h" #include "multimc_logic_export.h" -class ModList; +class ModFolderModel; class LegacyModList; class WorldList; class Task; @@ -29,115 +29,113 @@ class Task; */ class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance { - Q_OBJECT + Q_OBJECT public: - explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); - - virtual void init() override {} - virtual void saveNow() override {} - - /// Path to the instance's minecraft.jar - QString runnableJar() const; - - //! Path to the instance's modlist file. - QString modListFile() const; - - ////// Directories ////// - QString libDir() const; - QString savesDir() const; - QString texturePacksDir() const; - QString jarModsDir() const; - QString loaderModsDir() const; - QString coreModsDir() const; - QString resourceDir() const; - virtual QString instanceConfigFolder() const override; - QString minecraftRoot() const; // Path to the instance's minecraft directory. - QString binRoot() const; // Path to the instance's minecraft bin directory. - - /// Get the curent base jar of this instance. By default, it's the - /// versions/$version/$version.jar - QString baseJar() const; - - /// the default base jar of this instance - QString defaultBaseJar() const; - /// the default custom base jar of this instance - QString defaultCustomBaseJar() const; - - // the main jar that we actually want to keep when migrating the instance - QString mainJarToPreserve() const; - - /*! - * Whether or not custom base jar is used - */ - bool shouldUseCustomBaseJar() const; - - /*! - * The value of the custom base jar - */ - QString customBaseJar() const; - - std::shared_ptr jarModList() const; - QList getJarMods() const; - std::shared_ptr worldList() const; - - /*! - * Whether or not the instance's minecraft.jar needs to be rebuilt. - * If this is true, when the instance launches, its jar mods will be - * re-added to a fresh minecraft.jar file. - */ - bool shouldRebuild() const; - - QString currentVersionId() const; - QString intendedVersionId() const; - - QSet traits() const override - { - return {"legacy-instance", "texturepacks"}; - }; - - virtual bool shouldUpdate() const; - virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) override; - - virtual QString typeName() const override; - - bool canLaunch() const override - { - return false; - } - bool canEdit() const override - { - return true; - } - bool canExport() const override - { - return false; - } - std::shared_ptr createLaunchTask(AuthSessionPtr account) override - { - return nullptr; - } - IPathMatcher::Ptr getLogFileMatcher() override - { - return nullptr; - } - QString getLogFileRoot() override - { - return minecraftRoot(); - } - - QString getStatusbarDescription() override; - QStringList verboseDescription(AuthSessionPtr session) override; - - QProcessEnvironment createEnvironment() override - { - return QProcessEnvironment(); - } - QMap getVariables() const override - { - return {}; - } + explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); + + virtual void saveNow() override {} + + /// Path to the instance's minecraft.jar + QString runnableJar() const; + + //! Path to the instance's modlist file. + QString modListFile() const; + + ////// Directories ////// + QString libDir() const; + QString savesDir() const; + QString texturePacksDir() const; + QString jarModsDir() const; + QString loaderModsDir() const; + QString coreModsDir() const; + QString resourceDir() const; + virtual QString instanceConfigFolder() const override; + QString gameRoot() const override; // Path to the instance's minecraft directory. + QString binRoot() const; // Path to the instance's minecraft bin directory. + + /// Get the curent base jar of this instance. By default, it's the + /// versions/$version/$version.jar + QString baseJar() const; + + /// the default base jar of this instance + QString defaultBaseJar() const; + /// the default custom base jar of this instance + QString defaultCustomBaseJar() const; + + // the main jar that we actually want to keep when migrating the instance + QString mainJarToPreserve() const; + + /*! + * Whether or not custom base jar is used + */ + bool shouldUseCustomBaseJar() const; + + /*! + * The value of the custom base jar + */ + QString customBaseJar() const; + + std::shared_ptr jarModList() const; + std::shared_ptr worldList() const; + + /*! + * Whether or not the instance's minecraft.jar needs to be rebuilt. + * If this is true, when the instance launches, its jar mods will be + * re-added to a fresh minecraft.jar file. + */ + bool shouldRebuild() const; + + QString currentVersionId() const; + QString intendedVersionId() const; + + QSet traits() const override + { + return {"legacy-instance", "texturepacks"}; + }; + + virtual bool shouldUpdate() const; + virtual shared_qobject_ptr createUpdateTask(Net::Mode mode) override; + + virtual QString typeName() const override; + + bool canLaunch() const override + { + return false; + } + bool canEdit() const override + { + return true; + } + bool canExport() const override + { + return false; + } + shared_qobject_ptr createLaunchTask(AuthSessionPtr account) override + { + return nullptr; + } + IPathMatcher::Ptr getLogFileMatcher() override + { + return nullptr; + } + QString getLogFileRoot() override + { + return gameRoot(); + } + + QString getStatusbarDescription() override; + QStringList verboseDescription(AuthSessionPtr session) override; + + QProcessEnvironment createEnvironment() override + { + return QProcessEnvironment(); + } + QMap getVariables() const override + { + return {}; + } protected: - mutable std::shared_ptr jar_mod_list; - mutable std::shared_ptr m_world_list; + mutable std::shared_ptr jar_mod_list; + mutable std::shared_ptr m_world_list; }; diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp index 638b2e21..23b837c1 100644 --- a/api/logic/minecraft/legacy/LegacyModList.cpp +++ b/api/logic/minecraft/legacy/LegacyModList.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,153 +19,118 @@ #include LegacyModList::LegacyModList(const QString &dir, const QString &list_file) - : m_dir(dir), m_list_file(list_file) + : m_dir(dir), m_list_file(list_file) { - FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | - QDir::NoSymLinks); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); } - struct OrderItem - { - QString id; - bool enabled = false; - }; - typedef QList OrderList; + struct OrderItem + { + QString id; + bool enabled = false; + }; + typedef QList OrderList; -static void internalSort(QList &what) +static void internalSort(QList &what) { - auto predicate = [](const Mod &left, const Mod &right) - { - if (left.name() == right.name()) - { - return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0; - } - return left.name().localeAwareCompare(right.name()) < 0; - }; - std::sort(what.begin(), what.end(), predicate); + auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right) + { + return left.fileName().localeAwareCompare(right.fileName()) < 0; + }; + std::sort(what.begin(), what.end(), predicate); } static OrderList readListFile(const QString &m_list_file) { - OrderList itemList; - if (m_list_file.isNull() || m_list_file.isEmpty()) - return itemList; + OrderList itemList; + if (m_list_file.isNull() || m_list_file.isEmpty()) + return itemList; - QFile textFile(m_list_file); - if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) - return OrderList(); + QFile textFile(m_list_file); + if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text)) + return OrderList(); - QTextStream textStream; - textStream.setAutoDetectUnicode(true); - textStream.setDevice(&textFile); - while (true) - { - QString line = textStream.readLine(); - if (line.isNull() || line.isEmpty()) - break; - else - { - OrderItem it; - it.enabled = !line.endsWith(".disabled"); - if (!it.enabled) - { - line.chop(9); - } - it.id = line; - itemList.append(it); - } - } - textFile.close(); - return itemList; + QTextStream textStream; + textStream.setAutoDetectUnicode(true); + textStream.setDevice(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull() || line.isEmpty()) + break; + else + { + OrderItem it; + it.enabled = !line.endsWith(".disabled"); + if (!it.enabled) + { + line.chop(9); + } + it.id = line; + itemList.append(it); + } + } + textFile.close(); + return itemList; } bool LegacyModList::update() { - if (!m_dir.exists() || !m_dir.isReadable()) - return false; + if (!m_dir.exists() || !m_dir.isReadable()) + return false; - QList orderedMods; - QList newMods; - m_dir.refresh(); - auto folderContents = m_dir.entryInfoList(); - bool orderOrStateChanged = false; + QList orderedMods; + QList newMods; + m_dir.refresh(); + auto folderContents = m_dir.entryInfoList(); - // first, process the ordered items (if any) - OrderList listOrder = readListFile(m_list_file); - for (auto item : listOrder) - { - QFileInfo infoEnabled(m_dir.filePath(item.id)); - QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); - int idxEnabled = folderContents.indexOf(infoEnabled); - int idxDisabled = folderContents.indexOf(infoDisabled); - bool isEnabled; - // if both enabled and disabled versions are present, it's a special case... - if (idxEnabled >= 0 && idxDisabled >= 0) - { - // we only process the one we actually have in the order file. - // and exactly as we have it. - // THIS IS A CORNER CASE - isEnabled = item.enabled; - } - else - { - // only one is present. - // we pick the one that we found. - // we assume the mod was enabled/disabled by external means - isEnabled = idxEnabled >= 0; - } - int idx = isEnabled ? idxEnabled : idxDisabled; - QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; - // if the file from the index file exists - if (idx != -1) - { - // remove from the actual folder contents list - folderContents.takeAt(idx); - // append the new mod - orderedMods.append(Mod(info)); - if (isEnabled != item.enabled) - orderOrStateChanged = true; - } - else - { - orderOrStateChanged = true; - } - } - // if there are any untracked files... - if (folderContents.size()) - { - // the order surely changed! - for (auto entry : folderContents) - { - newMods.append(Mod(entry)); - } - internalSort(newMods); - orderedMods.append(newMods); - orderOrStateChanged = true; - } - // otherwise, if we were already tracking some mods - else if (mods.size()) - { - // if the number doesn't match, order changed. - if (mods.size() != orderedMods.size()) - orderOrStateChanged = true; - // if it does match, compare the mods themselves - else - for (int i = 0; i < mods.size(); i++) - { - if (!mods[i].strongCompare(orderedMods[i])) - { - orderOrStateChanged = true; - break; - } - } - } - mods.swap(orderedMods); - if (orderOrStateChanged && !m_list_file.isEmpty()) - { - qDebug() << "Mod list " << m_list_file << " changed!"; - } - return true; + // first, process the ordered items (if any) + OrderList listOrder = readListFile(m_list_file); + for (auto item : listOrder) + { + QFileInfo infoEnabled(m_dir.filePath(item.id)); + QFileInfo infoDisabled(m_dir.filePath(item.id + ".disabled")); + int idxEnabled = folderContents.indexOf(infoEnabled); + int idxDisabled = folderContents.indexOf(infoDisabled); + bool isEnabled; + // if both enabled and disabled versions are present, it's a special case... + if (idxEnabled >= 0 && idxDisabled >= 0) + { + // we only process the one we actually have in the order file. + // and exactly as we have it. + // THIS IS A CORNER CASE + isEnabled = item.enabled; + } + else + { + // only one is present. + // we pick the one that we found. + // we assume the mod was enabled/disabled by external means + isEnabled = idxEnabled >= 0; + } + int idx = isEnabled ? idxEnabled : idxDisabled; + QFileInfo &info = isEnabled ? infoEnabled : infoDisabled; + // if the file from the index file exists + if (idx != -1) + { + // remove from the actual folder contents list + folderContents.takeAt(idx); + // append the new mod + orderedMods.append(info); + } + } + // if there are any untracked files... append them sorted at the end + if (folderContents.size()) + { + for (auto entry : folderContents) + { + newMods.append(entry); + } + internalSort(newMods); + orderedMods.append(newMods); + } + mods.swap(orderedMods); + return true; } diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h index 19b191a7..9a7bea50 100644 --- a/api/logic/minecraft/legacy/LegacyModList.h +++ b/api/logic/minecraft/legacy/LegacyModList.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,38 +19,31 @@ #include #include -#include "minecraft/Mod.h" - #include "multimc_logic_export.h" -class LegacyInstance; -class BaseInstance; - -/** - * A legacy mod list. - * Backed by a folder. - */ class MULTIMC_LOGIC_EXPORT LegacyModList { public: - LegacyModList(const QString &dir, const QString &list_file = QString()); + using Mod = QFileInfo; + + LegacyModList(const QString &dir, const QString &list_file = QString()); - /// Reloads the mod list and returns true if the list changed. - bool update(); + /// Reloads the mod list and returns true if the list changed. + bool update(); - QDir dir() - { - return m_dir; - } + QDir dir() + { + return m_dir; + } - const QList & allMods() - { - return mods; - } + const QList & allMods() + { + return mods; + } protected: - QDir m_dir; - QString m_list_file; - QList mods; + QDir m_dir; + QString m_list_file; + QList mods; }; diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp index 5cc3b5d9..9d86a7b5 100644 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp @@ -1,5 +1,4 @@ #include "LegacyUpgradeTask.h" -#include "BaseInstanceProvider.h" #include "settings/INISettingsObject.h" #include "FileSystem.h" #include "NullInstance.h" @@ -8,132 +7,132 @@ #include "LegacyInstance.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/ComponentList.h" +#include "LegacyModList.h" #include "classparser.h" LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance) { - m_origInstance = origInstance; + m_origInstance = origInstance; } void LegacyUpgradeTask::executeTask() { - setStatus(tr("Copying instance %1").arg(m_origInstance->name())); + setStatus(tr("Copying instance %1").arg(m_origInstance->name())); - FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); - folderCopy.followSymlinks(true); + FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath); + folderCopy.followSymlinks(true); - m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); - connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &LegacyUpgradeTask::copyFinished); - connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &LegacyUpgradeTask::copyAborted); - m_copyFutureWatcher.setFuture(m_copyFuture); + m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); + connect(&m_copyFutureWatcher, &QFutureWatcher::finished, this, &LegacyUpgradeTask::copyFinished); + connect(&m_copyFutureWatcher, &QFutureWatcher::canceled, this, &LegacyUpgradeTask::copyAborted); + m_copyFutureWatcher.setFuture(m_copyFuture); } static QString decideVersion(const QString& currentVersion, const QString& intendedVersion) { - if(intendedVersion != currentVersion) - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - else if(!currentVersion.isEmpty()) - { - return currentVersion; - } - } - else - { - if(!intendedVersion.isEmpty()) - { - return intendedVersion; - } - } - return QString(); + if(intendedVersion != currentVersion) + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + else if(!currentVersion.isEmpty()) + { + return currentVersion; + } + } + else + { + if(!intendedVersion.isEmpty()) + { + return intendedVersion; + } + } + return QString(); } void LegacyUpgradeTask::copyFinished() { - auto successful = m_copyFuture.result(); - if(!successful) - { - emitFailed(tr("Instance folder copy failed.")); - return; - } - auto legacyInst = std::dynamic_pointer_cast(m_origInstance); + auto successful = m_copyFuture.result(); + if(!successful) + { + emitFailed(tr("Instance folder copy failed.")); + return; + } + auto legacyInst = std::dynamic_pointer_cast(m_origInstance); - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - // NOTE: this scope ensures the instance is fully saved before we emitSucceeded - { - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - inst.setName(m_instName); - inst.init(); + auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + // NOTE: this scope ensures the instance is fully saved before we emitSucceeded + { + MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); + inst.setName(m_instName); - QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); - if(preferredVersionNumber.isNull()) - { - // try to decide version based on the jar(s?) - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); - if(preferredVersionNumber.isNull()) - { - preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); - if(preferredVersionNumber.isNull()) - { - emitFailed(tr("Could not decide Minecraft version.")); - return; - } - } - } - auto components = inst.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", preferredVersionNumber, true); + QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId()); + if(preferredVersionNumber.isNull()) + { + // try to decide version based on the jar(s?) + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar()); + if(preferredVersionNumber.isNull()) + { + preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar()); + if(preferredVersionNumber.isNull()) + { + emitFailed(tr("Could not decide Minecraft version.")); + return; + } + } + } + auto components = inst.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", preferredVersionNumber, true); - QString jarPath = legacyInst->mainJarToPreserve(); - if(!jarPath.isNull()) - { - qDebug() << "Preserving base jar! : " << jarPath; - // FIXME: handle case when the jar is unreadable? - // TODO: check the hash, if it's the same as the upstream jar, do not do this - components->installCustomJar(jarPath); - } + QString jarPath = legacyInst->mainJarToPreserve(); + if(!jarPath.isNull()) + { + qDebug() << "Preserving base jar! : " << jarPath; + // FIXME: handle case when the jar is unreadable? + // TODO: check the hash, if it's the same as the upstream jar, do not do this + components->installCustomJar(jarPath); + } - auto jarMods = legacyInst->getJarMods(); - for(auto & jarMod: jarMods) - { - QString modPath = jarMod.filename().absoluteFilePath(); - qDebug() << "jarMod: " << modPath; - components->installJarMods({modPath}); - } + auto jarMods = legacyInst->jarModList()->allMods(); + for(auto & jarMod: jarMods) + { + QString modPath = jarMod.absoluteFilePath(); + qDebug() << "jarMod: " << modPath; + components->installJarMods({modPath}); + } - // remove all the extra garbage we no longer need - auto removeAll = [&](const QString &root, const QStringList &things) - { - for(auto &thing : things) - { - auto removePath = FS::PathCombine(root, thing); - QFileInfo stat(removePath); - if(stat.isDir()) - { - FS::deletePath(removePath); - } - else - { - QFile::remove(removePath); - } - } - }; - QStringList rootRemovables = {"modlist", "version", "instMods"}; - QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; - removeAll(inst.instanceRoot(), rootRemovables); - removeAll(inst.minecraftRoot(), mcRemovables); - } - emitSucceeded(); + // remove all the extra garbage we no longer need + auto removeAll = [&](const QString &root, const QStringList &things) + { + for(auto &thing : things) + { + auto removePath = FS::PathCombine(root, thing); + QFileInfo stat(removePath); + if(stat.isDir()) + { + FS::deletePath(removePath); + } + else + { + QFile::remove(removePath); + } + } + }; + QStringList rootRemovables = {"modlist", "version", "instMods"}; + QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"}; + removeAll(inst.instanceRoot(), rootRemovables); + removeAll(inst.gameRoot(), mcRemovables); + } + emitSucceeded(); } void LegacyUpgradeTask::copyAborted() { - emitFailed(tr("Instance folder copy has been aborted.")); - return; + emitFailed(tr("Instance folder copy has been aborted.")); + return; } diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h index a93dd0d9..e35e43b7 100644 --- a/api/logic/minecraft/legacy/LegacyUpgradeTask.h +++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.h @@ -11,22 +11,20 @@ #include "BaseInstance.h" -class BaseInstanceProvider; - class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public InstanceTask { - Q_OBJECT + Q_OBJECT public: - explicit LegacyUpgradeTask(InstancePtr origInstance); + explicit LegacyUpgradeTask(InstancePtr origInstance); protected: - //! Entry point for tasks. - virtual void executeTask() override; - void copyFinished(); - void copyAborted(); + //! Entry point for tasks. + virtual void executeTask() override; + void copyFinished(); + void copyAborted(); private: /* data */ - InstancePtr m_origInstance; - QFuture m_copyFuture; - QFutureWatcher m_copyFutureWatcher; + InstancePtr m_origInstance; + QFuture m_copyFuture; + QFutureWatcher m_copyFutureWatcher; }; diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp new file mode 100644 index 00000000..22ebd7d4 --- /dev/null +++ b/api/logic/minecraft/mod/LocalModParseTask.cpp @@ -0,0 +1,298 @@ +#include "LocalModParseTask.h" + +#include +#include +#include +#include +#include +#include + +#include "settings/INIFile.h" +#include "FileSystem.h" + +namespace { + +// NEW format +// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3 + +// OLD format: +// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc +std::shared_ptr ReadMCModInfo(QByteArray contents) +{ + auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr + { + if (!arr.at(0).isObject()) { + return nullptr; + } + std::shared_ptr details = std::make_shared(); + auto firstObj = arr.at(0).toObject(); + details->mod_id = firstObj.value("modid").toString(); + auto name = firstObj.value("name").toString(); + // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name + if(name != "Example Mod") { + details->name = name; + } + details->version = firstObj.value("version").toString(); + details->updateurl = firstObj.value("updateUrl").toString(); + auto homeurl = firstObj.value("url").toString().trimmed(); + if(!homeurl.isEmpty()) + { + // fix up url. + if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) + { + homeurl.prepend("http://"); + } + } + details->homeurl = homeurl; + details->description = firstObj.value("description").toString(); + QJsonArray authors = firstObj.value("authorList").toArray(); + if (authors.size() == 0) { + // FIXME: what is the format of this? is there any? + authors = firstObj.value("authors").toArray(); + } + + for (auto author: authors) + { + details->authors.append(author.toString()); + } + details->credits = firstObj.value("credits").toString(); + return details; + }; + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + // this is the very old format that had just the array + if (jsonDoc.isArray()) + { + return getInfoFromArray(jsonDoc.array()); + } + else if (jsonDoc.isObject()) + { + auto val = jsonDoc.object().value("modinfoversion"); + if(val.isUndefined()) { + val = jsonDoc.object().value("modListVersion"); + } + int version = val.toDouble(); + if (version != 2) + { + qCritical() << "BAD stuff happened to mod json:"; + qCritical() << contents; + return nullptr; + } + auto arrVal = jsonDoc.object().value("modlist"); + if(arrVal.isUndefined()) { + arrVal = jsonDoc.object().value("modList"); + } + if (arrVal.isArray()) + { + return getInfoFromArray(arrVal.toArray()); + } + } + return nullptr; +} + +// https://fabricmc.net/wiki/documentation:fabric_mod_json +std::shared_ptr ReadFabricModInfo(QByteArray contents) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = jsonDoc.object(); + auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0; + + std::shared_ptr details = std::make_shared(); + + details->mod_id = object.value("id").toString(); + details->version = object.value("version").toString(); + + details->name = object.contains("name") ? object.value("name").toString() : details->mod_id; + details->description = object.value("description").toString(); + + if (schemaVersion >= 1) + { + QJsonArray authors = object.value("authors").toArray(); + for (auto author: authors) + { + if(author.isObject()) { + details->authors.append(author.toObject().value("name").toString()); + } + else { + details->authors.append(author.toString()); + } + } + + if (object.contains("contact")) + { + QJsonObject contact = object.value("contact").toObject(); + + if (contact.contains("homepage")) + { + details->homeurl = contact.value("homepage").toString(); + } + } + } + return details; +} + +std::shared_ptr ReadForgeInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + // Read the data + details->name = "Minecraft Forge"; + details->mod_id = "Forge"; + details->homeurl = "http://www.minecraftforge.net/forum/"; + INIFile ini; + if (!ini.loadFile(contents)) + return details; + + QString major = ini.get("forge.major.number", "0").toString(); + QString minor = ini.get("forge.minor.number", "0").toString(); + QString revision = ini.get("forge.revision.number", "0").toString(); + QString build = ini.get("forge.build.number", "0").toString(); + + details->version = major + "." + minor + "." + revision + "." + build; + return details; +} + +std::shared_ptr ReadLiteModInfo(QByteArray contents) +{ + std::shared_ptr details = std::make_shared(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = jsonDoc.object(); + if (object.contains("name")) + { + details->mod_id = details->name = object.value("name").toString(); + } + if (object.contains("version")) + { + details->version = object.value("version").toString(""); + } + else + { + details->version = object.value("revision").toString(""); + } + details->mcversion = object.value("mcversion").toString(); + auto author = object.value("author").toString(); + if(!author.isEmpty()) { + details->authors.append(author); + } + details->description = object.value("description").toString(); + details->homeurl = object.value("url").toString(); + return details; +} + +} + +LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile): + m_token(token), + m_type(type), + m_modFile(modFile), + m_result(new Result()) +{ +} + +void LocalModParseTask::processAsZip() +{ + QuaZip zip(m_modFile.filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("mcmod.info")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadMCModInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + else if (zip.setCurrentFile("fabric.mod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadFabricModInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + else if (zip.setCurrentFile("forgeversion.properties")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadForgeInfo(file.readAll()); + file.close(); + zip.close(); + return; + } + + zip.close(); +} + +void LocalModParseTask::processAsFolder() +{ + QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); + if (mcmod_info.isFile()) + { + QFile mcmod(mcmod_info.filePath()); + if (!mcmod.open(QIODevice::ReadOnly)) + return; + auto data = mcmod.readAll(); + if (data.isEmpty() || data.isNull()) + return; + m_result->details = ReadMCModInfo(data); + } +} + +void LocalModParseTask::processAsLitemod() +{ + QuaZip zip(m_modFile.filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("litemod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadLiteModInfo(file.readAll()); + file.close(); + } + zip.close(); +} + +void LocalModParseTask::run() +{ + switch(m_type) + { + case Mod::MOD_ZIPFILE: + processAsZip(); + break; + case Mod::MOD_FOLDER: + processAsFolder(); + break; + case Mod::MOD_LITEMOD: + processAsLitemod(); + break; + default: + break; + } + emit finished(m_token); +} diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h new file mode 100644 index 00000000..0f119ba6 --- /dev/null +++ b/api/logic/minecraft/mod/LocalModParseTask.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include "Mod.h" +#include "ModDetails.h" + +class LocalModParseTask : public QObject, public QRunnable +{ + Q_OBJECT +public: + struct Result { + QString id; + std::shared_ptr details; + }; + using ResultPtr = std::shared_ptr; + ResultPtr result() const { + return m_result; + } + + LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile); + void run(); + +signals: + void finished(int token); + +private: + void processAsZip(); + void processAsFolder(); + void processAsLitemod(); + +private: + int m_token; + Mod::ModType m_type; + QFileInfo m_modFile; + ResultPtr m_result; +}; diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp new file mode 100644 index 00000000..df8b406d --- /dev/null +++ b/api/logic/minecraft/mod/Mod.cpp @@ -0,0 +1,151 @@ +/* Copyright 2013-2019 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 "Mod.h" +#include +#include + +namespace { + +ModDetails invalidDetails; + +} + + +Mod::Mod(const QFileInfo &file) +{ + repath(file); + m_changedDateTime = file.lastModified(); +} + +void Mod::repath(const QFileInfo &file) +{ + m_file = file; + QString name_base = file.fileName(); + + m_type = Mod::MOD_UNKNOWN; + + m_mmc_id = name_base; + + if (m_file.isDir()) + { + m_type = MOD_FOLDER; + m_name = name_base; + } + else if (m_file.isFile()) + { + if (name_base.endsWith(".disabled")) + { + m_enabled = false; + name_base.chop(9); + } + else + { + m_enabled = true; + } + if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) + { + m_type = MOD_ZIPFILE; + name_base.chop(4); + } + else if (name_base.endsWith(".litemod")) + { + m_type = MOD_LITEMOD; + name_base.chop(8); + } + else + { + m_type = MOD_SINGLEFILE; + } + m_name = name_base; + } +} + +bool Mod::enable(bool value) +{ + if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) + return false; + + if (m_enabled == value) + return false; + + QString path = m_file.absoluteFilePath(); + if (value) + { + QFile foo(path); + if (!path.endsWith(".disabled")) + return false; + path.chop(9); + if (!foo.rename(path)) + return false; + } + else + { + QFile foo(path); + path += ".disabled"; + if (!foo.rename(path)) + return false; + } + repath(QFileInfo(path)); + m_enabled = value; + return true; +} + +bool Mod::destroy() +{ + m_type = MOD_UNKNOWN; + return FS::deletePath(m_file.filePath()); +} + + +const ModDetails & Mod::details() const +{ + if(!m_localDetails) + return invalidDetails; + return *m_localDetails; +} + + +QString Mod::version() const +{ + return details().version; +} + +QString Mod::name() const +{ + auto & d = details(); + if(!d.name.isEmpty()) { + return d.name; + } + return m_name; +} + +QString Mod::homeurl() const +{ + return details().homeurl; +} + +QString Mod::description() const +{ + return details().description; +} + +QStringList Mod::authors() const +{ + return details().authors; +} diff --git a/api/logic/minecraft/mod/Mod.h b/api/logic/minecraft/mod/Mod.h new file mode 100644 index 00000000..d787fb48 --- /dev/null +++ b/api/logic/minecraft/mod/Mod.h @@ -0,0 +1,117 @@ +/* Copyright 2013-2019 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 "multimc_logic_export.h" + +#include "ModDetails.h" + + + +class MULTIMC_LOGIC_EXPORT Mod +{ +public: + enum ModType + { + MOD_UNKNOWN, //!< Indicates an unspecified mod type. + MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. + MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). + MOD_FOLDER, //!< The mod is in a folder on the filesystem. + MOD_LITEMOD, //!< The mod is a litemod + }; + + Mod() = default; + Mod(const QFileInfo &file); + + QFileInfo filename() const + { + return m_file; + } + QString mmc_id() const + { + return m_mmc_id; + } + ModType type() const + { + return m_type; + } + bool valid() + { + return m_type != MOD_UNKNOWN; + } + + QDateTime dateTimeChanged() const + { + return m_changedDateTime; + } + + bool enabled() const + { + return m_enabled; + } + + const ModDetails &details() const; + + QString name() const; + QString version() const; + QString homeurl() const; + QString description() const; + QStringList authors() const; + + bool enable(bool value); + + // delete all the files of this mod + bool destroy(); + + // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) + void repath(const QFileInfo &file); + + bool shouldResolve() { + return !m_resolving && !m_resolved; + } + bool isResolving() { + return m_resolving; + } + int resolutionTicket() + { + return m_resolutionTicket; + } + void setResolving(bool resolving, int resolutionTicket) { + m_resolving = resolving; + m_resolutionTicket = resolutionTicket; + } + void finishResolvingWithDetails(std::shared_ptr details){ + m_resolving = false; + m_resolved = true; + m_localDetails = details; + } + +protected: + QFileInfo m_file; + QDateTime m_changedDateTime; + QString m_mmc_id; + QString m_name; + bool m_enabled = true; + bool m_resolving = false; + bool m_resolved = false; + int m_resolutionTicket = 0; + ModType m_type = MOD_UNKNOWN; + std::shared_ptr m_localDetails; +}; diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h new file mode 100644 index 00000000..6ab4aee7 --- /dev/null +++ b/api/logic/minecraft/mod/ModDetails.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +struct ModDetails +{ + QString mod_id; + QString name; + QString version; + QString mcversion; + QString homeurl; + QString updateurl; + QString description; + QStringList authors; + QString credits; +}; diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp new file mode 100644 index 00000000..88349877 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderLoadTask.cpp @@ -0,0 +1,18 @@ +#include "ModFolderLoadTask.h" +#include + +ModFolderLoadTask::ModFolderLoadTask(QDir dir) : + m_dir(dir), m_result(new Result()) +{ +} + +void ModFolderLoadTask::run() +{ + m_dir.refresh(); + for (auto entry : m_dir.entryInfoList()) + { + Mod m(entry); + m_result->mods[m.mmc_id()] = m; + } + emit succeeded(); +} diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h new file mode 100644 index 00000000..8d720e65 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderLoadTask.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#include "Mod.h" +#include + +class ModFolderLoadTask : public QObject, public QRunnable +{ + Q_OBJECT +public: + struct Result { + QMap mods; + }; + using ResultPtr = std::shared_ptr; + ResultPtr result() const { + return m_result; + } + +public: + ModFolderLoadTask(QDir dir); + void run(); +signals: + void succeeded(); +private: + QDir m_dir; + ResultPtr m_result; +}; diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp new file mode 100644 index 00000000..59ccaaba --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel.cpp @@ -0,0 +1,554 @@ +/* Copyright 2013-2019 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 "ModFolderModel.h" +#include +#include +#include +#include +#include +#include +#include +#include "ModFolderLoadTask.h" +#include +#include +#include "LocalModParseTask.h" + +ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir) +{ + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + m_watcher = new QFileSystemWatcher(this); + connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +} + +void ModFolderModel::startWatching() +{ + if(is_watching) + return; + + update(); + + is_watching = m_watcher->addPath(m_dir.absolutePath()); + if (is_watching) + { + qDebug() << "Started watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to start watching " << m_dir.absolutePath(); + } +} + +void ModFolderModel::stopWatching() +{ + if(!is_watching) + return; + + is_watching = !m_watcher->removePath(m_dir.absolutePath()); + if (!is_watching) + { + qDebug() << "Stopped watching " << m_dir.absolutePath(); + } + else + { + qDebug() << "Failed to stop watching " << m_dir.absolutePath(); + } +} + +bool ModFolderModel::update() +{ + if (!isValid()) { + return false; + } + if(m_update) { + scheduled_update = true; + return true; + } + + auto task = new ModFolderLoadTask(m_dir); + m_update = task->result(); + QThreadPool *threadPool = QThreadPool::globalInstance(); + connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); + threadPool->start(task); + return true; +} + +void ModFolderModel::finishUpdate() +{ + QSet currentSet = modsIndex.keys().toSet(); + auto & newMods = m_update->mods; + QSet newSet = newMods.keys().toSet(); + + // see if the kept mods changed in some way + { + QSet kept = currentSet; + kept.intersect(newSet); + for(auto & keptMod: kept) { + auto & newMod = newMods[keptMod]; + auto row = modsIndex[keptMod]; + auto & currentMod = mods[row]; + if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) { + // no significant change, ignore... + continue; + } + auto & oldMod = mods[row]; + if(oldMod.isResolving()) { + activeTickets.remove(oldMod.resolutionTicket()); + } + oldMod = newMod; + resolveMod(mods[row]); + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + } + } + + // remove mods no longer present + { + QSet removed = currentSet; + QList removedRows; + removed.subtract(newSet); + for(auto & removedMod: removed) { + removedRows.append(modsIndex[removedMod]); + } + std::sort(removedRows.begin(), removedRows.end(), std::greater()); + for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) { + int removedIndex = *iter; + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + auto removedIter = mods.begin() + removedIndex; + if(removedIter->isResolving()) { + activeTickets.remove(removedIter->resolutionTicket()); + } + mods.erase(removedIter); + endRemoveRows(); + } + } + + // add new mods to the end + { + QSet added = newSet; + added.subtract(currentSet); + beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); + for(auto & addedMod: added) { + mods.append(newMods[addedMod]); + resolveMod(mods.last()); + } + endInsertRows(); + } + + // update index + { + modsIndex.clear(); + int idx = 0; + for(auto & mod: mods) { + modsIndex[mod.mmc_id()] = idx; + idx++; + } + } + + m_update.reset(); + + emit updateFinished(); + + if(scheduled_update) { + scheduled_update = false; + update(); + } +} + +void ModFolderModel::resolveMod(Mod& m) +{ + if(!m.shouldResolve()) { + return; + } + + auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename()); + auto result = task->result(); + result->id = m.mmc_id(); + activeTickets.insert(nextResolutionTicket, result); + m.setResolving(true, nextResolutionTicket); + nextResolutionTicket++; + QThreadPool *threadPool = QThreadPool::globalInstance(); + connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse); + threadPool->start(task); +} + +void ModFolderModel::finishModParse(int token) +{ + auto iter = activeTickets.find(token); + if(iter == activeTickets.end()) { + return; + } + auto result = *iter; + activeTickets.remove(token); + int row = modsIndex[result->id]; + auto & mod = mods[row]; + mod.finishResolvingWithDetails(result->details); + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); +} + +void ModFolderModel::disableInteraction(bool disabled) +{ + if (interaction_disabled == disabled) { + return; + } + interaction_disabled = disabled; + if(size()) { + emit dataChanged(index(0), index(size() - 1)); + } +} + +void ModFolderModel::directoryChanged(QString path) +{ + update(); +} + +bool ModFolderModel::isValid() +{ + return m_dir.exists() && m_dir.isReadable(); +} + +// FIXME: this does not take disabled mod (with extra .disable extension) into account... +bool ModFolderModel::installMod(const QString &filename) +{ + if(interaction_disabled) { + return false; + } + + // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName + auto originalPath = FS::NormalizePath(filename); + QFileInfo fileinfo(originalPath); + + if (!fileinfo.exists() || !fileinfo.isReadable()) + { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; + return false; + } + qDebug() << "installing: " << fileinfo.absoluteFilePath(); + + Mod installedMod(fileinfo); + if (!installedMod.valid()) + { + qDebug() << originalPath << "is not a valid mod. Ignoring it."; + return false; + } + + auto type = installedMod.type(); + if (type == Mod::MOD_UNKNOWN) + { + qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it."; + return false; + } + + auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName())); + if(originalPath == newpath) + { + qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; + return false; + } + + if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) + { + if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled"))) + { + if(!QFile::remove(newpath)) + { + // FIXME: report error in a user-visible way + qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; + return false; + } + qDebug() << newpath << "has been deleted."; + } + if (!QFile::copy(fileinfo.filePath(), newpath)) + { + qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; + // FIXME: report error in a user-visible way + return false; + } + FS::updateTimestamp(newpath); + installedMod.repath(newpath); + update(); + return true; + } + else if (type == Mod::MOD_FOLDER) + { + QString from = fileinfo.filePath(); + if(QFile::exists(newpath)) + { + qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath; + return false; + } + + if (!FS::copy(from, newpath)()) + { + qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; + return false; + } + installedMod.repath(newpath); + update(); + return true; + } + return false; +} + +bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) +{ + if(interaction_disabled) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto index: indexes) + { + if(index.column() != 0) { + continue; + } + setModStatus(index.row(), enable); + } + return true; +} + +bool ModFolderModel::deleteMods(const QModelIndexList& indexes) +{ + if(interaction_disabled) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto i: indexes) + { + Mod &m = mods[i.row()]; + m.destroy(); + } + return true; +} + +int ModFolderModel::columnCount(const QModelIndex &parent) const +{ + return NUM_COLUMNS; +} + +QVariant ModFolderModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int column = index.column(); + + if (row < 0 || row >= mods.size()) + return QVariant(); + + switch (role) + { + case Qt::DisplayRole: + switch (column) + { + case NameColumn: + return mods[row].name(); + case VersionColumn: { + switch(mods[row].type()) { + case Mod::MOD_FOLDER: + return tr("Folder"); + case Mod::MOD_SINGLEFILE: + return tr("File"); + default: + break; + } + return mods[row].version(); + } + case DateColumn: + return mods[row].dateTimeChanged(); + + default: + return QVariant(); + } + + case Qt::ToolTipRole: + return mods[row].mmc_id(); + + case Qt::CheckStateRole: + switch (column) + { + case ActiveColumn: + return mods[row].enabled() ? Qt::Checked : Qt::Unchecked; + default: + return QVariant(); + } + default: + return QVariant(); + } +} + +bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) + { + return false; + } + + if (role == Qt::CheckStateRole) + { + return setModStatus(index.row(), Toggle); + } + return false; +} + +bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) +{ + if(row < 0 || row >= mods.size()) { + return false; + } + + auto &mod = mods[row]; + bool desiredStatus; + switch(action) { + case Enable: + desiredStatus = true; + break; + case Disable: + desiredStatus = false; + break; + case Toggle: + default: + desiredStatus = !mod.enabled(); + break; + } + + if(desiredStatus == mod.enabled()) { + return true; + } + + // preserve the row, but change its ID + auto oldId = mod.mmc_id(); + if(!mod.enable(!mod.enabled())) { + return false; + } + auto newId = mod.mmc_id(); + if(modsIndex.contains(newId)) { + // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled + // But is it necessary? + } + modsIndex.remove(oldId); + modsIndex[newId] = row; + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + return true; +} + +QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return QString(); + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + case DateColumn: + return tr("Last changed"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case ActiveColumn: + return tr("Is the mod enabled?"); + case NameColumn: + return tr("The name of the mod."); + case VersionColumn: + return tr("The version of the mod."); + case DateColumn: + return tr("The date and time this mod was last changed (or added)."); + default: + return QVariant(); + } + default: + return QVariant(); + } + return QVariant(); +} + +Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + auto flags = defaultFlags; + if(interaction_disabled) { + flags &= ~Qt::ItemIsDropEnabled; + } + else + { + flags |= Qt::ItemIsDropEnabled; + if(index.isValid()) { + flags |= Qt::ItemIsUserCheckable; + } + } + return flags; +} + +Qt::DropActions ModFolderModel::supportedDropActions() const +{ + // copy from outside, move from within and other mod lists + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList ModFolderModel::mimeTypes() const +{ + QStringList types; + types << "text/uri-list"; + return types; +} + +bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) +{ + if (action == Qt::IgnoreAction) + { + return true; + } + + // check if the action is supported + if (!data || !(action & supportedDropActions())) + { + return false; + } + + // files dropped from outside? + if (data->hasUrls()) + { + auto urls = data->urls(); + for (auto url : urls) + { + // only local files may be dropped... + if (!url.isLocalFile()) + { + continue; + } + // TODO: implement not only copy, but also move + // FIXME: handle errors here + installMod(url.toLocalFile()); + } + return true; + } + return false; +} diff --git a/api/logic/minecraft/mod/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h new file mode 100644 index 00000000..8394e405 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel.h @@ -0,0 +1,145 @@ +/* Copyright 2013-2019 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 "Mod.h" + +#include "multimc_logic_export.h" +#include "ModFolderLoadTask.h" +#include "LocalModParseTask.h" + +class LegacyInstance; +class BaseInstance; +class QFileSystemWatcher; + +/** + * A legacy mod list. + * Backed by a folder. + */ +class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Columns + { + ActiveColumn = 0, + NameColumn, + VersionColumn, + DateColumn, + NUM_COLUMNS + }; + enum ModStatusAction { + Disable, + Enable, + Toggle + }; + ModFolderModel(const QString &dir); + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::DropActions supportedDropActions() const override; + + /// flags, mostly to support drag&drop + virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + QStringList mimeTypes() const override; + bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; + + virtual int rowCount(const QModelIndex &) const override + { + return size(); + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual int columnCount(const QModelIndex &parent) const override; + + size_t size() const + { + return mods.size(); + } + ; + bool empty() const + { + return size() == 0; + } + Mod &operator[](size_t index) + { + return mods[index]; + } + + /// Reloads the mod list and returns true if the list changed. + bool update(); + + /** + * Adds the given mod to the list at the given index - if the list supports custom ordering + */ + bool installMod(const QString& filename); + + /// Deletes all the selected mods + bool deleteMods(const QModelIndexList &indexes); + + /// Enable or disable listed mods + bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); + + void startWatching(); + void stopWatching(); + + bool isValid(); + + QDir dir() + { + return m_dir; + } + + const QList & allMods() + { + return mods; + } + +public slots: + void disableInteraction(bool disabled); + +private +slots: + void directoryChanged(QString path); + void finishUpdate(); + void finishModParse(int token); + +signals: + void updateFinished(); + +private: + void resolveMod(Mod& m); + bool setModStatus(int index, ModStatusAction action); + +protected: + QFileSystemWatcher *m_watcher; + bool is_watching = false; + ModFolderLoadTask::ResultPtr m_update; + bool scheduled_update = false; + bool interaction_disabled = false; + QDir m_dir; + QMap modsIndex; + QMap activeTickets; + int nextResolutionTicket = 0; + QList mods; +}; diff --git a/api/logic/minecraft/mod/ModFolderModel_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp new file mode 100644 index 00000000..76f16ed5 --- /dev/null +++ b/api/logic/minecraft/mod/ModFolderModel_test.cpp @@ -0,0 +1,53 @@ + +#include +#include +#include "TestUtil.h" + +#include "FileSystem.h" +#include "minecraft/mod/ModFolderModel.h" + +class ModFolderModelTest : public QObject +{ + Q_OBJECT + +private +slots: + // test for GH-1178 - install a folder with files to a mod list + void test_1178() + { + // source + QString source = QFINDTESTDATA("data/test_folder"); + + // sanity check + QVERIFY(!source.endsWith('/')); + + auto verify = [](QString path) + { + QDir target_dir(FS::PathCombine(path, "test_folder")); + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // 1. test with no trailing / + { + QString folder = source; + QTemporaryDir tempDir; + ModFolderModel m(tempDir.path()); + m.installMod(folder); + verify(tempDir.path()); + } + + // 2. test with trailing / + { + QString folder = source + '/'; + QTemporaryDir tempDir; + ModFolderModel m(tempDir.path()); + m.installMod(folder); + verify(tempDir.path()); + } + } +}; + +QTEST_GUILESS_MAIN(ModFolderModelTest) + +#include "ModFolderModel_test.moc" diff --git a/api/logic/minecraft/testdata/lib-native-arch.json b/api/logic/minecraft/testdata/lib-native-arch.json index a73aac54..501826ae 100644 --- a/api/logic/minecraft/testdata/lib-native-arch.json +++ b/api/logic/minecraft/testdata/lib-native-arch.json @@ -1,46 +1,46 @@ { - "downloads": { - "classifiers": { - "natives-osx": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", - "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", - "size": 418331, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" - }, - "natives-windows-32": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", - "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", - "size": 386792, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" - }, - "natives-windows-64": { - "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", - "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", - "size": 463390, - "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "tv.twitch:twitch-platform:5.16", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows-${arch}" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "linux" - } - } - ] + "downloads": { + "classifiers": { + "natives-osx": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar", + "sha1": "62503ee712766cf77f97252e5902786fd834b8c5", + "size": 418331, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-osx.jar" + }, + "natives-windows-32": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar", + "sha1": "7c6affe439099806a4f552da14c42f9d643d8b23", + "size": 386792, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar" + }, + "natives-windows-64": { + "path": "tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar", + "sha1": "39d0c3d363735b4785598e0e7fbf8297c706a9f9", + "size": 463390, + "url": "https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "tv.twitch:twitch-platform:5.16", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows-${arch}" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "linux" + } + } + ] } diff --git a/api/logic/minecraft/testdata/lib-native.json b/api/logic/minecraft/testdata/lib-native.json index 0a95b2b9..5b9f3b55 100644 --- a/api/logic/minecraft/testdata/lib-native.json +++ b/api/logic/minecraft/testdata/lib-native.json @@ -1,52 +1,52 @@ { - "downloads": { - "artifact": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", - "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", - "size": 22, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" - }, - "classifiers": { - "natives-linux": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", - "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", - "size": 578680, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" - }, - "natives-osx": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", - "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", - "size": 426822, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" - }, - "natives-windows": { - "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", - "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", - "size": 613748, - "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" - } - } - }, - "extract": { - "exclude": [ - "META-INF/" - ] - }, - "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", - "natives": { - "linux": "natives-linux", - "osx": "natives-osx", - "windows": "natives-windows" - }, - "rules": [ - { - "action": "allow" - }, - { - "action": "disallow", - "os": { - "name": "osx" - } - } - ] + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar", + "sha1": "b04f3ee8f5e43fa3b162981b50bb72fe1acabb33", + "size": 22, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar" + }, + "classifiers": { + "natives-linux": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar", + "sha1": "931074f46c795d2f7b30ed6395df5715cfd7675b", + "size": 578680, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-linux.jar" + }, + "natives-osx": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar", + "sha1": "bcab850f8f487c3f4c4dbabde778bb82bd1a40ed", + "size": 426822, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar" + }, + "natives-windows": { + "path": "org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar", + "sha1": "b84d5102b9dbfabfeb5e43c7e2828d98a7fc80e0", + "size": 613748, + "url": "https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-windows.jar" + } + } + }, + "extract": { + "exclude": [ + "META-INF/" + ] + }, + "name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209", + "natives": { + "linux": "natives-linux", + "osx": "natives-osx", + "windows": "natives-windows" + }, + "rules": [ + { + "action": "allow" + }, + { + "action": "disallow", + "os": { + "name": "osx" + } + } + ] } diff --git a/api/logic/minecraft/testdata/lib-simple.json b/api/logic/minecraft/testdata/lib-simple.json index 3ef0f490..90bbff07 100644 --- a/api/logic/minecraft/testdata/lib-simple.json +++ b/api/logic/minecraft/testdata/lib-simple.json @@ -1,11 +1,11 @@ { - "downloads": { - "artifact": { - "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", - "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", - "size": 5618, - "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" - } - }, - "name": "com.paulscode:codecwav:20101023" + "downloads": { + "artifact": { + "path": "com/paulscode/codecwav/20101023/codecwav-20101023.jar", + "sha1": "12f031cfe88fef5c1dd36c563c0a3a69bd7261da", + "size": 5618, + "url": "https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar" + } + }, + "name": "com.paulscode:codecwav:20101023" } diff --git a/api/logic/minecraft/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp index 2ad2b5b2..051b1279 100644 --- a/api/logic/minecraft/update/AssetUpdateTask.cpp +++ b/api/logic/minecraft/update/AssetUpdateTask.cpp @@ -7,96 +7,101 @@ AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } + +AssetUpdateTask::~AssetUpdateTask() +{ +} + void AssetUpdateTask::executeTask() { - setStatus(tr("Updating assets index...")); - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); - QUrl indexUrl = assets->url; - QString localPath = assets->id + ".json"; - auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); + setStatus(tr("Updating assets index...")); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); + QUrl indexUrl = assets->url; + QString localPath = assets->id + ".json"; + auto job = new NetJob(tr("Asset index for %1").arg(m_inst->name())); - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", localPath); - entry->setStale(true); - auto hexSha1 = assets->sha1.toLatin1(); - qDebug() << "Asset index SHA1:" << hexSha1; - auto dl = Net::Download::makeCached(indexUrl, entry); - auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); - job->addNetAction(dl); + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", localPath); + entry->setStale(true); + auto hexSha1 = assets->sha1.toLatin1(); + qDebug() << "Asset index SHA1:" << hexSha1; + auto dl = Net::Download::makeCached(indexUrl, entry); + auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); + job->addNetAction(dl); - downloadJob.reset(job); + downloadJob.reset(job); - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::assetIndexFinished); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - qDebug() << m_inst->name() << ": Starting asset index download"; - downloadJob->start(); + qDebug() << m_inst->name() << ": Starting asset index download"; + downloadJob->start(); } bool AssetUpdateTask::canAbort() const { - return true; + return true; } void AssetUpdateTask::assetIndexFinished() { - AssetsIndex index; - qDebug() << m_inst->name() << ": Finished asset index download"; + AssetsIndex index; + qDebug() << m_inst->name() << ": Finished asset index download"; - auto components = m_inst->getComponentList(); - auto profile = components->getProfile(); - auto assets = profile->getMinecraftAssets(); + auto components = m_inst->getComponentList(); + auto profile = components->getProfile(); + auto assets = profile->getMinecraftAssets(); - QString asset_fname = "assets/indexes/" + assets->id + ".json"; - // FIXME: this looks like a job for a generic validator based on json schema? - if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index)) - { - auto metacache = ENV.metacache(); - auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); - metacache->evictEntry(entry); - emitFailed(tr("Failed to read the assets index!")); - } + QString asset_fname = "assets/indexes/" + assets->id + ".json"; + // FIXME: this looks like a job for a generic validator based on json schema? + if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index)) + { + auto metacache = ENV.metacache(); + auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json"); + metacache->evictEntry(entry); + emitFailed(tr("Failed to read the assets index!")); + } - auto job = index.getDownloadJob(); - if(job) - { - setStatus(tr("Getting the assets files from Mojang...")); - downloadJob = job; - connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); - connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); - downloadJob->start(); - return; - } - emitSucceeded(); + auto job = index.getDownloadJob(); + if(job) + { + setStatus(tr("Getting the assets files from Mojang...")); + downloadJob = job; + connect(downloadJob.get(), &NetJob::succeeded, this, &AssetUpdateTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed); + connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress); + downloadJob->start(); + return; + } + emitSucceeded(); } void AssetUpdateTask::assetIndexFailed(QString reason) { - qDebug() << m_inst->name() << ": Failed asset index download"; - emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); + qDebug() << m_inst->name() << ": Failed asset index download"; + emitFailed(tr("Failed to download the assets index:\n%1").arg(reason)); } void AssetUpdateTask::assetsFailed(QString reason) { - emitFailed(tr("Failed to download assets:\n%1").arg(reason)); + emitFailed(tr("Failed to download assets:\n%1").arg(reason)); } bool AssetUpdateTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted AssetUpdateTask"; + } + return true; } diff --git a/api/logic/minecraft/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h index c666faa6..fdfa8f1c 100644 --- a/api/logic/minecraft/update/AssetUpdateTask.h +++ b/api/logic/minecraft/update/AssetUpdateTask.h @@ -5,22 +5,24 @@ class MinecraftInstance; class AssetUpdateTask : public Task { - Q_OBJECT + Q_OBJECT public: - AssetUpdateTask(MinecraftInstance * inst); - void executeTask() override; + AssetUpdateTask(MinecraftInstance * inst); + virtual ~AssetUpdateTask(); - bool canAbort() const override; + void executeTask() override; + + bool canAbort() const override; private slots: - void assetIndexFinished(); - void assetIndexFailed(QString reason); - void assetsFailed(QString reason); + void assetIndexFinished(); + void assetIndexFailed(QString reason); + void assetsFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; }; diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp index 1bd339e4..52a8375b 100644 --- a/api/logic/minecraft/update/FMLLibrariesTask.cpp +++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp @@ -7,125 +7,124 @@ FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } void FMLLibrariesTask::executeTask() { - // Get the mod list - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto components = inst->getComponentList(); - auto profile = components->getProfile(); + // Get the mod list + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto components = inst->getComponentList(); + auto profile = components->getProfile(); - if (!profile->hasTrait("legacyFML")) - { - emitSucceeded(); - return; - } + if (!profile->hasTrait("legacyFML")) + { + emitSucceeded(); + return; + } - QString version = components->getComponentVersion("net.minecraft"); - auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; - if (!fmlLibsMapping.contains(version)) - { - emitSucceeded(); - return; - } + QString version = components->getComponentVersion("net.minecraft"); + auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping; + if (!fmlLibsMapping.contains(version)) + { + emitSucceeded(); + return; + } - auto &libList = fmlLibsMapping[version]; + auto &libList = fmlLibsMapping[version]; - // determine if we need some libs for FML or forge - setStatus(tr("Checking for FML libraries...")); - if(!components->getComponent("net.minecraftforge")) - { - emitSucceeded(); - return; - } + // determine if we need some libs for FML or forge + setStatus(tr("Checking for FML libraries...")); + if(!components->getComponent("net.minecraftforge")) + { + emitSucceeded(); + return; + } - // now check the lib folder inside the instance for files. - for (auto &lib : libList) - { - QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); - if (libInfo.exists()) - continue; - fmlLibsToProcess.append(lib); - } + // now check the lib folder inside the instance for files. + for (auto &lib : libList) + { + QFileInfo libInfo(FS::PathCombine(inst->libDir(), lib.filename)); + if (libInfo.exists()) + continue; + fmlLibsToProcess.append(lib); + } - // if everything is in place, there's nothing to do here... - if (fmlLibsToProcess.isEmpty()) - { - emitSucceeded(); - return; - } + // if everything is in place, there's nothing to do here... + if (fmlLibsToProcess.isEmpty()) + { + emitSucceeded(); + return; + } - // download missing libs to our place - setStatus(tr("Dowloading FML libraries...")); - auto dljob = new NetJob("FML libraries"); - auto metacache = ENV.metacache(); - for (auto &lib : fmlLibsToProcess) - { - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename - : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); - } + // download missing libs to our place + setStatus(tr("Dowloading FML libraries...")); + auto dljob = new NetJob("FML libraries"); + auto metacache = ENV.metacache(); + for (auto &lib : fmlLibsToProcess) + { + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + QString urlString = (lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL : URLConstants::FMLLIBS_FORGE_BASE_URL) + lib.filename; + dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); + } - connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); - connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); - connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); - downloadJob.reset(dljob); - downloadJob->start(); + connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); + connect(dljob, &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed); + connect(dljob, &NetJob::progress, this, &FMLLibrariesTask::progress); + downloadJob.reset(dljob); + downloadJob->start(); } bool FMLLibrariesTask::canAbort() const { - return true; + return true; } void FMLLibrariesTask::fmllibsFinished() { - downloadJob.reset(); - if (!fmlLibsToProcess.isEmpty()) - { - setStatus(tr("Copying FML libraries into the instance...")); - MinecraftInstance *inst = (MinecraftInstance *)m_inst; - auto metacache = ENV.metacache(); - int index = 0; - for (auto &lib : fmlLibsToProcess) - { - progress(index, fmlLibsToProcess.size()); - auto entry = metacache->resolveEntry("fmllibs", lib.filename); - auto path = FS::PathCombine(inst->libDir(), lib.filename); - if (!FS::ensureFilePathExists(path)) - { - emitFailed(tr("Failed creating FML library folder inside the instance.")); - return; - } - if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) - { - emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); - return; - } - index++; - } - progress(index, fmlLibsToProcess.size()); - } - emitSucceeded(); + downloadJob.reset(); + if (!fmlLibsToProcess.isEmpty()) + { + setStatus(tr("Copying FML libraries into the instance...")); + MinecraftInstance *inst = (MinecraftInstance *)m_inst; + auto metacache = ENV.metacache(); + int index = 0; + for (auto &lib : fmlLibsToProcess) + { + progress(index, fmlLibsToProcess.size()); + auto entry = metacache->resolveEntry("fmllibs", lib.filename); + auto path = FS::PathCombine(inst->libDir(), lib.filename); + if (!FS::ensureFilePathExists(path)) + { + emitFailed(tr("Failed creating FML library folder inside the instance.")); + return; + } + if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename))) + { + emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename)); + return; + } + index++; + } + progress(index, fmlLibsToProcess.size()); + } + emitSucceeded(); } void FMLLibrariesTask::fmllibsFailed(QString reason) { - QStringList failed = downloadJob->getFailedFiles(); - QString failed_all = failed.join("\n"); - emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); + QStringList failed = downloadJob->getFailedFiles(); + QString failed_all = failed.join("\n"); + emitFailed(tr("Failed to download the following files:\n%1\n\nReason:%2\nPlease try again.").arg(failed_all, reason)); } bool FMLLibrariesTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted FMLLibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted FMLLibrariesTask"; + } + return true; } diff --git a/api/logic/minecraft/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h index 85f45468..a1e70ed4 100644 --- a/api/logic/minecraft/update/FMLLibrariesTask.h +++ b/api/logic/minecraft/update/FMLLibrariesTask.h @@ -7,24 +7,25 @@ class MinecraftInstance; class FMLLibrariesTask : public Task { - Q_OBJECT + Q_OBJECT public: - FMLLibrariesTask(MinecraftInstance * inst); + FMLLibrariesTask(MinecraftInstance * inst); + virtual ~FMLLibrariesTask() {}; - void executeTask() override; + void executeTask() override; - bool canAbort() const override; + bool canAbort() const override; private slots: - void fmllibsFinished(); - void fmllibsFailed(QString reason); + void fmllibsFinished(); + void fmllibsFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; - QList fmlLibsToProcess; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; + QList fmlLibsToProcess; }; diff --git a/api/logic/minecraft/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp index 34e2292a..e2b1bb48 100644 --- a/api/logic/minecraft/update/FoldersTask.cpp +++ b/api/logic/minecraft/update/FoldersTask.cpp @@ -3,19 +3,19 @@ #include FoldersTask::FoldersTask(MinecraftInstance * inst) - :Task() + :Task() { - m_inst = inst; + m_inst = inst; } void FoldersTask::executeTask() { - // Make directories - QDir mcDir(m_inst->minecraftRoot()); - if (!mcDir.exists() && !mcDir.mkpath(".")) - { - emitFailed(tr("Failed to create folder for minecraft binaries.")); - return; - } - emitSucceeded(); + // Make directories + QDir mcDir(m_inst->gameRoot()); + if (!mcDir.exists() && !mcDir.mkpath(".")) + { + emitFailed(tr("Failed to create folder for minecraft binaries.")); + return; + } + emitSucceeded(); } diff --git a/api/logic/minecraft/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h index 6e669b1e..f6ed5e6d 100644 --- a/api/logic/minecraft/update/FoldersTask.h +++ b/api/logic/minecraft/update/FoldersTask.h @@ -5,11 +5,13 @@ class MinecraftInstance; class FoldersTask : public Task { - Q_OBJECT + Q_OBJECT public: - FoldersTask(MinecraftInstance * inst); - void executeTask() override; + FoldersTask(MinecraftInstance * inst); + virtual ~FoldersTask() {}; + + void executeTask() override; private: - MinecraftInstance *m_inst; + MinecraftInstance *m_inst; }; diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp index 0bec61c1..912f492b 100644 --- a/api/logic/minecraft/update/LibrariesTask.cpp +++ b/api/logic/minecraft/update/LibrariesTask.cpp @@ -5,86 +5,85 @@ LibrariesTask::LibrariesTask(MinecraftInstance * inst) { - m_inst = inst; + m_inst = inst; } void LibrariesTask::executeTask() { - setStatus(tr("Getting the library files from Mojang...")); - qDebug() << m_inst->name() << ": downloading libraries"; - MinecraftInstance *inst = (MinecraftInstance *)m_inst; + setStatus(tr("Getting the library files from Mojang...")); + qDebug() << m_inst->name() << ": downloading libraries"; + MinecraftInstance *inst = (MinecraftInstance *)m_inst; - // Build a list of URLs that will need to be downloaded. - auto components = inst->getComponentList(); - auto profile = components->getProfile(); + // Build a list of URLs that will need to be downloaded. + auto components = inst->getComponentList(); + auto profile = components->getProfile(); - auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); - downloadJob.reset(job); + auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name())); + downloadJob.reset(job); - auto metacache = ENV.metacache(); - QList brokenLocalLibs; - QStringList failedFiles; - auto createJob = [&](const LibraryPtr & lib) - { - if(!lib) - { - emitFailed(tr("Null jar is specified in the metadata, aborting.")); - return; - } - auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles, inst->getLocalLibraryPath()); - for(auto dl : dls) - { - downloadJob->addNetAction(dl); - } - }; - auto createJobs = [&](const QList & libs) - { - for (auto lib : libs) - { - createJob(lib); - } - }; - createJobs(profile->getLibraries()); - createJobs(profile->getNativeLibraries()); - createJobs(profile->getJarMods()); - createJob(profile->getMainJar()); + auto metacache = ENV.metacache(); - // FIXME: this is never filled!!!! - if (!brokenLocalLibs.empty()) - { - downloadJob.reset(); - QString failed_all = failedFiles.join("\n"); - emitFailed(tr("Some libraries marked as 'local' are missing their jar " - "files:\n%1\n\nYou'll have to correct this problem manually. If this is " - "an externally tracked instance, make sure to run it at least once " - "outside of MultiMC.").arg(failed_all)); - return; - } - connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); - connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); - connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); - downloadJob->start(); + auto processArtifactPool = [&](const QList & pool, QStringList & errors, const QString & localPath) + { + for (auto lib : pool) + { + if(!lib) + { + emitFailed(tr("Null jar is specified in the metadata, aborting.")); + return false; + } + auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); + for(auto dl : dls) + { + downloadJob->addNetAction(dl); + } + } + return true; + }; + + QStringList failedLocalLibraries; + QList libArtifactPool; + libArtifactPool.append(profile->getLibraries()); + libArtifactPool.append(profile->getNativeLibraries()); + libArtifactPool.append(profile->getMainJar()); + processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath()); + + QStringList failedLocalJarMods; + processArtifactPool(profile->getJarMods(), failedLocalJarMods, inst->jarModsDir()); + + if (!failedLocalJarMods.empty() || !failedLocalLibraries.empty()) + { + downloadJob.reset(); + QString failed_all = (failedLocalLibraries + failedLocalJarMods).join("\n"); + emitFailed(tr("Some artifacts marked as 'local' are missing their files:\n%1\n\nYou need to either add the files, or removed the packages that require them.\nYou'll have to correct this problem manually.").arg(failed_all)); + return; + } + + connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded); + connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed); + connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress); + downloadJob->start(); } bool LibrariesTask::canAbort() const { - return true; + return true; } void LibrariesTask::jarlibFailed(QString reason) { - emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); + emitFailed(tr("Game update failed: it was impossible to fetch the required libraries.\nReason:\n%1").arg(reason)); } bool LibrariesTask::abort() { - if(downloadJob) - { - return downloadJob->abort(); - } - else - { - qWarning() << "Prematurely aborted LibrariesTask"; - } - return true; + if(downloadJob) + { + return downloadJob->abort(); + } + else + { + qWarning() << "Prematurely aborted LibrariesTask"; + } + return true; } diff --git a/api/logic/minecraft/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h index d06a5037..49f76932 100644 --- a/api/logic/minecraft/update/LibrariesTask.h +++ b/api/logic/minecraft/update/LibrariesTask.h @@ -5,21 +5,22 @@ class MinecraftInstance; class LibrariesTask : public Task { - Q_OBJECT + Q_OBJECT public: - LibrariesTask(MinecraftInstance * inst); + LibrariesTask(MinecraftInstance * inst); + virtual ~LibrariesTask() {}; - void executeTask() override; + void executeTask() override; - bool canAbort() const override; + bool canAbort() const override; private slots: - void jarlibFailed(QString reason); + void jarlibFailed(QString reason); public slots: - bool abort() override; + bool abort() override; private: - MinecraftInstance *m_inst; - NetJobPtr downloadJob; + MinecraftInstance *m_inst; + NetJobPtr downloadJob; }; diff --git a/api/logic/modplatform/flame/FileResolvingTask.cpp b/api/logic/modplatform/flame/FileResolvingTask.cpp index efc73621..295574f0 100644 --- a/api/logic/modplatform/flame/FileResolvingTask.cpp +++ b/api/logic/modplatform/flame/FileResolvingTask.cpp @@ -1,117 +1,63 @@ #include "FileResolvingTask.h" #include "Json.h" -const char * metabase = "https://cursemeta.dries007.net"; +namespace { + const char * metabase = "https://cursemeta.dries007.net"; +} Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) - : m_toProcess(toProcess) + : m_toProcess(toProcess) { } void Flame::FileResolvingTask::executeTask() { - setStatus(tr("Resolving mod IDs...")); - setProgress(0, m_toProcess.files.size()); - m_dljob.reset(new NetJob("Mod id resolver")); - results.resize(m_toProcess.files.size()); - int index = 0; - for(auto & file: m_toProcess.files) - { - auto projectIdStr = QString::number(file.projectId); - auto fileIdStr = QString::number(file.fileId); - QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); - auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); - m_dljob->addNetAction(dl); - index ++; - } - connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); - m_dljob->start(); + setStatus(tr("Resolving mod IDs...")); + setProgress(0, m_toProcess.files.size()); + m_dljob.reset(new NetJob("Mod id resolver")); + results.resize(m_toProcess.files.size()); + int index = 0; + for(auto & file: m_toProcess.files) + { + auto projectIdStr = QString::number(file.projectId); + auto fileIdStr = QString::number(file.fileId); + QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results[index]); + m_dljob->addNetAction(dl); + index ++; + } + connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished); + m_dljob->start(); } void Flame::FileResolvingTask::netJobFinished() { - bool failed = false; - int index = 0; - for(auto & bytes: results) - { - try - { - auto doc = Json::requireDocument(bytes); - auto obj = Json::requireObject(doc); - auto & out = m_toProcess.files[index]; - // result code signifies true failure. - if(obj.contains("code")) - { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:"; - qCritical() << bytes; - failed = true; - continue; - } - out.fileName = Json::requireString(obj, "FileNameOnDisk"); - QString rawUrl = Json::requireString(obj, "DownloadURL"); - out.url = QUrl(rawUrl, QUrl::TolerantMode); - if(!out.url.isValid()) - { - throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); - } - // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience - // It is also optional - QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); - if(!projObj.isEmpty()) - { - QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); - if(strType == "singlefile") - { - out.type = File::Type::SingleFile; - } - else if(strType == "ctoc") - { - out.type = File::Type::Ctoc; - } - else if(strType == "cmod2") - { - out.type = File::Type::Cmod2; - } - else if(strType == "mod") - { - out.type = File::Type::Mod; - } - else if(strType == "folder") - { - out.type = File::Type::Folder; - } - else if(strType == "modpack") - { - out.type = File::Type::Modpack; - } - else - { - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType; - out.type = File::Type::Unknown; - failed = true; - continue; - } - out.targetFolder = Json::ensureString(projObj, "Path", "mods"); - } - out.resolved = true; - } - catch(JSONValidationError & e) - { - auto & out = m_toProcess.files[index]; - qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; - qCritical() << e.cause(); - qCritical() << "JSON:"; - qCritical() << bytes; - failed = true; - } - index++; - } - if(!failed) - { - emitSucceeded(); - } - else - { - emitFailed(tr("Some mod ID resolving tasks failed.")); - } + bool failed = false; + int index = 0; + for(auto & bytes: results) + { + auto & out = m_toProcess.files[index]; + try + { + failed &= (!out.parseFromBytes(bytes)); + } + catch (const JSONValidationError &e) + { + + qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:"; + qCritical() << e.cause(); + qCritical() << "JSON:"; + qCritical() << bytes; + failed = true; + } + index++; + } + if(!failed) + { + emitSucceeded(); + } + else + { + emitFailed(tr("Some mod ID resolving tasks failed.")); + } } diff --git a/api/logic/modplatform/flame/FileResolvingTask.h b/api/logic/modplatform/flame/FileResolvingTask.h index 00658452..5679b907 100644 --- a/api/logic/modplatform/flame/FileResolvingTask.h +++ b/api/logic/modplatform/flame/FileResolvingTask.h @@ -10,23 +10,25 @@ namespace Flame { class MULTIMC_LOGIC_EXPORT FileResolvingTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit FileResolvingTask(Flame::Manifest &toProcess); - const Flame::Manifest &getResults() const - { - return m_toProcess; - } + explicit FileResolvingTask(Flame::Manifest &toProcess); + virtual ~FileResolvingTask() {}; + + const Flame::Manifest &getResults() const + { + return m_toProcess; + } protected: - virtual void executeTask() override; + virtual void executeTask() override; protected slots: - void netJobFinished(); + void netJobFinished(); private: /* data */ - Flame::Manifest m_toProcess; - QVector results; - NetJobPtr m_dljob; + Flame::Manifest m_toProcess; + QVector results; + NetJobPtr m_dljob; }; } diff --git a/api/logic/modplatform/flame/PackManifest.cpp b/api/logic/modplatform/flame/PackManifest.cpp index 6a9324fe..1db0a161 100644 --- a/api/logic/modplatform/flame/PackManifest.cpp +++ b/api/logic/modplatform/flame/PackManifest.cpp @@ -3,64 +3,124 @@ static void loadFileV1(Flame::File & f, QJsonObject & file) { - f.projectId = Json::requireInteger(file, "projectID"); - f.fileId = Json::requireInteger(file, "fileID"); - f.required = Json::ensureBoolean(file, QString("required"), true); + f.projectId = Json::requireInteger(file, "projectID"); + f.fileId = Json::requireInteger(file, "fileID"); + f.required = Json::ensureBoolean(file, QString("required"), true); } static void loadModloaderV1(Flame::Modloader & m, QJsonObject & modLoader) { - m.id = Json::requireString(modLoader, "id"); - m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); + m.id = Json::requireString(modLoader, "id"); + m.primary = Json::ensureBoolean(modLoader, QString("primary"), false); } static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft) { - m.version = Json::requireString(minecraft, "version"); - // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack - // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing - m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); - auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); - for (const auto & item : arr) - { - auto obj = Json::requireObject(item); - Flame::Modloader loader; - loadModloaderV1(loader, obj); - m.modLoaders.append(loader); - } + m.version = Json::requireString(minecraft, "version"); + // extra libraries... apparently only used for a custom Minecraft launcher in the 1.2.5 FTB retro pack + // intended use is likely hardcoded in the 'Flame' client, the manifest says nothing + m.libraries = Json::ensureString(minecraft, QString("libraries"), QString()); + auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray()); + for (const auto & item : arr) + { + auto obj = Json::requireObject(item); + Flame::Modloader loader; + loadModloaderV1(loader, obj); + m.modLoaders.append(loader); + } } static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest) { - auto mc = Json::requireObject(manifest, "minecraft"); - loadMinecraftV1(m.minecraft, mc); - m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); - m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); - auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (const auto & item : arr) - { - auto obj = Json::requireObject(item); - Flame::File file; - loadFileV1(file, obj); - m.files.append(file); - } - m.overrides = Json::ensureString(manifest, "overrides", "overrides"); + auto mc = Json::requireObject(manifest, "minecraft"); + loadMinecraftV1(m.minecraft, mc); + m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); + m.version = Json::ensureString(manifest, QString("version"), QString()); + m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward"); + auto arr = Json::ensureArray(manifest, "files", QJsonArray()); + for (const auto & item : arr) + { + auto obj = Json::requireObject(item); + Flame::File file; + loadFileV1(file, obj); + m.files.append(file); + } + m.overrides = Json::ensureString(manifest, "overrides", "overrides"); } void Flame::loadManifest(Flame::Manifest & m, const QString &filepath) { - auto doc = Json::requireDocument(filepath); - auto obj = Json::requireObject(doc); - m.manifestType = Json::requireString(obj, "manifestType"); - if(m.manifestType != "minecraftModpack") - { - throw JSONValidationError("Not a modpack manifest!"); - } - m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); - if(m.manifestVersion != 1) - { - throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); - } - loadManifestV1(m, obj); + auto doc = Json::requireDocument(filepath); + auto obj = Json::requireObject(doc); + m.manifestType = Json::requireString(obj, "manifestType"); + if(m.manifestType != "minecraftModpack") + { + throw JSONValidationError("Not a modpack manifest!"); + } + m.manifestVersion = Json::requireInteger(obj, "manifestVersion"); + if(m.manifestVersion != 1) + { + throw JSONValidationError(QString("Unknown manifest version (%1)").arg(m.manifestVersion)); + } + loadManifestV1(m, obj); +} + +bool Flame::File::parseFromBytes(const QByteArray& bytes) +{ + auto doc = Json::requireDocument(bytes); + auto obj = Json::requireObject(doc); + // result code signifies true failure. + if(obj.contains("code")) + { + qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:"; + qCritical() << bytes; + return false; + } + fileName = Json::requireString(obj, "FileNameOnDisk"); + QString rawUrl = Json::requireString(obj, "DownloadURL"); + url = QUrl(rawUrl, QUrl::TolerantMode); + if(!url.isValid()) + { + throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); + } + // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience + // It is also optional + QJsonObject projObj = Json::ensureObject(obj, "_Project", {}); + if(!projObj.isEmpty()) + { + QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower(); + if(strType == "singlefile") + { + type = File::Type::SingleFile; + } + else if(strType == "ctoc") + { + type = File::Type::Ctoc; + } + else if(strType == "cmod2") + { + type = File::Type::Cmod2; + } + else if(strType == "mod") + { + type = File::Type::Mod; + } + else if(strType == "folder") + { + type = File::Type::Folder; + } + else if(strType == "modpack") + { + type = File::Type::Modpack; + } + else + { + qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType; + type = File::Type::Unknown; + return false; + } + targetFolder = Json::ensureString(projObj, "Path", "mods"); + } + resolved = true; + return true; } diff --git a/api/logic/modplatform/flame/PackManifest.h b/api/logic/modplatform/flame/PackManifest.h index 1a5254a8..02f39f0e 100644 --- a/api/logic/modplatform/flame/PackManifest.h +++ b/api/logic/modplatform/flame/PackManifest.h @@ -8,51 +8,54 @@ namespace Flame { struct File { - int projectId = 0; - int fileId = 0; - // NOTE: the opposite to 'optional'. This is at the time of writing unused. - bool required = true; - - // our - bool resolved = false; - QString fileName; - QUrl url; - QString targetFolder = QLatin1Literal("mods"); - enum class Type - { - Unknown, - Folder, - Ctoc, - SingleFile, - Cmod2, - Modpack, - Mod - } type = Type::Mod; + // NOTE: throws JSONValidationError + bool parseFromBytes(const QByteArray &bytes); + + int projectId = 0; + int fileId = 0; + // NOTE: the opposite to 'optional'. This is at the time of writing unused. + bool required = true; + + // our + bool resolved = false; + QString fileName; + QUrl url; + QString targetFolder = QLatin1Literal("mods"); + enum class Type + { + Unknown, + Folder, + Ctoc, + SingleFile, + Cmod2, + Modpack, + Mod + } type = Type::Mod; }; struct Modloader { - QString id; - bool primary = false; + QString id; + bool primary = false; }; struct Minecraft { - QString version; - QString libraries; - QVector modLoaders; + QString version; + QString libraries; + QVector modLoaders; }; struct Manifest { - QString manifestType; - int manifestVersion = 0; - Flame::Minecraft minecraft; - QString name; - QString version; - QString author; - QVector files; - QString overrides; + QString manifestType; + int manifestVersion = 0; + Flame::Minecraft minecraft; + QString name; + QString version; + QString author; + QVector files; + QString overrides; }; void loadManifest(Flame::Manifest & m, const QString &filepath); diff --git a/api/logic/modplatform/flame/UrlResolvingTask.cpp b/api/logic/modplatform/flame/UrlResolvingTask.cpp new file mode 100644 index 00000000..efea350c --- /dev/null +++ b/api/logic/modplatform/flame/UrlResolvingTask.cpp @@ -0,0 +1,175 @@ +#include "UrlResolvingTask.h" +#include +#include + + +namespace { + const char * metabase = "https://cursemeta.dries007.net"; +} + +Flame::UrlResolvingTask::UrlResolvingTask(const QString& toProcess) + : m_url(toProcess) +{ +} + +void Flame::UrlResolvingTask::executeTask() +{ + resolveUrl(); +} + +void Flame::UrlResolvingTask::resolveUrl() +{ + setStatus(tr("Resolving URL...")); + setProgress(0, 1); + QUrl actualUrl(m_url); + if(actualUrl.host() != "www.curseforge.com") { + emitFailed(tr("Not a Twitch URL.")); + return; + } + m_dljob.reset(new NetJob("URL resolver")); + + bool weAreDigging = false; + needle = QString(); + + if(m_url.startsWith("https://")) { + if(m_url.endsWith("?client=y")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download?client=y + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088?client=y + m_url.chop(9); + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088 + } + if(m_url.endsWith("/download")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download -> need to dig inside html... + weAreDigging = true; + needle = m_url; + needle.replace("https://", "twitch://"); + needle.replace("/download", "/download-client/"); + m_url.append("?client=y"); + } else if (m_url.contains("/download/")) { + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download/2697088 + m_url.replace("/download/", "/download-client/"); + } + } + else if(m_url.startsWith("twitch://")) { + // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + m_url.replace(0, 9, "https://"); + // https://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + } + auto dl = Net::Download::makeByteArray(QUrl(m_url), &results); + m_dljob->addNetAction(dl); + if(weAreDigging) { + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processHTML); + } else { + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCCIP); + } + m_dljob->start(); +} + +void Flame::UrlResolvingTask::processHTML() +{ + QString htmlDoc = QString::fromUtf8(results); + auto index = htmlDoc.indexOf(needle); + if(index < 0) { + emitFailed(tr("Couldn't find the needle in the haystack...")); + return; + } + auto indexStart = index; + int indexEnd = -1; + while((index + 1) < htmlDoc.size() && htmlDoc[index] != '"') { + index ++; + if(htmlDoc[index] == '"') { + indexEnd = index; + break; + } + } + if(indexEnd > 0) { + QString found = htmlDoc.mid(indexStart, indexEnd - indexStart); + qDebug() << "Found needle: " << found; + // twitch://www.curseforge.com/minecraft/modpacks/ftb-sky-odyssey/download-client/2697088 + m_url = found; + resolveUrl(); + return; + } + emitFailed(tr("Couldn't find the end of the needle in the haystack...")); + return; +} + +void Flame::UrlResolvingTask::processCCIP() +{ + QDomDocument doc; + if (!doc.setContent(results)) { + qDebug() << results; + emitFailed(tr("Resolving failed.")); + return; + } + auto packageNode = doc.namedItem("package"); + if(!packageNode.isElement()) { + emitFailed(tr("Resolving failed: missing package root element.")); + return; + } + auto projectNode = packageNode.namedItem("project"); + if(!projectNode.isElement()) { + emitFailed(tr("Resolving failed: missing project element.")); + return; + } + auto attribs = projectNode.attributes(); + + auto projectIdNode = attribs.namedItem("id"); + if(!projectIdNode.isAttr()) { + emitFailed(tr("Resolving failed: missing id attribute.")); + return; + } + auto fileIdNode = attribs.namedItem("file"); + if(!fileIdNode.isAttr()) { + emitFailed(tr("Resolving failed: missing file attribute.")); + return; + } + + auto projectId = projectIdNode.nodeValue(); + auto fileId = fileIdNode.nodeValue(); + bool success = true; + m_result.projectId = projectId.toInt(&success); + if(!success) { + emitFailed(tr("Failed to resove projectId as a number.")); + return; + } + m_result.fileId = fileId.toInt(&success); + if(!success) { + emitFailed(tr("Failed to resove fileId as a number.")); + return; + } + qDebug() << "Resolved" << m_url << "as" << m_result.projectId << "/" << m_result.fileId; + resolveIDs(); +} + +void Flame::UrlResolvingTask::resolveIDs() +{ + setStatus(tr("Resolving mod IDs...")); + m_dljob.reset(new NetJob("Mod id resolver")); + auto projectIdStr = QString::number(m_result.projectId); + auto fileIdStr = QString::number(m_result.fileId); + QString metaurl = QString("%1/%2/%3.json").arg(metabase, projectIdStr, fileIdStr); + auto dl = Net::Download::makeByteArray(QUrl(metaurl), &results); + m_dljob->addNetAction(dl); + connect(m_dljob.get(), &NetJob::finished, this, &Flame::UrlResolvingTask::processCursemeta); + m_dljob->start(); +} + +void Flame::UrlResolvingTask::processCursemeta() +{ + try { + if(m_result.parseFromBytes(results)) { + emitSucceeded(); + qDebug() << results; + return; + } + } catch (const JSONValidationError &e) { + + qCritical() << "Resolving of" << m_result.projectId << m_result.fileId << "failed because of a parsing error:"; + qCritical() << e.cause(); + qCritical() << "JSON:"; + qCritical() << results; + } + emitFailed(tr("Failed to resolve the modpack file.")); +} diff --git a/api/logic/modplatform/flame/UrlResolvingTask.h b/api/logic/modplatform/flame/UrlResolvingTask.h new file mode 100644 index 00000000..98b78f67 --- /dev/null +++ b/api/logic/modplatform/flame/UrlResolvingTask.h @@ -0,0 +1,43 @@ +#pragma once + +#include "tasks/Task.h" +#include "net/NetJob.h" +#include "PackManifest.h" + +#include "multimc_logic_export.h" + +namespace Flame +{ +class MULTIMC_LOGIC_EXPORT UrlResolvingTask : public Task +{ + Q_OBJECT +public: + explicit UrlResolvingTask(const QString &toProcess); + virtual ~UrlResolvingTask() {}; + + const Flame::File &getResults() const + { + return m_result; + } + +protected: + virtual void executeTask() override; + +protected slots: + void processCCIP(); + void processHTML(); + void processCursemeta(); + +private: + void resolveUrl(); + void resolveIDs(); + +private: /* data */ + QString m_url; + QString needle; + Flame::File m_result; + QByteArray results; + NetJobPtr m_dljob; +}; +} + diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp b/api/logic/modplatform/ftb/FtbPackFetchTask.cpp index 5588a7fd..fe3f3fac 100644 --- a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp +++ b/api/logic/modplatform/ftb/FtbPackFetchTask.cpp @@ -1,117 +1,168 @@ #include "FtbPackFetchTask.h" #include +#include "FtbPrivatePackManager.h" -FtbPackFetchTask::FtbPackFetchTask() -{ -} - -FtbPackFetchTask::~FtbPackFetchTask() -{ -} +#include "net/URLConstants.h" void FtbPackFetchTask::fetch() { - NetJob *netJob = new NetJob("FtbModpackFetch"); + publicPacks.clear(); + thirdPartyPacks.clear(); + + NetJob *netJob = new NetJob("FtbModpackFetch"); - QUrl publicPacksUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/modpacks.xml"); - qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); + QUrl publicPacksUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/modpacks.xml"); + qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); + netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData)); - QUrl thirdPartyUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/thirdparty.xml"); - qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); - netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); + QUrl thirdPartyUrl = QUrl(URLConstants::FTB_CDN_BASE_URL + "static/thirdparty.xml"); + qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); + netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData)); - QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished); - QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed); + QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished); + QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed); - jobPtr.reset(netJob); - netJob->start(); + jobPtr.reset(netJob); + netJob->start(); } -void FtbPackFetchTask::fileDownloadFinished() +void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch) { - jobPtr.reset(); - - QStringList failedLists; - - if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks)) { - failedLists.append(tr("Public Packs")); - } - - if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks)) { - failedLists.append(tr("Third Party Packs")); - } + QString privatePackBaseUrl = URLConstants::FTB_CDN_BASE_URL + "static/%1.xml"; + + for (auto &packCode: toFetch) + { + QByteArray *data = new QByteArray(); + NetJob *job = new NetJob("Fetching private pack"); + job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); + + QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] + { + FtbModpackList packs; + parseAndAddPacks(*data, FtbPackType::Private, packs); + foreach(FtbModpack currentPack, packs) + { + currentPack.packCode = packCode; + emit privateFileDownloadFinished(currentPack); + } + + job->deleteLater(); + + data->clear(); + delete data; + }); + + QObject::connect(job, &NetJob::failed, this, [this, job, packCode, data](QString reason) + { + emit privateFileDownloadFailed(reason, packCode); + job->deleteLater(); + + data->clear(); + delete data; + }); + + job->start(); + } +} - if(failedLists.size() > 0) { - emit failed(QString("Failed to download some pack lists:%1").arg(failedLists.join("\n- "))); - } else { - emit finished(publicPacks, thirdPartyPacks); - } +void FtbPackFetchTask::fileDownloadFinished() +{ + jobPtr.reset(); + + QStringList failedLists; + + if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks)) + { + failedLists.append(tr("Public Packs")); + } + + if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks)) + { + failedLists.append(tr("Third Party Packs")); + } + + if(failedLists.size() > 0) + { + emit failed(tr("Failed to download some pack lists: %1").arg(failedLists.join("\n- "))); + } + else + { + emit finished(publicPacks, thirdPartyPacks); + } } bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list) { - QDomDocument doc; - - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)){ - auto fullErrMsg = QString("Failed to fetch modpack data: %s %d:%d!").arg(errorMsg, errorLine, errorCol); - qWarning() << fullErrMsg; - data.clear(); - return false; - } - - QDomNodeList nodes = doc.elementsByTagName("modpack"); - for(int i = 0; i < nodes.length(); i++) { - QDomElement element = nodes.at(i).toElement(); - - FtbModpack modpack; - modpack.name = element.attribute("name"); - modpack.currentVersion = element.attribute("version"); - modpack.mcVersion = element.attribute("mcVersion"); - modpack.description = element.attribute("description"); - modpack.mods = element.attribute("mods"); - modpack.logo = element.attribute("logo"); - modpack.oldVersions = element.attribute("oldVersions").split(";"); - modpack.broken = false; - modpack.bugged = false; - - //remove empty if the xml is bugged - for(QString curr : modpack.oldVersions) { - if(curr.isNull() || curr.isEmpty()) { - modpack.oldVersions.removeAll(curr); - modpack.bugged = true; - qWarning() << "Removed some empty versions from" << modpack.name; - } - } - - if(modpack.oldVersions.size() < 1) { - if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) { - modpack.oldVersions.append(modpack.currentVersion); - qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; - } else { - modpack.broken = true; - qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; - } - } - - modpack.author = element.attribute("author"); - - modpack.dir = element.attribute("dir"); - modpack.file = element.attribute("url"); - - modpack.type = packType; - - list.append(modpack); - } - - return true; + QDomDocument doc; + + QString errorMsg = "Unknown error."; + int errorLine = -1; + int errorCol = -1; + + if(!doc.setContent(data, false, &errorMsg, &errorLine, &errorCol)) + { + auto fullErrMsg = QString("Failed to fetch modpack data: %1 %2:3d!").arg(errorMsg, errorLine, errorCol); + qWarning() << fullErrMsg; + data.clear(); + return false; + } + + QDomNodeList nodes = doc.elementsByTagName("modpack"); + for(int i = 0; i < nodes.length(); i++) + { + QDomElement element = nodes.at(i).toElement(); + + FtbModpack modpack; + modpack.name = element.attribute("name"); + modpack.currentVersion = element.attribute("version"); + modpack.mcVersion = element.attribute("mcVersion"); + modpack.description = element.attribute("description"); + modpack.mods = element.attribute("mods"); + modpack.logo = element.attribute("logo"); + modpack.oldVersions = element.attribute("oldVersions").split(";"); + modpack.broken = false; + modpack.bugged = false; + + //remove empty if the xml is bugged + for(QString curr : modpack.oldVersions) + { + if(curr.isNull() || curr.isEmpty()) + { + modpack.oldVersions.removeAll(curr); + modpack.bugged = true; + qWarning() << "Removed some empty versions from" << modpack.name; + } + } + + if(modpack.oldVersions.size() < 1) + { + if(!modpack.currentVersion.isNull() && !modpack.currentVersion.isEmpty()) + { + modpack.oldVersions.append(modpack.currentVersion); + qWarning() << "Added current version to oldVersions because oldVersions was empty! (" + modpack.name + ")"; + } + else + { + modpack.broken = true; + qWarning() << "Broken pack:" << modpack.name << " => No valid version!"; + } + } + + modpack.author = element.attribute("author"); + + modpack.dir = element.attribute("dir"); + modpack.file = element.attribute("url"); + + modpack.type = packType; + + list.append(modpack); + } + + return true; } -void FtbPackFetchTask::fileDownloadFailed(QString reason){ - qWarning() << "Fetching FtbPacks failed: " << reason; - emit failed(reason); +void FtbPackFetchTask::fileDownloadFailed(QString reason) +{ + qWarning() << "Fetching FtbPacks failed:" << reason; + emit failed(reason); } diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.h b/api/logic/modplatform/ftb/FtbPackFetchTask.h index b5635b12..f955fe83 100644 --- a/api/logic/modplatform/ftb/FtbPackFetchTask.h +++ b/api/logic/modplatform/ftb/FtbPackFetchTask.h @@ -8,30 +8,33 @@ class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject { - Q_OBJECT + Q_OBJECT public: - FtbPackFetchTask(); - virtual ~FtbPackFetchTask(); + FtbPackFetchTask() = default; + virtual ~FtbPackFetchTask() = default; - void fetch(); + void fetch(); + void fetchPrivate(const QStringList &toFetch); private: - NetJobPtr jobPtr; + NetJobPtr jobPtr; - QByteArray publicModpacksXmlFileData; - QByteArray thirdPartyModpacksXmlFileData; + QByteArray publicModpacksXmlFileData; + QByteArray thirdPartyModpacksXmlFileData; - bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list); - FtbModpackList publicPacks; - FtbModpackList thirdPartyPacks; + bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list); + FtbModpackList publicPacks; + FtbModpackList thirdPartyPacks; protected slots: - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); + void fileDownloadFinished(); + void fileDownloadFailed(QString reason); signals: - void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); - void failed(QString reason); + void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks); + void failed(QString reason); + void privateFileDownloadFinished(FtbModpack modpack); + void privateFileDownloadFailed(QString reason, QString packCode); }; diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp b/api/logic/modplatform/ftb/FtbPackInstallTask.cpp index b57c2092..4962bcac 100644 --- a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp +++ b/api/logic/modplatform/ftb/FtbPackInstallTask.cpp @@ -9,190 +9,199 @@ #include "minecraft/ComponentList.h" #include "minecraft/GradleSpecifier.h" +#include "net/URLConstants.h" + FtbPackInstallTask::FtbPackInstallTask(FtbModpack pack, QString version) { - m_pack = pack; - m_version = version; + m_pack = pack; + m_version = version; } void FtbPackInstallTask::executeTask() { - downloadPack(); + downloadPack(); } void FtbPackInstallTask::downloadPack() { - setStatus(tr("Downloading zip for %1").arg(m_pack.name)); - - auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); - auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset); - NetJob *job = new NetJob("Download FTB Pack"); - - entry->setStale(true); - QString url = QString("http://ftb.cursecdn.com/FTB2/modpacks/%1").arg(packoffset); - job->addNetAction(Net::Download::makeCached(url, entry)); - archivePath = entry->getFullPath(); - - netJobContainer.reset(job); - connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded); - connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed); - connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress); - job->start(); - - progress(1, 4); + setStatus(tr("Downloading zip for %1").arg(m_pack.name)); + + auto packoffset = QString("%1/%2/%3").arg(m_pack.dir, m_version.replace(".", "_"), m_pack.file); + auto entry = ENV.metacache()->resolveEntry("FTBPacks", packoffset); + NetJob *job = new NetJob("Download FTB Pack"); + + entry->setStale(true); + QString url; + if(m_pack.type == FtbPackType::Private) + { + url = QString(URLConstants::FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset); + } + else + { + url = QString(URLConstants::FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset); + } + job->addNetAction(Net::Download::makeCached(url, entry)); + archivePath = entry->getFullPath(); + + netJobContainer.reset(job); + connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded); + connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed); + connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress); + job->start(); + + progress(1, 4); } void FtbPackInstallTask::onDownloadSucceeded() { - abortable = false; - unzip(); + abortable = false; + unzip(); } void FtbPackInstallTask::onDownloadFailed(QString reason) { - emitFailed(reason); + abortable = false; + emitFailed(reason); } void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total) { - abortable = true; - progress(current, total * 4); - setStatus(tr("Downloading zip for %1 (%2\%)").arg(m_pack.name).arg(current / 10)); + abortable = true; + progress(current, total * 4); + setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); } void FtbPackInstallTask::unzip() { - progress(2, 4); - setStatus(tr("Extracting modpack")); - QDir extractDir(m_stagingPath); - - m_packZip.reset(new QuaZip(archivePath)); - if(!m_packZip->open(QuaZip::mdUnzip)) - { - emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); - return; - } - - m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); - connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &FtbPackInstallTask::onUnzipFinished); - connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &FtbPackInstallTask::onUnzipCanceled); - m_extractFutureWatcher.setFuture(m_extractFuture); + progress(2, 4); + setStatus(tr("Extracting modpack")); + QDir extractDir(m_stagingPath); + + m_packZip.reset(new QuaZip(archivePath)); + if(!m_packZip->open(QuaZip::mdUnzip)) + { + emitFailed(tr("Failed to open modpack file %1!").arg(archivePath)); + return; + } + + m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip"); + connect(&m_extractFutureWatcher, &QFutureWatcher::finished, this, &FtbPackInstallTask::onUnzipFinished); + connect(&m_extractFutureWatcher, &QFutureWatcher::canceled, this, &FtbPackInstallTask::onUnzipCanceled); + m_extractFutureWatcher.setFuture(m_extractFuture); } void FtbPackInstallTask::onUnzipFinished() { - install(); + install(); } void FtbPackInstallTask::onUnzipCanceled() { - emitAborted(); + emitAborted(); } void FtbPackInstallTask::install() { - progress(3, 4); - setStatus(tr("Installing modpack")); - QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); - if(unzipMcDir.exists()) - { - //ok, found minecraft dir, move contents to instance dir - if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) - { - emitFailed(tr("Failed to move unzipped minecraft!")); - return; - } - } - - QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(instanceConfigPath); - instanceSettings->registerSetting("InstanceType", "Legacy"); - instanceSettings->set("InstanceType", "OneSix"); - - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getComponentList(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); - - bool fallback = true; - - //handle different versions - QFile packJson(m_stagingPath + "/.minecraft/pack.json"); - QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); - if(packJson.exists()) - { - packJson.open(QIODevice::ReadOnly | QIODevice::Text); - QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); - packJson.close(); - - //we only care about the libs - QJsonArray libs = doc.object().value("libraries").toArray(); - - foreach (const QJsonValue &value, libs) - { - QString nameValue = value.toObject().value("name").toString(); - if(!nameValue.startsWith("net.minecraftforge")) - { - continue; - } - - GradleSpecifier forgeVersion(nameValue); - - components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); - packJson.remove(); - fallback = false; - break; - } - - } - - if(jarmodDir.exists()) - { - qDebug() << "Found jarmods, installing..."; - - QStringList jarmods; - for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << "Jarmod:" << info.fileName(); - jarmods.push_back(info.absoluteFilePath()); - } - - components->installJarMods(jarmods); - fallback = false; - } - - //just nuke unzip directory, it s not needed anymore - FS::deletePath(m_stagingPath + "/unzip"); - - if(fallback) - { - //TODO: Some fallback mechanism... or just keep failing! - emitFailed(tr("No installation method found!")); - return; - } - - components->saveNow(); - - progress(4, 4); - - instance.init(); - instance.setName(m_instName); - if(m_instIcon == "default") - { - m_instIcon = "ftb_logo"; - } - instance.setIconKey(m_instIcon); - instance.setGroupInitial(m_instGroup); - instanceSettings->resumeSave(); - - emitSucceeded(); + progress(3, 4); + setStatus(tr("Installing modpack")); + QDir unzipMcDir(m_stagingPath + "/unzip/minecraft"); + if(unzipMcDir.exists()) + { + //ok, found minecraft dir, move contents to instance dir + if(!QDir().rename(m_stagingPath + "/unzip/minecraft", m_stagingPath + "/.minecraft")) + { + emitFailed(tr("Failed to move unzipped minecraft!")); + return; + } + } + + QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(instanceConfigPath); + instanceSettings->registerSetting("InstanceType", "Legacy"); + instanceSettings->set("InstanceType", "OneSix"); + + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getComponentList(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); + + bool fallback = true; + + //handle different versions + QFile packJson(m_stagingPath + "/.minecraft/pack.json"); + QDir jarmodDir = QDir(m_stagingPath + "/unzip/instMods"); + if(packJson.exists()) + { + packJson.open(QIODevice::ReadOnly | QIODevice::Text); + QJsonDocument doc = QJsonDocument::fromJson(packJson.readAll()); + packJson.close(); + + //we only care about the libs + QJsonArray libs = doc.object().value("libraries").toArray(); + + foreach (const QJsonValue &value, libs) + { + QString nameValue = value.toObject().value("name").toString(); + if(!nameValue.startsWith("net.minecraftforge")) + { + continue; + } + + GradleSpecifier forgeVersion(nameValue); + + components->setComponentVersion("net.minecraftforge", forgeVersion.version().replace(m_pack.mcVersion, "").replace("-", "")); + packJson.remove(); + fallback = false; + break; + } + + } + + if(jarmodDir.exists()) + { + qDebug() << "Found jarmods, installing..."; + + QStringList jarmods; + for (auto info: jarmodDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) + { + qDebug() << "Jarmod:" << info.fileName(); + jarmods.push_back(info.absoluteFilePath()); + } + + components->installJarMods(jarmods); + fallback = false; + } + + //just nuke unzip directory, it s not needed anymore + FS::deletePath(m_stagingPath + "/unzip"); + + if(fallback) + { + //TODO: Some fallback mechanism... or just keep failing! + emitFailed(tr("No installation method found!")); + return; + } + + components->saveNow(); + + progress(4, 4); + + instance.setName(m_instName); + if(m_instIcon == "default") + { + m_instIcon = "ftb_logo"; + } + instance.setIconKey(m_instIcon); + instanceSettings->resumeSave(); + + emitSucceeded(); } bool FtbPackInstallTask::abort() { - if(abortable) - { - return netJobContainer->abort(); - } - return false; + if(abortable) + { + return netJobContainer->abort(); + } + return false; } diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.h b/api/logic/modplatform/ftb/FtbPackInstallTask.h index 0bfba3d6..3319025e 100644 --- a/api/logic/modplatform/ftb/FtbPackInstallTask.h +++ b/api/logic/modplatform/ftb/FtbPackInstallTask.h @@ -1,6 +1,5 @@ #pragma once #include "InstanceTask.h" -#include "BaseInstanceProvider.h" #include "net/NetJob.h" #include "quazip.h" #include "quazipdir.h" @@ -9,41 +8,41 @@ #include "meta/VersionList.h" #include "modplatform/ftb/PackHelpers.h" -class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask { - - Q_OBJECT +class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask +{ + Q_OBJECT public: - explicit FtbPackInstallTask(FtbModpack pack, QString version); - virtual ~FtbPackInstallTask(){} + explicit FtbPackInstallTask(FtbModpack pack, QString version); + virtual ~FtbPackInstallTask(){} - bool abort() override; + bool abort() override; protected: - //! Entry point for tasks. - virtual void executeTask() override; + //! Entry point for tasks. + virtual void executeTask() override; private: - void downloadPack(); - void unzip(); - void install(); + void downloadPack(); + void unzip(); + void install(); private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); - void onDownloadProgress(qint64 current, qint64 total); + void onDownloadSucceeded(); + void onDownloadFailed(QString reason); + void onDownloadProgress(qint64 current, qint64 total); - void onUnzipFinished(); - void onUnzipCanceled(); + void onUnzipFinished(); + void onUnzipCanceled(); private: /* data */ - bool abortable = false; - std::unique_ptr m_packZip; - QFuture m_extractFuture; - QFutureWatcher m_extractFutureWatcher; - NetJobPtr netJobContainer; - QString archivePath; - - FtbModpack m_pack; - QString m_version; + bool abortable = false; + std::unique_ptr m_packZip; + QFuture m_extractFuture; + QFutureWatcher m_extractFutureWatcher; + NetJobPtr netJobContainer; + QString archivePath; + + FtbModpack m_pack; + QString m_version; }; diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp b/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp new file mode 100644 index 00000000..c3477cec --- /dev/null +++ b/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp @@ -0,0 +1,37 @@ +#include "FtbPrivatePackManager.h" + +#include + +#include "FileSystem.h" + +void FtbPrivatePackManager::load() +{ + try + { + currentPacks = QString::fromUtf8(FS::read(m_filename)).split('\n', QString::SkipEmptyParts).toSet(); + dirty = false; + } + catch(...) + { + currentPacks = {}; + qWarning() << "Failed to read third party FTB pack codes from" << m_filename; + } +} + +void FtbPrivatePackManager::save() const +{ + if(!dirty) + { + return; + } + try + { + QStringList list = currentPacks.toList(); + FS::write(m_filename, list.join('\n').toUtf8()); + dirty = false; + } + catch(...) + { + qWarning() << "Failed to write third party FTB pack codes to" << m_filename; + } +} diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.h b/api/logic/modplatform/ftb/FtbPrivatePackManager.h new file mode 100644 index 00000000..388224d6 --- /dev/null +++ b/api/logic/modplatform/ftb/FtbPrivatePackManager.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include "multimc_logic_export.h" + +class MULTIMC_LOGIC_EXPORT FtbPrivatePackManager +{ +public: + ~FtbPrivatePackManager() + { + save(); + } + void load(); + void save() const; + bool empty() const + { + return currentPacks.empty(); + } + const QSet &getCurrentPackCodes() const + { + return currentPacks; + } + void add(const QString &code) + { + currentPacks.insert(code); + dirty = true; + } + void remove(const QString &code) + { + currentPacks.remove(code); + dirty = true; + } + +private: + QSet currentPacks; + QString m_filename = "private_packs.txt"; + mutable bool dirty = false; +}; diff --git a/api/logic/modplatform/ftb/PackHelpers.h b/api/logic/modplatform/ftb/PackHelpers.h index 39b65927..4306caee 100644 --- a/api/logic/modplatform/ftb/PackHelpers.h +++ b/api/logic/modplatform/ftb/PackHelpers.h @@ -6,30 +6,33 @@ #include //Header for structs etc... -enum FtbPackType { - Public, - ThirdParty, - Private +enum class FtbPackType +{ + Public, + ThirdParty, + Private }; -struct FtbModpack { - QString name; - QString description; - QString author; - QStringList oldVersions; - QString currentVersion; - QString mcVersion; - QString mods; - QString logo; - - //Technical data - QString dir; - QString file; //<- Url in the xml, but doesn't make much sense - - bool bugged = false; - bool broken = false; - - FtbPackType type; +struct FtbModpack +{ + QString name; + QString description; + QString author; + QStringList oldVersions; + QString currentVersion; + QString mcVersion; + QString mods; + QString logo; + + //Technical data + QString dir; + QString file; //<- Url in the xml, but doesn't make much sense + + bool bugged = false; + bool broken = false; + + FtbPackType type; + QString packCode; }; //We need it for the proxy model diff --git a/api/logic/net/ByteArraySink.h b/api/logic/net/ByteArraySink.h index 03b77fcc..20e6764c 100644 --- a/api/logic/net/ByteArraySink.h +++ b/api/logic/net/ByteArraySink.h @@ -9,54 +9,54 @@ namespace Net { class ByteArraySink : public Sink { public: - ByteArraySink(QByteArray *output) - :m_output(output) - { - // nil - }; + ByteArraySink(QByteArray *output) + :m_output(output) + { + // nil + }; - virtual ~ByteArraySink() - { - // nil - } + virtual ~ByteArraySink() + { + // nil + } public: - JobStatus init(QNetworkRequest & request) override - { - m_output->clear(); - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; - }; - - JobStatus write(QByteArray & data) override - { - m_output->append(data); - if(writeAllValidators(data)) - return Job_InProgress; - return Job_Failed; - } - - JobStatus abort() override - { - m_output->clear(); - failAllValidators(); - return Job_Failed; - } - - JobStatus finalize(QNetworkReply &reply) override - { - if(finalizeAllValidators(reply)) - return Job_Finished; - return Job_Failed; - } - - bool hasLocalData() override - { - return false; - } + JobStatus init(QNetworkRequest & request) override + { + m_output->clear(); + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; + }; + + JobStatus write(QByteArray & data) override + { + m_output->append(data); + if(writeAllValidators(data)) + return Job_InProgress; + return Job_Failed; + } + + JobStatus abort() override + { + m_output->clear(); + failAllValidators(); + return Job_Failed; + } + + JobStatus finalize(QNetworkReply &reply) override + { + if(finalizeAllValidators(reply)) + return Job_Finished; + return Job_Failed; + } + + bool hasLocalData() override + { + return false; + } private: - QByteArray * m_output; + QByteArray * m_output; }; } diff --git a/api/logic/net/ChecksumValidator.h b/api/logic/net/ChecksumValidator.h index 3cd25bc3..0d6b19c2 100644 --- a/api/logic/net/ChecksumValidator.h +++ b/api/logic/net/ChecksumValidator.h @@ -9,49 +9,47 @@ namespace Net { class ChecksumValidator: public Validator { public: /* con/des */ - ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) - :m_checksum(algorithm), m_expected(expected) - { - }; - virtual ~ChecksumValidator() {}; + ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray()) + :m_checksum(algorithm), m_expected(expected) + { + }; + virtual ~ChecksumValidator() {}; public: /* methods */ - bool init(QNetworkRequest &) override - { - m_checksum.reset(); - return true; - } - bool write(QByteArray & data) override - { - m_checksum.addData(data); - this->data.append(data); - return true; - } - bool abort() override - { - return true; - } - bool validate(QNetworkReply &) override - { - if(m_expected.size() && m_expected != hash()) - { - qWarning() << "Checksum mismatch, download is bad."; - return false; - } - return true; - } - QByteArray hash() - { - return m_checksum.result(); - } - void setExpected(QByteArray expected) - { - m_expected = expected; - } + bool init(QNetworkRequest &) override + { + m_checksum.reset(); + return true; + } + bool write(QByteArray & data) override + { + m_checksum.addData(data); + return true; + } + bool abort() override + { + return true; + } + bool validate(QNetworkReply &) override + { + if(m_expected.size() && m_expected != hash()) + { + qWarning() << "Checksum mismatch, download is bad."; + return false; + } + return true; + } + QByteArray hash() + { + return m_checksum.result(); + } + void setExpected(QByteArray expected) + { + m_expected = expected; + } private: /* data */ - QByteArray data; - QCryptographicHash m_checksum; - QByteArray m_expected; + QCryptographicHash m_checksum; + QByteArray m_expected; }; } \ No newline at end of file diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp index 631d3076..229782b0 100644 --- a/api/logic/net/Download.cpp +++ b/api/logic/net/Download.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,271 +28,281 @@ namespace Net { Download::Download():NetAction() { - m_status = Job_NotStarted; + m_status = Job_NotStarted; } Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); - auto cachedNode = new MetaCacheSink(entry, md5Node); - dl->m_sink.reset(cachedNode); - dl->m_target_path = entry->getFullPath(); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); + auto cachedNode = new MetaCacheSink(entry, md5Node); + dl->m_sink.reset(cachedNode); + dl->m_target_path = entry->getFullPath(); + return std::shared_ptr(dl); } Download::Ptr Download::makeByteArray(QUrl url, QByteArray *output, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new ByteArraySink(output)); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new ByteArraySink(output)); + return std::shared_ptr(dl); } Download::Ptr Download::makeFile(QUrl url, QString path, Options options) { - Download * dl = new Download(); - dl->m_url = url; - dl->m_options = options; - dl->m_sink.reset(new FileSink(path)); - return std::shared_ptr(dl); + Download * dl = new Download(); + dl->m_url = url; + dl->m_options = options; + dl->m_sink.reset(new FileSink(path)); + return std::shared_ptr(dl); } void Download::addValidator(Validator * v) { - m_sink->addValidator(v); + m_sink->addValidator(v); } void Download::start() { - if(m_status == Job_Aborted) - { - qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); - emit aborted(m_index_within_job); - return; - } - QNetworkRequest request(m_url); - m_status = m_sink->init(request); - switch(m_status) - { - case Job_Finished: - emit succeeded(m_index_within_job); - qDebug() << "Download cache hit " << m_url.toString(); - return; - case Job_InProgress: - qDebug() << "Downloading " << m_url.toString(); - break; - case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. - case Job_NotStarted: - case Job_Failed: - emit failed(m_index_within_job); - return; - case Job_Aborted: - return; - } + if(m_status == Job_Aborted) + { + qWarning() << "Attempt to start an aborted Download:" << m_url.toString(); + emit aborted(m_index_within_job); + return; + } + QNetworkRequest request(m_url); + m_status = m_sink->init(request); + switch(m_status) + { + case Job_Finished: + emit succeeded(m_index_within_job); + qDebug() << "Download cache hit " << m_url.toString(); + return; + case Job_InProgress: + qDebug() << "Downloading " << m_url.toString(); + break; + case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink. + case Job_NotStarted: + case Job_Failed: + emit failed(m_index_within_job); + return; + case Job_Aborted: + return; + } - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0"); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0"); - QNetworkReply *rep = ENV.qnam().get(request); + QNetworkReply *rep = ENV.qnam().get(request); - m_reply.reset(rep); - connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); - connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); - connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); + m_reply.reset(rep); + connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64))); + connect(rep, SIGNAL(finished()), SLOT(downloadFinished())); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors); + connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead); } void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } void Download::downloadError(QNetworkReply::NetworkError error) { - if(error == QNetworkReply::OperationCanceledError) - { - qCritical() << "Aborted " << m_url.toString(); - m_status = Job_Aborted; - } - else - { - if(m_options & Option::AcceptLocalFiles) - { - if(m_sink->hasLocalData()) - { - m_status = Job_Failed_Proceed; - return; - } - } - // error happened during download. - qCritical() << "Failed " << m_url.toString() << " with reason " << error; - m_status = Job_Failed; - } + if(error == QNetworkReply::OperationCanceledError) + { + qCritical() << "Aborted " << m_url.toString(); + m_status = Job_Aborted; + } + else + { + if(m_options & Option::AcceptLocalFiles) + { + if(m_sink->hasLocalData()) + { + m_status = Job_Failed_Proceed; + return; + } + } + // error happened during download. + qCritical() << "Failed " << m_url.toString() << " with reason " << error; + m_status = Job_Failed; + } } void Download::sslErrors(const QList & errors) { - int i = 1; - for (auto error : errors) - { - qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); - auto cert = error.certificate(); - qCritical() << "Certificate in question:\n" << cert.toText(); - i++; - } + int i = 1; + for (auto error : errors) + { + qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString(); + auto cert = error.certificate(); + qCritical() << "Certificate in question:\n" << cert.toText(); + i++; + } } bool Download::handleRedirect() { - QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); - if(!redirect.isValid()) - { - if(!m_reply->hasRawHeader("Location")) - { - // no redirect -> it's fine to continue - return false; - } - // there is a Location header, but it's not correct. we need to apply some workarounds... - QByteArray redirectBA = m_reply->rawHeader("Location"); - if(redirectBA.size() == 0) - { - // empty, yet present redirect header? WTF? - return false; - } - QString redirectStr = QString::fromUtf8(redirectBA); + QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl(); + if(!redirect.isValid()) + { + if(!m_reply->hasRawHeader("Location")) + { + // no redirect -> it's fine to continue + return false; + } + // there is a Location header, but it's not correct. we need to apply some workarounds... + QByteArray redirectBA = m_reply->rawHeader("Location"); + if(redirectBA.size() == 0) + { + // empty, yet present redirect header? WTF? + return false; + } + QString redirectStr = QString::fromUtf8(redirectBA); - /* - * IF the URL begins with //, we need to insert the URL scheme. - * See: https://bugreports.qt-project.org/browse/QTBUG-41061 - */ - if(redirectStr.startsWith("//")) - { - redirectStr = m_reply->url().scheme() + ":" + redirectStr; - } + if(redirectStr.startsWith("//")) + { + /* + * IF the URL begins with //, we need to insert the URL scheme. + * See: https://bugreports.qt.io/browse/QTBUG-41061 + * See: http://tools.ietf.org/html/rfc3986#section-4.2 + */ + redirectStr = m_reply->url().scheme() + ":" + redirectStr; + } + else if(redirectStr.startsWith("/")) + { + /* + * IF the URL begins with /, we need to process it as a relative URL + */ + auto url = m_reply->url(); + url.setPath(redirectStr, QUrl::TolerantMode); + redirectStr = url.toString(); + } - /* - * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. - * FIXME: report Qt bug for this - */ - redirect = QUrl(redirectStr, QUrl::TolerantMode); - if(!redirect.isValid()) - { - qWarning() << "Failed to parse redirect URL:" << redirectStr; - downloadError(QNetworkReply::ProtocolFailure); - return false; - } - qDebug() << "Fixed location header:" << redirect; - } - else - { - qDebug() << "Location header:" << redirect; - } + /* + * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues. + * FIXME: report Qt bug for this + */ + redirect = QUrl(redirectStr, QUrl::TolerantMode); + if(!redirect.isValid()) + { + qWarning() << "Failed to parse redirect URL:" << redirectStr; + downloadError(QNetworkReply::ProtocolFailure); + return false; + } + qDebug() << "Fixed location header:" << redirect; + } + else + { + qDebug() << "Location header:" << redirect; + } - m_url = QUrl(redirect.toString()); - qDebug() << "Following redirect to " << m_url.toString(); - start(); - return true; + m_url = QUrl(redirect.toString()); + qDebug() << "Following redirect to " << m_url.toString(); + start(); + return true; } void Download::downloadFinished() { - // handle HTTP redirection first - if(handleRedirect()) - { - qDebug() << "Download redirected:" << m_url.toString(); - return; - } + // handle HTTP redirection first + if(handleRedirect()) + { + qDebug() << "Download redirected:" << m_url.toString(); + return; + } - // if the download failed before this point ... - if (m_status == Job_Failed_Proceed) - { - qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit succeeded(m_index_within_job); - return; - } - else if (m_status == Job_Failed) - { - qDebug() << "Download failed in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - else if(m_status == Job_Aborted) - { - qDebug() << "Download aborted in previous step:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit aborted(m_index_within_job); - return; - } + // if the download failed before this point ... + if (m_status == Job_Failed_Proceed) + { + qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit succeeded(m_index_within_job); + return; + } + else if (m_status == Job_Failed) + { + qDebug() << "Download failed in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + else if(m_status == Job_Aborted) + { + qDebug() << "Download aborted in previous step:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit aborted(m_index_within_job); + return; + } - // make sure we got all the remaining data, if any - auto data = m_reply->readAll(); - if(data.size()) - { - qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; - m_status = m_sink->write(data); - } + // make sure we got all the remaining data, if any + auto data = m_reply->readAll(); + if(data.size()) + { + qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path; + m_status = m_sink->write(data); + } - // otherwise, finalize the whole graph - m_status = m_sink->finalize(*m_reply.get()); - if (m_status != Job_Finished) - { - qDebug() << "Download failed to finalize:" << m_url.toString(); - m_sink->abort(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_reply.reset(); - qDebug() << "Download succeeded:" << m_url.toString(); - emit succeeded(m_index_within_job); + // otherwise, finalize the whole graph + m_status = m_sink->finalize(*m_reply.get()); + if (m_status != Job_Finished) + { + qDebug() << "Download failed to finalize:" << m_url.toString(); + m_sink->abort(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + m_reply.reset(); + qDebug() << "Download succeeded:" << m_url.toString(); + emit succeeded(m_index_within_job); } void Download::downloadReadyRead() { - if(m_status == Job_InProgress) - { - auto data = m_reply->readAll(); - m_status = m_sink->write(data); - if(m_status == Job_Failed) - { - qCritical() << "Failed to process response chunk for " << m_target_path; - } - // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; - } - else - { - qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; - } + if(m_status == Job_InProgress) + { + auto data = m_reply->readAll(); + m_status = m_sink->write(data); + if(m_status == Job_Failed) + { + qCritical() << "Failed to process response chunk for " << m_target_path; + } + // qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes"; + } + else + { + qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status; + } } } bool Net::Download::abort() { - if(m_reply) - { - m_reply->abort(); - } - else - { - m_status = Job_Aborted; - } - return true; + if(m_reply) + { + m_reply->abort(); + } + else + { + m_status = Job_Aborted; + } + return true; } bool Net::Download::canAbort() { - return true; + return true; } diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h index 00bf108c..18472911 100644 --- a/api/logic/net/Download.h +++ b/api/logic/net/Download.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,52 +24,52 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Download : public NetAction { - Q_OBJECT + Q_OBJECT public: /* types */ - typedef std::shared_ptr Ptr; - enum class Option - { - NoOptions = 0, - AcceptLocalFiles = 1 - }; - Q_DECLARE_FLAGS(Options, Option) + typedef std::shared_ptr Ptr; + enum class Option + { + NoOptions = 0, + AcceptLocalFiles = 1 + }; + Q_DECLARE_FLAGS(Options, Option) protected: /* con/des */ - explicit Download(); + explicit Download(); public: - virtual ~Download(){}; - static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); - static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); - static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); + virtual ~Download(){}; + static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions); + static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions); + static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions); public: /* methods */ - QString getTargetFilepath() - { - return m_target_path; - } - void addValidator(Validator * v); - bool abort() override; - bool canAbort() override; + QString getTargetFilepath() + { + return m_target_path; + } + void addValidator(Validator * v); + bool abort() override; + bool canAbort() override; private: /* methods */ - bool handleRedirect(); + bool handleRedirect(); protected slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; - void downloadError(QNetworkReply::NetworkError error) override; + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; + void downloadError(QNetworkReply::NetworkError error) override; void sslErrors(const QList & errors); - void downloadFinished() override; - void downloadReadyRead() override; + void downloadFinished() override; + void downloadReadyRead() override; public slots: - void start() override; + void start() override; private: /* data */ - // FIXME: remove this, it has no business being here. - QString m_target_path; - std::unique_ptr m_sink; - Options m_options; + // FIXME: remove this, it has no business being here. + QString m_target_path; + std::unique_ptr m_sink; + Options m_options; }; } diff --git a/api/logic/net/FileSink.cpp b/api/logic/net/FileSink.cpp index f52c5788..8b3e917d 100644 --- a/api/logic/net/FileSink.cpp +++ b/api/logic/net/FileSink.cpp @@ -7,109 +7,109 @@ namespace Net { FileSink::FileSink(QString filename) - :m_filename(filename) + :m_filename(filename) { - // nil -}; + // nil +} FileSink::~FileSink() { - // nil -}; + // nil +} JobStatus FileSink::init(QNetworkRequest& request) { - auto result = initCache(request); - if(result != Job_InProgress) - { - return result; - } - // create a new save file and open it for writing - if (!FS::ensureFilePathExists(m_filename)) - { - qCritical() << "Could not create folder for " + m_filename; - return Job_Failed; - } - wroteAnyData = false; - m_output_file.reset(new QSaveFile(m_filename)); - if (!m_output_file->open(QIODevice::WriteOnly)) - { - qCritical() << "Could not open " + m_filename + " for writing"; - return Job_Failed; - } + auto result = initCache(request); + if(result != Job_InProgress) + { + return result; + } + // create a new save file and open it for writing + if (!FS::ensureFilePathExists(m_filename)) + { + qCritical() << "Could not create folder for " + m_filename; + return Job_Failed; + } + wroteAnyData = false; + m_output_file.reset(new QSaveFile(m_filename)); + if (!m_output_file->open(QIODevice::WriteOnly)) + { + qCritical() << "Could not open " + m_filename + " for writing"; + return Job_Failed; + } - if(initAllValidators(request)) - return Job_InProgress; - return Job_Failed; + if(initAllValidators(request)) + return Job_InProgress; + return Job_Failed; } JobStatus FileSink::initCache(QNetworkRequest &) { - return Job_InProgress; + return Job_InProgress; } JobStatus FileSink::write(QByteArray& data) { - if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) - { - qCritical() << "Failed writing into " + m_filename; - m_output_file->cancelWriting(); - m_output_file.reset(); - wroteAnyData = false; - return Job_Failed; - } - wroteAnyData = true; - return Job_InProgress; + if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) + { + qCritical() << "Failed writing into " + m_filename; + m_output_file->cancelWriting(); + m_output_file.reset(); + wroteAnyData = false; + return Job_Failed; + } + wroteAnyData = true; + return Job_InProgress; } JobStatus FileSink::abort() { - m_output_file->cancelWriting(); - failAllValidators(); - return Job_Failed; + m_output_file->cancelWriting(); + failAllValidators(); + return Job_Failed; } JobStatus FileSink::finalize(QNetworkReply& reply) { - bool gotFile = false; - QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); - bool validStatus = false; - int statusCode = statusCodeV.toInt(&validStatus); - if(validStatus) - { - // this leaves out 304 Not Modified - gotFile = statusCode == 200 || statusCode == 203; - } - // if we wrote any data to the save file, we try to commit the data to the real file. - // if it actually got a proper file, we write it even if it was empty - if (gotFile || wroteAnyData) - { - // ask validators for data consistency - // we only do this for actual downloads, not 'your data is still the same' cache hits - if(!finalizeAllValidators(reply)) - return Job_Failed; - // nothing went wrong... - if (!m_output_file->commit()) - { - qCritical() << "Failed to commit changes to " << m_filename; - m_output_file->cancelWriting(); - return Job_Failed; - } - } - // then get rid of the save file - m_output_file.reset(); + bool gotFile = false; + QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute); + bool validStatus = false; + int statusCode = statusCodeV.toInt(&validStatus); + if(validStatus) + { + // this leaves out 304 Not Modified + gotFile = statusCode == 200 || statusCode == 203; + } + // if we wrote any data to the save file, we try to commit the data to the real file. + // if it actually got a proper file, we write it even if it was empty + if (gotFile || wroteAnyData) + { + // ask validators for data consistency + // we only do this for actual downloads, not 'your data is still the same' cache hits + if(!finalizeAllValidators(reply)) + return Job_Failed; + // nothing went wrong... + if (!m_output_file->commit()) + { + qCritical() << "Failed to commit changes to " << m_filename; + m_output_file->cancelWriting(); + return Job_Failed; + } + } + // then get rid of the save file + m_output_file.reset(); - return finalizeCache(reply); + return finalizeCache(reply); } JobStatus FileSink::finalizeCache(QNetworkReply &) { - return Job_Finished; + return Job_Finished; } bool FileSink::hasLocalData() { - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; + QFileInfo info(m_filename); + return info.exists() && info.size() != 0; } } diff --git a/api/logic/net/FileSink.h b/api/logic/net/FileSink.h index 035d5bfd..875fe511 100644 --- a/api/logic/net/FileSink.h +++ b/api/logic/net/FileSink.h @@ -6,23 +6,23 @@ namespace Net { class FileSink : public Sink { public: /* con/des */ - FileSink(QString filename); - virtual ~FileSink(); + FileSink(QString filename); + virtual ~FileSink(); public: /* methods */ - JobStatus init(QNetworkRequest & request) override; - JobStatus write(QByteArray & data) override; - JobStatus abort() override; - JobStatus finalize(QNetworkReply & reply) override; - bool hasLocalData() override; + JobStatus init(QNetworkRequest & request) override; + JobStatus write(QByteArray & data) override; + JobStatus abort() override; + JobStatus finalize(QNetworkReply & reply) override; + bool hasLocalData() override; protected: /* methods */ - virtual JobStatus initCache(QNetworkRequest &); - virtual JobStatus finalizeCache(QNetworkReply &reply); + virtual JobStatus initCache(QNetworkRequest &); + virtual JobStatus finalizeCache(QNetworkReply &reply); protected: /* data */ - QString m_filename; - bool wroteAnyData = false; - std::unique_ptr m_output_file; + QString m_filename; + bool wroteAnyData = false; + std::unique_ptr m_output_file; }; } diff --git a/api/logic/net/HttpMetaCache.cpp b/api/logic/net/HttpMetaCache.cpp index ebcb0a27..0e3d41b5 100644 --- a/api/logic/net/HttpMetaCache.cpp +++ b/api/logic/net/HttpMetaCache.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,244 +30,244 @@ QString MetaEntry::getFullPath() { - // FIXME: make local? - return FS::PathCombine(basePath, relativePath); + // FIXME: make local? + return FS::PathCombine(basePath, relativePath); } HttpMetaCache::HttpMetaCache(QString path) : QObject() { - m_index_file = path; - saveBatchingTimer.setSingleShot(true); - saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); - connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); + m_index_file = path; + saveBatchingTimer.setSingleShot(true); + saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer); + connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow())); } HttpMetaCache::~HttpMetaCache() { - saveBatchingTimer.stop(); - SaveNow(); + saveBatchingTimer.stop(); + SaveNow(); } MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path) { - // no base. no base path. can't store - if (!m_entries.contains(base)) - { - // TODO: log problem - return MetaEntryPtr(); - } - EntryMap &map = m_entries[base]; - if (map.entry_list.contains(resource_path)) - { - return map.entry_list[resource_path]; - } - return MetaEntryPtr(); + // no base. no base path. can't store + if (!m_entries.contains(base)) + { + // TODO: log problem + return MetaEntryPtr(); + } + EntryMap &map = m_entries[base]; + if (map.entry_list.contains(resource_path)) + { + return map.entry_list[resource_path]; + } + return MetaEntryPtr(); } MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) { - auto entry = getEntry(base, resource_path); - // it's not present? generate a default stale entry - if (!entry) - { - return staleEntry(base, resource_path); - } + auto entry = getEntry(base, resource_path); + // it's not present? generate a default stale entry + if (!entry) + { + return staleEntry(base, resource_path); + } - auto &selected_base = m_entries[base]; - QString real_path = FS::PathCombine(selected_base.base_path, resource_path); - QFileInfo finfo(real_path); + auto &selected_base = m_entries[base]; + QString real_path = FS::PathCombine(selected_base.base_path, resource_path); + QFileInfo finfo(real_path); - // is the file really there? if not -> stale - if (!finfo.isFile() || !finfo.isReadable()) - { - // if the file doesn't exist, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } + // is the file really there? if not -> stale + if (!finfo.isFile() || !finfo.isReadable()) + { + // if the file doesn't exist, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } - if (!expected_etag.isEmpty() && expected_etag != entry->etag) - { - // if the etag doesn't match expected, we disown the entry - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } + if (!expected_etag.isEmpty() && expected_etag != entry->etag) + { + // if the etag doesn't match expected, we disown the entry + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } - // if the file changed, check md5sum - qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); - if (file_last_changed != entry->local_changed_timestamp) - { - QFile input(real_path); - input.open(QIODevice::ReadOnly); - QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) - .toHex() - .constData(); - if (entry->md5sum != md5sum) - { - selected_base.entry_list.remove(resource_path); - return staleEntry(base, resource_path); - } - // md5sums matched... keep entry and save the new state to file - entry->local_changed_timestamp = file_last_changed; - SaveEventually(); - } + // if the file changed, check md5sum + qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch(); + if (file_last_changed != entry->local_changed_timestamp) + { + QFile input(real_path); + input.open(QIODevice::ReadOnly); + QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5) + .toHex() + .constData(); + if (entry->md5sum != md5sum) + { + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // md5sums matched... keep entry and save the new state to file + entry->local_changed_timestamp = file_last_changed; + SaveEventually(); + } - // entry passed all the checks we cared about. - entry->basePath = getBasePath(base); - return entry; + // entry passed all the checks we cared about. + entry->basePath = getBasePath(base); + return entry; } bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) { - if (!m_entries.contains(stale_entry->baseId)) - { - qCritical() << "Cannot add entry with unknown base: " - << stale_entry->baseId.toLocal8Bit(); - return false; - } - if (stale_entry->stale) - { - qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); - return false; - } - m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; - SaveEventually(); - return true; + if (!m_entries.contains(stale_entry->baseId)) + { + qCritical() << "Cannot add entry with unknown base: " + << stale_entry->baseId.toLocal8Bit(); + return false; + } + if (stale_entry->stale) + { + qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit(); + return false; + } + m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry; + SaveEventually(); + return true; } bool HttpMetaCache::evictEntry(MetaEntryPtr entry) { - if(entry) - { - entry->stale = true; - SaveEventually(); - return true; - } - return false; + if(entry) + { + entry->stale = true; + SaveEventually(); + return true; + } + return false; } MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path) { - auto foo = new MetaEntry(); - foo->baseId = base; - foo->basePath = getBasePath(base); - foo->relativePath = resource_path; - foo->stale = true; - return MetaEntryPtr(foo); + auto foo = new MetaEntry(); + foo->baseId = base; + foo->basePath = getBasePath(base); + foo->relativePath = resource_path; + foo->stale = true; + return MetaEntryPtr(foo); } void HttpMetaCache::addBase(QString base, QString base_root) { - // TODO: report error - if (m_entries.contains(base)) - return; - // TODO: check if the base path is valid - EntryMap foo; - foo.base_path = base_root; - m_entries[base] = foo; + // TODO: report error + if (m_entries.contains(base)) + return; + // TODO: check if the base path is valid + EntryMap foo; + foo.base_path = base_root; + m_entries[base] = foo; } QString HttpMetaCache::getBasePath(QString base) { - if (m_entries.contains(base)) - { - return m_entries[base].base_path; - } - return QString(); + if (m_entries.contains(base)) + { + return m_entries[base].base_path; + } + return QString(); } void HttpMetaCache::Load() { - if(m_index_file.isNull()) - return; + if(m_index_file.isNull()) + return; - QFile index(m_index_file); - if (!index.open(QIODevice::ReadOnly)) - return; + QFile index(m_index_file); + if (!index.open(QIODevice::ReadOnly)) + return; - QJsonDocument json = QJsonDocument::fromJson(index.readAll()); - if (!json.isObject()) - return; - auto root = json.object(); - // check file version first - auto version_val = root.value("version"); - if (!version_val.isString()) - return; - if (version_val.toString() != "1") - return; + QJsonDocument json = QJsonDocument::fromJson(index.readAll()); + if (!json.isObject()) + return; + auto root = json.object(); + // check file version first + auto version_val = root.value("version"); + if (!version_val.isString()) + return; + if (version_val.toString() != "1") + return; - // read the entry array - auto entries_val = root.value("entries"); - if (!entries_val.isArray()) - return; - QJsonArray array = entries_val.toArray(); - for (auto element : array) - { - if (!element.isObject()) - return; - auto element_obj = element.toObject(); - QString base = element_obj.value("base").toString(); - if (!m_entries.contains(base)) - continue; - auto &entrymap = m_entries[base]; - auto foo = new MetaEntry(); - foo->baseId = base; - QString path = foo->relativePath = element_obj.value("path").toString(); - foo->md5sum = element_obj.value("md5sum").toString(); - foo->etag = element_obj.value("etag").toString(); - foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); - foo->remote_changed_timestamp = - element_obj.value("remote_changed_timestamp").toString(); - // presumed innocent until closer examination - foo->stale = false; - entrymap.entry_list[path] = MetaEntryPtr(foo); - } + // read the entry array + auto entries_val = root.value("entries"); + if (!entries_val.isArray()) + return; + QJsonArray array = entries_val.toArray(); + for (auto element : array) + { + if (!element.isObject()) + return; + auto element_obj = element.toObject(); + QString base = element_obj.value("base").toString(); + if (!m_entries.contains(base)) + continue; + auto &entrymap = m_entries[base]; + auto foo = new MetaEntry(); + foo->baseId = base; + QString path = foo->relativePath = element_obj.value("path").toString(); + foo->md5sum = element_obj.value("md5sum").toString(); + foo->etag = element_obj.value("etag").toString(); + foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble(); + foo->remote_changed_timestamp = + element_obj.value("remote_changed_timestamp").toString(); + // presumed innocent until closer examination + foo->stale = false; + entrymap.entry_list[path] = MetaEntryPtr(foo); + } } void HttpMetaCache::SaveEventually() { - // reset the save timer - saveBatchingTimer.stop(); - saveBatchingTimer.start(30000); + // reset the save timer + saveBatchingTimer.stop(); + saveBatchingTimer.start(30000); } void HttpMetaCache::SaveNow() { - if(m_index_file.isNull()) - return; - QJsonObject toplevel; - toplevel.insert("version", QJsonValue(QString("1"))); - QJsonArray entriesArr; - for (auto group : m_entries) - { - for (auto entry : group.entry_list) - { - // do not save stale entries. they are dead. - if(entry->stale) - { - continue; - } - QJsonObject entryObj; - entryObj.insert("base", QJsonValue(entry->baseId)); - entryObj.insert("path", QJsonValue(entry->relativePath)); - entryObj.insert("md5sum", QJsonValue(entry->md5sum)); - entryObj.insert("etag", QJsonValue(entry->etag)); - entryObj.insert("last_changed_timestamp", - QJsonValue(double(entry->local_changed_timestamp))); - if (!entry->remote_changed_timestamp.isEmpty()) - entryObj.insert("remote_changed_timestamp", - QJsonValue(entry->remote_changed_timestamp)); - entriesArr.append(entryObj); - } - } - toplevel.insert("entries", entriesArr); + if(m_index_file.isNull()) + return; + QJsonObject toplevel; + toplevel.insert("version", QJsonValue(QString("1"))); + QJsonArray entriesArr; + for (auto group : m_entries) + { + for (auto entry : group.entry_list) + { + // do not save stale entries. they are dead. + if(entry->stale) + { + continue; + } + QJsonObject entryObj; + entryObj.insert("base", QJsonValue(entry->baseId)); + entryObj.insert("path", QJsonValue(entry->relativePath)); + entryObj.insert("md5sum", QJsonValue(entry->md5sum)); + entryObj.insert("etag", QJsonValue(entry->etag)); + entryObj.insert("last_changed_timestamp", + QJsonValue(double(entry->local_changed_timestamp))); + if (!entry->remote_changed_timestamp.isEmpty()) + entryObj.insert("remote_changed_timestamp", + QJsonValue(entry->remote_changed_timestamp)); + entriesArr.append(entryObj); + } + } + toplevel.insert("entries", entriesArr); - QJsonDocument doc(toplevel); - try - { - FS::write(m_index_file, doc.toJson()); - } - catch (Exception & e) - { - qWarning() << e.what(); - } + QJsonDocument doc(toplevel); + try + { + FS::write(m_index_file, doc.toJson()); + } + catch (const Exception &e) + { + qWarning() << e.what(); + } } diff --git a/api/logic/net/HttpMetaCache.h b/api/logic/net/HttpMetaCache.h index 7ee5f735..aa92f8ff 100644 --- a/api/logic/net/HttpMetaCache.h +++ b/api/logic/net/HttpMetaCache.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,99 +27,99 @@ class MULTIMC_LOGIC_EXPORT MetaEntry { friend class HttpMetaCache; protected: - MetaEntry() {} + MetaEntry() {} public: - bool isStale() - { - return stale; - } - void setStale(bool stale) - { - this->stale = stale; - } - QString getFullPath(); - QString getRemoteChangedTimestamp() - { - return remote_changed_timestamp; - } - void setRemoteChangedTimestamp(QString remote_changed_timestamp) - { - this->remote_changed_timestamp = remote_changed_timestamp; - } - void setLocalChangedTimestamp(qint64 timestamp) - { - local_changed_timestamp = timestamp; - } - QString getETag() - { - return etag; - } - void setETag(QString etag) - { - this->etag = etag; - } - QString getMD5Sum() - { - return md5sum; - } - void setMD5Sum(QString md5sum) - { - this->md5sum = md5sum; - } + bool isStale() + { + return stale; + } + void setStale(bool stale) + { + this->stale = stale; + } + QString getFullPath(); + QString getRemoteChangedTimestamp() + { + return remote_changed_timestamp; + } + void setRemoteChangedTimestamp(QString remote_changed_timestamp) + { + this->remote_changed_timestamp = remote_changed_timestamp; + } + void setLocalChangedTimestamp(qint64 timestamp) + { + local_changed_timestamp = timestamp; + } + QString getETag() + { + return etag; + } + void setETag(QString etag) + { + this->etag = etag; + } + QString getMD5Sum() + { + return md5sum; + } + void setMD5Sum(QString md5sum) + { + this->md5sum = md5sum; + } protected: - QString baseId; - QString basePath; - QString relativePath; - QString md5sum; - QString etag; - qint64 local_changed_timestamp = 0; - QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time - bool stale = true; + QString baseId; + QString basePath; + QString relativePath; + QString md5sum; + QString etag; + qint64 local_changed_timestamp = 0; + QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time + bool stale = true; }; typedef std::shared_ptr MetaEntryPtr; class MULTIMC_LOGIC_EXPORT HttpMetaCache : public QObject { - Q_OBJECT + Q_OBJECT public: - // supply path to the cache index file - HttpMetaCache(QString path = QString()); - ~HttpMetaCache(); + // supply path to the cache index file + HttpMetaCache(QString path = QString()); + ~HttpMetaCache(); - // get the entry solely from the cache - // you probably don't want this, unless you have some specific caching needs. - MetaEntryPtr getEntry(QString base, QString resource_path); + // get the entry solely from the cache + // you probably don't want this, unless you have some specific caching needs. + MetaEntryPtr getEntry(QString base, QString resource_path); - // get the entry from cache and verify that it isn't stale (within reason) - MetaEntryPtr resolveEntry(QString base, QString resource_path, - QString expected_etag = QString()); + // get the entry from cache and verify that it isn't stale (within reason) + MetaEntryPtr resolveEntry(QString base, QString resource_path, + QString expected_etag = QString()); - // add a previously resolved stale entry - bool updateEntry(MetaEntryPtr stale_entry); + // add a previously resolved stale entry + bool updateEntry(MetaEntryPtr stale_entry); - // evict selected entry from cache - bool evictEntry(MetaEntryPtr entry); + // evict selected entry from cache + bool evictEntry(MetaEntryPtr entry); - void addBase(QString base, QString base_root); + void addBase(QString base, QString base_root); - // (re)start a timer that calls SaveNow later. - void SaveEventually(); - void Load(); - QString getBasePath(QString base); + // (re)start a timer that calls SaveNow later. + void SaveEventually(); + void Load(); + QString getBasePath(QString base); public slots: - void SaveNow(); + void SaveNow(); private: - // create a new stale entry, given the parameters - MetaEntryPtr staleEntry(QString base, QString resource_path); - struct EntryMap - { - QString base_path; - QMap entry_list; - }; - QMap m_entries; - QString m_index_file; - QTimer saveBatchingTimer; + // create a new stale entry, given the parameters + MetaEntryPtr staleEntry(QString base, QString resource_path); + struct EntryMap + { + QString base_path; + QMap entry_list; + }; + QMap m_entries; + QString m_index_file; + QTimer saveBatchingTimer; }; diff --git a/api/logic/net/MetaCacheSink.cpp b/api/logic/net/MetaCacheSink.cpp index c9d75310..d7f18533 100644 --- a/api/logic/net/MetaCacheSink.cpp +++ b/api/logic/net/MetaCacheSink.cpp @@ -7,59 +7,59 @@ namespace Net { MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) - :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) + :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) { - addValidator(md5sum); -}; + addValidator(md5sum); +} MetaCacheSink::~MetaCacheSink() { - // nil -}; + // nil +} JobStatus MetaCacheSink::initCache(QNetworkRequest& request) { - if (!m_entry->isStale()) - { - return Job_Finished; - } - // check if file exists, if it does, use its information for the request - QFile current(m_filename); - if(current.exists() && current.size() != 0) - { - if (m_entry->getRemoteChangedTimestamp().size()) - { - request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->getRemoteChangedTimestamp().toLatin1()); - } - if (m_entry->getETag().size()) - { - request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); - } - } - return Job_InProgress; + if (!m_entry->isStale()) + { + return Job_Finished; + } + // check if file exists, if it does, use its information for the request + QFile current(m_filename); + if(current.exists() && current.size() != 0) + { + if (m_entry->getRemoteChangedTimestamp().size()) + { + request.setRawHeader(QString("If-Modified-Since").toLatin1(), m_entry->getRemoteChangedTimestamp().toLatin1()); + } + if (m_entry->getETag().size()) + { + request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1()); + } + } + return Job_InProgress; } JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply) { - QFileInfo output_file_info(m_filename); - if(wroteAnyData) - { - m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); - } - m_entry->setETag(reply.rawHeader("ETag").constData()); - if (reply.hasRawHeader("Last-Modified")) - { - m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); - } - m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); - m_entry->setStale(false); - ENV.metacache()->updateEntry(m_entry); - return Job_Finished; + QFileInfo output_file_info(m_filename); + if(wroteAnyData) + { + m_entry->setMD5Sum(m_md5Node->hash().toHex().constData()); + } + m_entry->setETag(reply.rawHeader("ETag").constData()); + if (reply.hasRawHeader("Last-Modified")) + { + m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData()); + } + m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); + m_entry->setStale(false); + ENV.metacache()->updateEntry(m_entry); + return Job_Finished; } bool MetaCacheSink::hasLocalData() { - QFileInfo info(m_filename); - return info.exists() && info.size() != 0; + QFileInfo info(m_filename); + return info.exists() && info.size() != 0; } } diff --git a/api/logic/net/MetaCacheSink.h b/api/logic/net/MetaCacheSink.h index 0f3bbdf6..edcf7ad1 100644 --- a/api/logic/net/MetaCacheSink.h +++ b/api/logic/net/MetaCacheSink.h @@ -7,16 +7,16 @@ namespace Net { class MetaCacheSink : public FileSink { public: /* con/des */ - MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); - virtual ~MetaCacheSink(); - bool hasLocalData() override; + MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum); + virtual ~MetaCacheSink(); + bool hasLocalData() override; protected: /* methods */ - JobStatus initCache(QNetworkRequest & request) override; - JobStatus finalizeCache(QNetworkReply & reply) override; + JobStatus initCache(QNetworkRequest & request) override; + JobStatus finalizeCache(QNetworkReply & reply) override; private: /* data */ - MetaEntryPtr m_entry; - ChecksumValidator * m_md5Node; + MetaEntryPtr m_entry; + ChecksumValidator * m_md5Node; }; } diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h index 62e26d92..9a95f5ad 100644 --- a/api/logic/net/Mode.h +++ b/api/logic/net/Mode.h @@ -4,7 +4,7 @@ namespace Net { enum class Mode { - Offline, - Online + Offline, + Online }; } diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h index 08e40a29..da34a04e 100644 --- a/api/logic/net/NetAction.h +++ b/api/logic/net/NetAction.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,91 +25,91 @@ enum JobStatus { - Job_NotStarted, - Job_InProgress, - Job_Finished, - Job_Failed, - Job_Aborted, - /* - * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. - * Same could be true for aborted task - the presence of pre-existing result is a separate concern - */ - Job_Failed_Proceed + Job_NotStarted, + Job_InProgress, + Job_Finished, + Job_Failed, + Job_Aborted, + /* + * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion. + * Same could be true for aborted task - the presence of pre-existing result is a separate concern + */ + Job_Failed_Proceed }; typedef std::shared_ptr NetActionPtr; class MULTIMC_LOGIC_EXPORT NetAction : public QObject { - Q_OBJECT + Q_OBJECT protected: - explicit NetAction() : QObject(0) {}; + explicit NetAction() : QObject(0) {}; public: - virtual ~NetAction() {}; - - bool isRunning() const - { - return m_status == Job_InProgress; - } - bool isFinished() const - { - return m_status >= Job_Finished; - } - bool wasSuccessful() const - { - return m_status == Job_Finished || m_status == Job_Failed_Proceed; - } - - qint64 totalProgress() const - { - return m_total_progress; - } - qint64 currentProgress() const - { - return m_progress; - } - virtual bool abort() - { - return false; - } - virtual bool canAbort() - { - return false; - } - QUrl url() - { - return m_url; - } + virtual ~NetAction() {}; + + bool isRunning() const + { + return m_status == Job_InProgress; + } + bool isFinished() const + { + return m_status >= Job_Finished; + } + bool wasSuccessful() const + { + return m_status == Job_Finished || m_status == Job_Failed_Proceed; + } + + qint64 totalProgress() const + { + return m_total_progress; + } + qint64 currentProgress() const + { + return m_progress; + } + virtual bool abort() + { + return false; + } + virtual bool canAbort() + { + return false; + } + QUrl url() + { + return m_url; + } signals: - void started(int index); - void netActionProgress(int index, qint64 current, qint64 total); - void succeeded(int index); - void failed(int index); - void aborted(int index); + void started(int index); + void netActionProgress(int index, qint64 current, qint64 total); + void succeeded(int index); + void failed(int index); + void aborted(int index); protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; - virtual void downloadError(QNetworkReply::NetworkError error) = 0; - virtual void downloadFinished() = 0; - virtual void downloadReadyRead() = 0; + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; + virtual void downloadError(QNetworkReply::NetworkError error) = 0; + virtual void downloadFinished() = 0; + virtual void downloadReadyRead() = 0; public slots: - virtual void start() = 0; + virtual void start() = 0; public: - /// index within the parent job, FIXME: nuke - int m_index_within_job = 0; + /// index within the parent job, FIXME: nuke + int m_index_within_job = 0; - /// the network reply - unique_qobject_ptr m_reply; + /// the network reply + unique_qobject_ptr m_reply; - /// source URL - QUrl m_url; + /// source URL + QUrl m_url; - qint64 m_progress = 0; - qint64 m_total_progress = 1; + qint64 m_progress = 0; + qint64 m_total_progress = 1; protected: - JobStatus m_status = Job_NotStarted; + JobStatus m_status = Job_NotStarted; }; diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp index 1ff8e28f..71e31736 100644 --- a/api/logic/net/NetJob.cpp +++ b/api/logic/net/NetJob.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,197 +20,197 @@ void NetJob::partSucceeded(int index) { - // do progress. all slots are 1 in size at least - auto &slot = parts_progress[index]; - partProgress(index, slot.total_progress, slot.total_progress); - - m_doing.remove(index); - m_done.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); + // do progress. all slots are 1 in size at least + auto &slot = parts_progress[index]; + partProgress(index, slot.total_progress, slot.total_progress); + + m_doing.remove(index); + m_done.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partFailed(int index) { - m_doing.remove(index); - auto &slot = parts_progress[index]; - if (slot.failures == 3) - { - m_failed.insert(index); - } - else - { - slot.failures++; - m_todo.enqueue(index); - } - downloads[index].get()->disconnect(this); - startMoreParts(); + m_doing.remove(index); + auto &slot = parts_progress[index]; + if (slot.failures == 3) + { + m_failed.insert(index); + } + else + { + slot.failures++; + m_todo.enqueue(index); + } + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partAborted(int index) { - m_aborted = true; - m_doing.remove(index); - m_failed.insert(index); - downloads[index].get()->disconnect(this); - startMoreParts(); + m_aborted = true; + m_doing.remove(index); + m_failed.insert(index); + downloads[index].get()->disconnect(this); + startMoreParts(); } void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) { - auto &slot = parts_progress[index]; - slot.current_progress = bytesReceived; - slot.total_progress = bytesTotal; - - int done = m_done.size(); - int doing = m_doing.size(); - int all = parts_progress.size(); - - qint64 bytesAll = 0; - qint64 bytesTotalAll = 0; - for(auto & partIdx: m_doing) - { - auto part = parts_progress[partIdx]; - // do not count parts with unknown/nonsensical total size - if(part.total_progress <= 0) - { - continue; - } - bytesAll += part.current_progress; - bytesTotalAll += part.total_progress; - } - - qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; - auto current = done * 1000 + doing * inprogress; - auto current_total = all * 1000; - // HACK: make sure it never jumps backwards. - // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress - if(m_current_progress == 1000) { - m_current_progress = inprogress; - } - if(m_current_progress > current) - { - current = m_current_progress; - } - m_current_progress = current; - setProgress(current, current_total); + auto &slot = parts_progress[index]; + slot.current_progress = bytesReceived; + slot.total_progress = bytesTotal; + + int done = m_done.size(); + int doing = m_doing.size(); + int all = parts_progress.size(); + + qint64 bytesAll = 0; + qint64 bytesTotalAll = 0; + for(auto & partIdx: m_doing) + { + auto part = parts_progress[partIdx]; + // do not count parts with unknown/nonsensical total size + if(part.total_progress <= 0) + { + continue; + } + bytesAll += part.current_progress; + bytesTotalAll += part.total_progress; + } + + qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; + auto current = done * 1000 + doing * inprogress; + auto current_total = all * 1000; + // HACK: make sure it never jumps backwards. + // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress + if(m_current_progress == 1000) { + m_current_progress = inprogress; + } + if(m_current_progress > current) + { + current = m_current_progress; + } + m_current_progress = current; + setProgress(current, current_total); } void NetJob::executeTask() { - // hack that delays early failures so they can be caught easier - QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); + // hack that delays early failures so they can be caught easier + QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); } void NetJob::startMoreParts() { - if(!isRunning()) - { - // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. - return; - } - // OK. We are actively processing tasks, proceed. - // Check for final conditions if there's nothing in the queue. - if(!m_todo.size()) - { - if(!m_doing.size()) - { - if(!m_failed.size()) - { - emitSucceeded(); - } - else if(m_aborted) - { - emitAborted(); - } - else - { - emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); - } - } - return; - } - // There's work to do, try to start more parts. - while (m_doing.size() < 6) - { - if(!m_todo.size()) - return; - int doThis = m_todo.dequeue(); - m_doing.insert(doThis); - auto part = downloads[doThis]; - // connect signals :D - connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); - connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), - SLOT(partProgress(int, qint64, qint64))); - part->start(); - } + if(!isRunning()) + { + // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later. + return; + } + // OK. We are actively processing tasks, proceed. + // Check for final conditions if there's nothing in the queue. + if(!m_todo.size()) + { + if(!m_doing.size()) + { + if(!m_failed.size()) + { + emitSucceeded(); + } + else if(m_aborted) + { + emitAborted(); + } + else + { + emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); + } + } + return; + } + // There's work to do, try to start more parts. + while (m_doing.size() < 6) + { + if(!m_todo.size()) + return; + int doThis = m_todo.dequeue(); + m_doing.insert(doThis); + auto part = downloads[doThis]; + // connect signals :D + connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int))); + connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)), + SLOT(partProgress(int, qint64, qint64))); + part->start(); + } } QStringList NetJob::getFailedFiles() { - QStringList failed; - for (auto index: m_failed) - { - failed.push_back(downloads[index]->url().toString()); - } - failed.sort(); - return failed; + QStringList failed; + for (auto index: m_failed) + { + failed.push_back(downloads[index]->url().toString()); + } + failed.sort(); + return failed; } bool NetJob::canAbort() const { - bool canFullyAbort = true; - // can abort the waiting? - for(auto index: m_todo) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - // can abort the active? - for(auto index: m_doing) - { - auto part = downloads[index]; - canFullyAbort &= part->canAbort(); - } - return canFullyAbort; + bool canFullyAbort = true; + // can abort the waiting? + for(auto index: m_todo) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + // can abort the active? + for(auto index: m_doing) + { + auto part = downloads[index]; + canFullyAbort &= part->canAbort(); + } + return canFullyAbort; } bool NetJob::abort() { - bool fullyAborted = true; - // fail all waiting - m_failed.unite(m_todo.toSet()); - m_todo.clear(); - // abort active - auto toKill = m_doing.toList(); - for(auto index: toKill) - { - auto part = downloads[index]; - fullyAborted &= part->abort(); - } - return fullyAborted; + bool fullyAborted = true; + // fail all waiting + m_failed.unite(m_todo.toSet()); + m_todo.clear(); + // abort active + auto toKill = m_doing.toList(); + for(auto index: toKill) + { + auto part = downloads[index]; + fullyAborted &= part->abort(); + } + return fullyAborted; } bool NetJob::addNetAction(NetActionPtr action) { - action->m_index_within_job = downloads.size(); - downloads.append(action); - part_info pi; - parts_progress.append(pi); - partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); - - if(action->isRunning()) - { - connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); - connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); - connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); - } - else - { - m_todo.append(parts_progress.size() - 1); - } - return true; + action->m_index_within_job = downloads.size(); + downloads.append(action); + part_info pi; + parts_progress.append(pi); + partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress()); + + if(action->isRunning()) + { + connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int))); + connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int))); + connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64))); + } + else + { + m_todo.append(parts_progress.size() - 1); + } + return true; } diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h index be58c61a..0b56bdaa 100644 --- a/api/logic/net/NetJob.h +++ b/api/logic/net/NetJob.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,64 +28,64 @@ typedef shared_qobject_ptr NetJobPtr; class MULTIMC_LOGIC_EXPORT NetJob : public Task { - Q_OBJECT + Q_OBJECT public: - explicit NetJob(QString job_name) : Task() - { - setObjectName(job_name); - } - virtual ~NetJob() {} + explicit NetJob(QString job_name) : Task() + { + setObjectName(job_name); + } + virtual ~NetJob() {} - bool addNetAction(NetActionPtr action); + bool addNetAction(NetActionPtr action); - NetActionPtr operator[](int index) - { - return downloads[index]; - } - const NetActionPtr at(const int index) - { - return downloads.at(index); - } - NetActionPtr first() - { - if (downloads.size()) - return downloads[0]; - return NetActionPtr(); - } - int size() const - { - return downloads.size(); - } - QStringList getFailedFiles(); + NetActionPtr operator[](int index) + { + return downloads[index]; + } + const NetActionPtr at(const int index) + { + return downloads.at(index); + } + NetActionPtr first() + { + if (downloads.size()) + return downloads[0]; + return NetActionPtr(); + } + int size() const + { + return downloads.size(); + } + QStringList getFailedFiles(); - bool canAbort() const override; + bool canAbort() const override; private slots: - void startMoreParts(); + void startMoreParts(); public slots: - virtual void executeTask() override; - virtual bool abort() override; + virtual void executeTask() override; + virtual bool abort() override; private slots: - void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); - void partSucceeded(int index); - void partFailed(int index); - void partAborted(int index); + void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); + void partSucceeded(int index); + void partFailed(int index); + void partAborted(int index); private: - struct part_info - { - qint64 current_progress = 0; - qint64 total_progress = 1; - int failures = 0; - }; - QList downloads; - QList parts_progress; - QQueue m_todo; - QSet m_doing; - QSet m_done; - QSet m_failed; - qint64 m_current_progress = 0; - bool m_aborted = false; + struct part_info + { + qint64 current_progress = 0; + qint64 total_progress = 1; + int failures = 0; + }; + QList downloads; + QList parts_progress; + QQueue m_todo; + QSet m_doing; + QSet m_done; + QSet m_failed; + qint64 m_current_progress = 0; + bool m_aborted = false; }; diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp index d1ddf39d..3526e207 100644 --- a/api/logic/net/PasteUpload.cpp +++ b/api/logic/net/PasteUpload.cpp @@ -8,18 +8,18 @@ PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window) { - m_key = key; - QByteArray temp; - QJsonObject topLevelObj; - QJsonObject sectionObject; - sectionObject.insert("contents", text); - QJsonArray sectionArray; - sectionArray.append(sectionObject); - topLevelObj.insert("description", "MultiMC Log Upload"); - topLevelObj.insert("sections", sectionArray); - QJsonDocument docOut; - docOut.setObject(topLevelObj); - m_jsonContent = docOut.toJson(); + m_key = key; + QByteArray temp; + QJsonObject topLevelObj; + QJsonObject sectionObject; + sectionObject.insert("contents", text); + QJsonArray sectionArray; + sectionArray.append(sectionObject); + topLevelObj.insert("description", "MultiMC Log Upload"); + topLevelObj.insert("sections", sectionArray); + QJsonDocument docOut; + docOut.setObject(topLevelObj); + m_jsonContent = docOut.toJson(); } PasteUpload::~PasteUpload() @@ -28,76 +28,76 @@ PasteUpload::~PasteUpload() bool PasteUpload::validateText() { - return m_jsonContent.size() <= maxSize(); + return m_jsonContent.size() <= maxSize(); } void PasteUpload::executeTask() { - QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes")); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setRawHeader("Content-Type", "application/json"); - request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); - request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); + request.setRawHeader("Content-Type", "application/json"); + request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size())); + request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str()); - QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); + QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent); - m_reply = std::shared_ptr(rep); - setStatus(tr("Uploading to paste.ee")); - connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); + m_reply = std::shared_ptr(rep); + setStatus(tr("Uploading to paste.ee")); + connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished())); } void PasteUpload::downloadError(QNetworkReply::NetworkError error) { - // error happened during download. - qCritical() << "Network error: " << error; - emitFailed(m_reply->errorString()); + // error happened during download. + qCritical() << "Network error: " << error; + emitFailed(m_reply->errorString()); } void PasteUpload::downloadFinished() { - QByteArray data = m_reply->readAll(); - // if the download succeeded - if (m_reply->error() == QNetworkReply::NetworkError::NoError) - { - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - emitFailed(jsonError.errorString()); - return; - } - if (!parseResult(doc)) - { - emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); - return; - } - } - // else the download failed - else - { - emitFailed(QString("Network error: %1").arg(m_reply->errorString())); - m_reply.reset(); - return; - } - emitSucceeded(); + QByteArray data = m_reply->readAll(); + // if the download succeeded + if (m_reply->error() == QNetworkReply::NetworkError::NoError) + { + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + emitFailed(jsonError.errorString()); + return; + } + if (!parseResult(doc)) + { + emitFailed(tr("paste.ee returned an error. Please consult the logs for more information")); + return; + } + } + // else the download failed + else + { + emitFailed(QString("Network error: %1").arg(m_reply->errorString())); + m_reply.reset(); + return; + } + emitSucceeded(); } bool PasteUpload::parseResult(QJsonDocument doc) { - auto object = doc.object(); - auto status = object.value("success").toBool(); - if (!status) - { - qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); - return false; - } - m_pasteLink = object.value("link").toString(); - m_pasteID = object.value("id").toString(); - qDebug() << m_pasteLink; - return true; + auto object = doc.object(); + auto status = object.value("success").toBool(); + if (!status) + { + qCritical() << "paste.ee reported error:" << QString(object.value("error").toString()); + return false; + } + m_pasteLink = object.value("link").toString(); + m_pasteID = object.value("id").toString(); + qDebug() << m_pasteLink; + return true; } diff --git a/api/logic/net/PasteUpload.h b/api/logic/net/PasteUpload.h index 62d2e766..11e05c2e 100644 --- a/api/logic/net/PasteUpload.h +++ b/api/logic/net/PasteUpload.h @@ -8,41 +8,42 @@ class MULTIMC_LOGIC_EXPORT PasteUpload : public Task { - Q_OBJECT + Q_OBJECT public: - PasteUpload(QWidget *window, QString text, QString key = "public"); - virtual ~PasteUpload(); - QString pasteLink() - { - return m_pasteLink; - } - QString pasteID() - { - return m_pasteID; - } - int maxSize() - { - // 2MB for paste.ee - public - if(m_key == "public") - return 1024*1024*2; - // 12MB for paste.ee - with actual key - return 1024*1024*12; - } - bool validateText(); + PasteUpload(QWidget *window, QString text, QString key = "public"); + virtual ~PasteUpload(); + + QString pasteLink() + { + return m_pasteLink; + } + QString pasteID() + { + return m_pasteID; + } + int maxSize() + { + // 2MB for paste.ee - public + if(m_key == "public") + return 1024*1024*2; + // 12MB for paste.ee - with actual key + return 1024*1024*12; + } + bool validateText(); protected: - virtual void executeTask(); + virtual void executeTask(); private: - bool parseResult(QJsonDocument doc); - QString m_error; - QWidget *m_window; - QString m_pasteID; - QString m_pasteLink; - QString m_key; - QByteArray m_jsonContent; - std::shared_ptr m_reply; + bool parseResult(QJsonDocument doc); + QString m_error; + QWidget *m_window; + QString m_pasteID; + QString m_pasteLink; + QString m_key; + QByteArray m_jsonContent; + std::shared_ptr m_reply; public slots: - void downloadError(QNetworkReply::NetworkError); - void downloadFinished(); + void downloadError(QNetworkReply::NetworkError); + void downloadFinished(); }; diff --git a/api/logic/net/Sink.h b/api/logic/net/Sink.h index de9b1722..d526895c 100644 --- a/api/logic/net/Sink.h +++ b/api/logic/net/Sink.h @@ -9,63 +9,63 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Sink { public: /* con/des */ - Sink() {}; - virtual ~Sink() {}; + Sink() {}; + virtual ~Sink() {}; public: /* methods */ - virtual JobStatus init(QNetworkRequest & request) = 0; - virtual JobStatus write(QByteArray & data) = 0; - virtual JobStatus abort() = 0; - virtual JobStatus finalize(QNetworkReply & reply) = 0; - virtual bool hasLocalData() = 0; + virtual JobStatus init(QNetworkRequest & request) = 0; + virtual JobStatus write(QByteArray & data) = 0; + virtual JobStatus abort() = 0; + virtual JobStatus finalize(QNetworkReply & reply) = 0; + virtual bool hasLocalData() = 0; - void addValidator(Validator * validator) - { - if(validator) - { - validators.push_back(std::shared_ptr(validator)); - } - } + void addValidator(Validator * validator) + { + if(validator) + { + validators.push_back(std::shared_ptr(validator)); + } + } protected: /* methods */ - bool finalizeAllValidators(QNetworkReply & reply) - { - for(auto & validator: validators) - { - if(!validator->validate(reply)) - return false; - } - return true; - } - bool failAllValidators() - { - bool success = true; - for(auto & validator: validators) - { - success &= validator->abort(); - } - return success; - } - bool initAllValidators(QNetworkRequest & request) - { - for(auto & validator: validators) - { - if(!validator->init(request)) - return false; - } - return true; - } - bool writeAllValidators(QByteArray & data) - { - for(auto & validator: validators) - { - if(!validator->write(data)) - return false; - } - return true; - } + bool finalizeAllValidators(QNetworkReply & reply) + { + for(auto & validator: validators) + { + if(!validator->validate(reply)) + return false; + } + return true; + } + bool failAllValidators() + { + bool success = true; + for(auto & validator: validators) + { + success &= validator->abort(); + } + return success; + } + bool initAllValidators(QNetworkRequest & request) + { + for(auto & validator: validators) + { + if(!validator->init(request)) + return false; + } + return true; + } + bool writeAllValidators(QByteArray & data) + { + for(auto & validator: validators) + { + if(!validator->write(data)) + return false; + } + return true; + } protected: /* data */ - std::vector> validators; + std::vector> validators; }; } diff --git a/api/logic/net/URLConstants.cpp b/api/logic/net/URLConstants.cpp index bd476b2c..9a4d920b 100644 --- a/api/logic/net/URLConstants.cpp +++ b/api/logic/net/URLConstants.cpp @@ -4,12 +4,12 @@ namespace URLConstants { QString getLegacyJarUrl(QString version) { - return "http://" + AWS_DOWNLOAD_VERSIONS + getJarPath(version); + return AWS_DOWNLOAD_VERSIONS + getJarPath(version); } QString getJarPath(QString version) { - return version + "/" + version + ".jar"; + return version + "/" + version + ".jar"; } diff --git a/api/logic/net/URLConstants.h b/api/logic/net/URLConstants.h index 22d128b2..5ff0f794 100644 --- a/api/logic/net/URLConstants.h +++ b/api/logic/net/URLConstants.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,21 +19,17 @@ namespace URLConstants { -const QString AWS_DOWNLOAD_VERSIONS("s3.amazonaws.com/Minecraft.Download/versions/"); -const QString RESOURCE_BASE("resources.download.minecraft.net/"); -const QString LIBRARY_BASE("libraries.minecraft.net/"); -//const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/"); -const QString SKINS_BASE("crafatar.com/skins/"); -const QString AUTH_BASE("authserver.mojang.com/"); -const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json"); -const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json"); -const QString MOJANG_STATUS_URL("http://status.mojang.com/check"); -const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news"); -const QString LITELOADER_URL("http://dl.liteloader.com/versions/versions.json"); +const QString AWS_DOWNLOAD_VERSIONS("https://s3.amazonaws.com/Minecraft.Download/versions/"); +const QString RESOURCE_BASE("https://resources.download.minecraft.net/"); +const QString LIBRARY_BASE("https://libraries.minecraft.net/"); +const QString SKINS_BASE("https://crafatar.com/skins/"); +const QString AUTH_BASE("https://authserver.mojang.com/"); +const QString MOJANG_STATUS_URL("https://status.mojang.com/check"); const QString IMGUR_BASE_URL("https://api.imgur.com/3/"); -const QString FMLLIBS_OUR_BASE_URL("http://files.multimc.org/fmllibs/"); -const QString FMLLIBS_FORGE_BASE_URL("http://files.minecraftforge.net/fmllibs/"); -const QString TRANSLATIONS_BASE_URL("http://files.multimc.org/translations/"); +const QString FMLLIBS_OUR_BASE_URL("https://files.multimc.org/fmllibs/"); +const QString FMLLIBS_FORGE_BASE_URL("https://files.minecraftforge.net/fmllibs/"); +const QString TRANSLATIONS_BASE_URL("https://files.multimc.org/translations/"); +const QString FTB_CDN_BASE_URL("https://ftb.forgecdn.net/FTB2/"); QString getJarPath(QString version); QString getLegacyJarUrl(QString version); diff --git a/api/logic/net/Validator.h b/api/logic/net/Validator.h index a390ab28..955412ce 100644 --- a/api/logic/net/Validator.h +++ b/api/logic/net/Validator.h @@ -8,13 +8,13 @@ namespace Net { class MULTIMC_LOGIC_EXPORT Validator { public: /* con/des */ - Validator() {}; - virtual ~Validator() {}; + Validator() {}; + virtual ~Validator() {}; public: /* methods */ - virtual bool init(QNetworkRequest & request) = 0; - virtual bool write(QByteArray & data) = 0; - virtual bool abort() = 0; - virtual bool validate(QNetworkReply & reply) = 0; + virtual bool init(QNetworkRequest & request) = 0; + virtual bool write(QByteArray & data) = 0; + virtual bool abort() = 0; + virtual bool validate(QNetworkReply & reply) = 0; }; } \ No newline at end of file diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp index 0ee3f9da..a68022e8 100644 --- a/api/logic/news/NewsChecker.cpp +++ b/api/logic/news/NewsChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,110 +22,110 @@ NewsChecker::NewsChecker(const QString& feedUrl) { - m_feedUrl = feedUrl; + m_feedUrl = feedUrl; } void NewsChecker::reloadNews() { - // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. - if (isLoadingNews()) - { - qDebug() << "Ignored request to reload news. Currently reloading already."; - return; - } - - qDebug() << "Reloading news."; - - NetJob* job = new NetJob("News RSS Feed"); - job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); - QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); - m_newsNetJob.reset(job); - job->start(); + // Start a netjob to download the RSS feed and call rssDownloadFinished() when it's done. + if (isLoadingNews()) + { + qDebug() << "Ignored request to reload news. Currently reloading already."; + return; + } + + qDebug() << "Reloading news."; + + NetJob* job = new NetJob("News RSS Feed"); + job->addNetAction(Net::Download::makeByteArray(m_feedUrl, &newsData)); + QObject::connect(job, &NetJob::succeeded, this, &NewsChecker::rssDownloadFinished); + QObject::connect(job, &NetJob::failed, this, &NewsChecker::rssDownloadFailed); + m_newsNetJob.reset(job); + job->start(); } void NewsChecker::rssDownloadFinished() { - // Parse the XML file and process the RSS feed entries. - qDebug() << "Finished loading RSS feed."; - - m_newsNetJob.reset(); - QDomDocument doc; - { - // Stuff to store error info in. - QString errorMsg = "Unknown error."; - int errorLine = -1; - int errorCol = -1; - - // Parse the XML. - if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) - { - QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); - fail(fullErrorMsg); - newsData.clear(); - return; - } - newsData.clear(); - } - - // If the parsing succeeded, read it. - QDomNodeList items = doc.elementsByTagName("item"); - m_newsEntries.clear(); - for (int i = 0; i < items.length(); i++) - { - QDomElement element = items.at(i).toElement(); - NewsEntryPtr entry; - entry.reset(new NewsEntry()); - QString errorMsg = "An unknown error occurred."; - if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) - { - qDebug() << "Loaded news entry" << entry->title; - m_newsEntries.append(entry); - } - else - { - qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; - } - } - - succeed(); + // Parse the XML file and process the RSS feed entries. + qDebug() << "Finished loading RSS feed."; + + m_newsNetJob.reset(); + QDomDocument doc; + { + // Stuff to store error info in. + QString errorMsg = "Unknown error."; + int errorLine = -1; + int errorCol = -1; + + // Parse the XML. + if (!doc.setContent(newsData, false, &errorMsg, &errorLine, &errorCol)) + { + QString fullErrorMsg = QString("Error parsing RSS feed XML. %s at %d:%d.").arg(errorMsg, errorLine, errorCol); + fail(fullErrorMsg); + newsData.clear(); + return; + } + newsData.clear(); + } + + // If the parsing succeeded, read it. + QDomNodeList items = doc.elementsByTagName("item"); + m_newsEntries.clear(); + for (int i = 0; i < items.length(); i++) + { + QDomElement element = items.at(i).toElement(); + NewsEntryPtr entry; + entry.reset(new NewsEntry()); + QString errorMsg = "An unknown error occurred."; + if (NewsEntry::fromXmlElement(element, entry.get(), &errorMsg)) + { + qDebug() << "Loaded news entry" << entry->title; + m_newsEntries.append(entry); + } + else + { + qWarning() << "Failed to load news entry at index" << i << ":" << errorMsg; + } + } + + succeed(); } void NewsChecker::rssDownloadFailed(QString reason) { - // Set an error message and fail. - fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); + // Set an error message and fail. + fail(tr("Failed to load news RSS feed:\n%1").arg(reason)); } QList NewsChecker::getNewsEntries() const { - return m_newsEntries; + return m_newsEntries; } bool NewsChecker::isLoadingNews() const { - return m_newsNetJob.get() != nullptr; + return m_newsNetJob.get() != nullptr; } QString NewsChecker::getLastLoadErrorMsg() const { - return m_lastLoadError; + return m_lastLoadError; } void NewsChecker::succeed() { - m_lastLoadError = ""; - qDebug() << "News loading succeeded."; - m_newsNetJob.reset(); - emit newsLoaded(); + m_lastLoadError = ""; + qDebug() << "News loading succeeded."; + m_newsNetJob.reset(); + emit newsLoaded(); } void NewsChecker::fail(const QString& errorMsg) { - m_lastLoadError = errorMsg; - qDebug() << "Failed to load news:" << errorMsg; - m_newsNetJob.reset(); - emit newsLoadingFailed(errorMsg); + m_lastLoadError = errorMsg; + qDebug() << "Failed to load news:" << errorMsg; + m_newsNetJob.reset(); + emit newsLoadingFailed(errorMsg); } diff --git a/api/logic/news/NewsChecker.h b/api/logic/news/NewsChecker.h index 44f2534a..5983eeb3 100644 --- a/api/logic/news/NewsChecker.h +++ b/api/logic/news/NewsChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,79 +27,79 @@ class MULTIMC_LOGIC_EXPORT NewsChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - /*! - * Constructs a news reader to read from the given RSS feed URL. - */ - NewsChecker(const QString& feedUrl); - - /*! - * Returns the error message for the last time the news was loaded. - * Empty string if the last load was successful. - */ - QString getLastLoadErrorMsg() const; - - /*! - * Returns true if the news has been loaded successfully. - */ - bool isNewsLoaded() const; - - //! True if the news is currently loading. If true, reloadNews() will do nothing. - bool isLoadingNews() const; - - /*! - * Returns a list of news entries. - */ - QList getNewsEntries() const; - - /*! - * Reloads the news from the website's RSS feed. - * If the news is already loading, this does nothing. - */ - void Q_SLOT reloadNews(); + /*! + * Constructs a news reader to read from the given RSS feed URL. + */ + NewsChecker(const QString& feedUrl); + + /*! + * Returns the error message for the last time the news was loaded. + * Empty string if the last load was successful. + */ + QString getLastLoadErrorMsg() const; + + /*! + * Returns true if the news has been loaded successfully. + */ + bool isNewsLoaded() const; + + //! True if the news is currently loading. If true, reloadNews() will do nothing. + bool isLoadingNews() const; + + /*! + * Returns a list of news entries. + */ + QList getNewsEntries() const; + + /*! + * Reloads the news from the website's RSS feed. + * If the news is already loading, this does nothing. + */ + void Q_SLOT reloadNews(); signals: - /*! - * Signal fired after the news has finished loading. - */ - void newsLoaded(); + /*! + * Signal fired after the news has finished loading. + */ + void newsLoaded(); - /*! - * Signal fired after the news fails to load. - */ - void newsLoadingFailed(QString errorMsg); + /*! + * Signal fired after the news fails to load. + */ + void newsLoadingFailed(QString errorMsg); protected slots: - void rssDownloadFinished(); - void rssDownloadFailed(QString reason); + void rssDownloadFinished(); + void rssDownloadFailed(QString reason); protected: /* data */ - //! The URL for the RSS feed to fetch. - QString m_feedUrl; + //! The URL for the RSS feed to fetch. + QString m_feedUrl; - //! List of news entries. - QList m_newsEntries; + //! List of news entries. + QList m_newsEntries; - //! The network job to use to load the news. - NetJobPtr m_newsNetJob; + //! The network job to use to load the news. + NetJobPtr m_newsNetJob; - //! True if news has been loaded. - bool m_loadedNews; + //! True if news has been loaded. + bool m_loadedNews; - QByteArray newsData; + QByteArray newsData; - /*! - * Gets the error message that was given last time the news was loaded. - * If the last news load succeeded, this will be an empty string. - */ - QString m_lastLoadError; + /*! + * Gets the error message that was given last time the news was loaded. + * If the last news load succeeded, this will be an empty string. + */ + QString m_lastLoadError; protected slots: - /// Emits newsLoaded() and sets m_lastLoadError to empty string. - void succeed(); + /// Emits newsLoaded() and sets m_lastLoadError to empty string. + void succeed(); - /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. - void fail(const QString& errorMsg); + /// Emits newsLoadingFailed() and sets m_lastLoadError to the given message. + void fail(const QString& errorMsg); }; diff --git a/api/logic/news/NewsEntry.cpp b/api/logic/news/NewsEntry.cpp index 4377f766..2edffb1b 100644 --- a/api/logic/news/NewsEntry.cpp +++ b/api/logic/news/NewsEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,23 +19,23 @@ #include NewsEntry::NewsEntry(QObject* parent) : - QObject(parent) + QObject(parent) { - this->title = tr("Untitled"); - this->content = tr("No content."); - this->link = ""; - this->author = tr("Unknown Author"); - this->pubDate = QDateTime::currentDateTime(); + this->title = tr("Untitled"); + this->content = tr("No content."); + this->link = ""; + this->author = tr("Unknown Author"); + this->pubDate = QDateTime::currentDateTime(); } NewsEntry::NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent) : - QObject(parent) + QObject(parent) { - this->title = title; - this->content = content; - this->link = link; - this->author = author; - this->pubDate = pubDate; + this->title = title; + this->content = content; + this->link = link; + this->author = author; + this->pubDate = pubDate; } /*! @@ -43,35 +43,35 @@ NewsEntry::NewsEntry(const QString& title, const QString& content, const QString */ inline QString childValue(const QDomElement& element, const QString& childName, QString defaultVal="") { - QDomNodeList nodes = element.elementsByTagName(childName); - if (nodes.count() > 0) - { - QDomElement element = nodes.at(0).toElement(); - return element.text(); - } - else - { - return defaultVal; - } + QDomNodeList nodes = element.elementsByTagName(childName); + if (nodes.count() > 0) + { + QDomElement element = nodes.at(0).toElement(); + return element.text(); + } + else + { + return defaultVal; + } } bool NewsEntry::fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg) { - QString title = childValue(element, "title", tr("Untitled")); - QString content = childValue(element, "description", tr("No content.")); - QString link = childValue(element, "link"); - QString author = childValue(element, "dc:creator", tr("Unknown Author")); - QString pubDateStr = childValue(element, "pubDate"); + QString title = childValue(element, "title", tr("Untitled")); + QString content = childValue(element, "description", tr("No content.")); + QString link = childValue(element, "link"); + QString author = childValue(element, "dc:creator", tr("Unknown Author")); + QString pubDateStr = childValue(element, "pubDate"); - // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. - QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); - QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); + // FIXME: For now, we're just ignoring timezones. We assume that all time zones in the RSS feed are the same. + QString dateFormat("ddd, dd MMM yyyy hh:mm:ss"); + QDateTime pubDate = QDateTime::fromString(pubDateStr, dateFormat); - entry->title = title; - entry->content = content; - entry->link = link; - entry->author = author; - entry->pubDate = pubDate; - return true; + entry->title = title; + entry->content = content; + entry->link = link; + entry->author = author; + entry->pubDate = pubDate; + return true; } diff --git a/api/logic/news/NewsEntry.h b/api/logic/news/NewsEntry.h index 16a17f9c..94a9e761 100644 --- a/api/logic/news/NewsEntry.h +++ b/api/logic/news/NewsEntry.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,41 +24,41 @@ class NewsEntry : public QObject { - Q_OBJECT + Q_OBJECT public: - /*! - * Constructs an empty news entry. - */ - explicit NewsEntry(QObject* parent=0); + /*! + * Constructs an empty news entry. + */ + explicit NewsEntry(QObject* parent=0); - /*! - * Constructs a new news entry. - * Note that content may contain HTML. - */ - NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); + /*! + * Constructs a new news entry. + * Note that content may contain HTML. + */ + NewsEntry(const QString& title, const QString& content, const QString& link, const QString& author, const QDateTime& pubDate, QObject* parent=0); - /*! - * Attempts to load information from the given XML element into the given news entry pointer. - * If this fails, the function will return false and store an error message in the errorMsg pointer. - */ - static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); + /*! + * Attempts to load information from the given XML element into the given news entry pointer. + * If this fails, the function will return false and store an error message in the errorMsg pointer. + */ + static bool fromXmlElement(const QDomElement& element, NewsEntry* entry, QString* errorMsg=0); - //! The post title. - QString title; + //! The post title. + QString title; - //! The post's content. May contain HTML. - QString content; + //! The post's content. May contain HTML. + QString content; - //! URL to the post. - QString link; + //! URL to the post. + QString link; - //! The post's author. - QString author; - - //! The date and time that this post was published. - QDateTime pubDate; + //! The post's author. + QString author; + + //! The date and time that this post was published. + QDateTime pubDate; }; typedef std::shared_ptr NewsEntryPtr; diff --git a/api/logic/notifications/NotificationChecker.cpp b/api/logic/notifications/NotificationChecker.cpp index 6d006c31..8209c28b 100644 --- a/api/logic/notifications/NotificationChecker.cpp +++ b/api/logic/notifications/NotificationChecker.cpp @@ -10,120 +10,120 @@ NotificationChecker::NotificationChecker(QObject *parent) - : QObject(parent) + : QObject(parent) { } void NotificationChecker::setNotificationsUrl(const QUrl ¬ificationsUrl) { - m_notificationsUrl = notificationsUrl; + m_notificationsUrl = notificationsUrl; } void NotificationChecker::setApplicationChannel(QString channel) { - m_appVersionChannel = channel; + m_appVersionChannel = channel; } void NotificationChecker::setApplicationFullVersion(QString version) { - m_appFullVersion = version; + m_appFullVersion = version; } void NotificationChecker::setApplicationPlatform(QString platform) { - m_appPlatform = platform; + m_appPlatform = platform; } QList NotificationChecker::notificationEntries() const { - return m_entries; + return m_entries; } void NotificationChecker::checkForNotifications() { - if (!m_notificationsUrl.isValid()) - { - qCritical() << "Failed to check for notifications. No notifications URL set." - << "If you'd like to use MultiMC's notification system, please pass the " - "URL to CMake at compile time."; - return; - } - if (m_checkJob) - { - return; - } - m_checkJob.reset(new NetJob("Checking for notifications")); - auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); - entry->setStale(true); - m_checkJob->addNetAction(m_download = Net::Download::makeCached(m_notificationsUrl, entry)); - connect(m_download.get(), &Net::Download::succeeded, this, &NotificationChecker::downloadSucceeded); - m_checkJob->start(); + if (!m_notificationsUrl.isValid()) + { + qCritical() << "Failed to check for notifications. No notifications URL set." + << "If you'd like to use MultiMC's notification system, please pass the " + "URL to CMake at compile time."; + return; + } + if (m_checkJob) + { + return; + } + m_checkJob.reset(new NetJob("Checking for notifications")); + auto entry = ENV.metacache()->resolveEntry("root", "notifications.json"); + entry->setStale(true); + m_checkJob->addNetAction(m_download = Net::Download::makeCached(m_notificationsUrl, entry)); + connect(m_download.get(), &Net::Download::succeeded, this, &NotificationChecker::downloadSucceeded); + m_checkJob->start(); } void NotificationChecker::downloadSucceeded(int) { - m_entries.clear(); - - QFile file(m_download->getTargetFilepath()); - if (file.open(QFile::ReadOnly)) - { - QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); - for (auto it = root.begin(); it != root.end(); ++it) - { - QJsonObject obj = (*it).toObject(); - NotificationEntry entry; - entry.id = obj.value("id").toDouble(); - entry.message = obj.value("message").toString(); - entry.channel = obj.value("channel").toString(); - entry.platform = obj.value("platform").toString(); - entry.from = obj.value("from").toString(); - entry.to = obj.value("to").toString(); - const QString type = obj.value("type").toString("critical"); - if (type == "critical") - { - entry.type = NotificationEntry::Critical; - } - else if (type == "warning") - { - entry.type = NotificationEntry::Warning; - } - else if (type == "information") - { - entry.type = NotificationEntry::Information; - } - if(entryApplies(entry)) - m_entries.append(entry); - } - } - - m_checkJob.reset(); - - emit notificationCheckFinished(); + m_entries.clear(); + + QFile file(m_download->getTargetFilepath()); + if (file.open(QFile::ReadOnly)) + { + QJsonArray root = QJsonDocument::fromJson(file.readAll()).array(); + for (auto it = root.begin(); it != root.end(); ++it) + { + QJsonObject obj = (*it).toObject(); + NotificationEntry entry; + entry.id = obj.value("id").toDouble(); + entry.message = obj.value("message").toString(); + entry.channel = obj.value("channel").toString(); + entry.platform = obj.value("platform").toString(); + entry.from = obj.value("from").toString(); + entry.to = obj.value("to").toString(); + const QString type = obj.value("type").toString("critical"); + if (type == "critical") + { + entry.type = NotificationEntry::Critical; + } + else if (type == "warning") + { + entry.type = NotificationEntry::Warning; + } + else if (type == "information") + { + entry.type = NotificationEntry::Information; + } + if(entryApplies(entry)) + m_entries.append(entry); + } + } + + m_checkJob.reset(); + + emit notificationCheckFinished(); } bool versionLessThan(const QString &v1, const QString &v2) { - QStringList l1 = v1.split('.'); - QStringList l2 = v2.split('.'); - while (!l1.isEmpty() && !l2.isEmpty()) - { - int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); - int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); - if (one != two) - { - return one < two; - } - } - return false; + QStringList l1 = v1.split('.'); + QStringList l2 = v2.split('.'); + while (!l1.isEmpty() && !l2.isEmpty()) + { + int one = l1.isEmpty() ? 0 : l1.takeFirst().toInt(); + int two = l2.isEmpty() ? 0 : l2.takeFirst().toInt(); + if (one != two) + { + return one < two; + } + } + return false; } bool NotificationChecker::entryApplies(const NotificationChecker::NotificationEntry& entry) const { - bool channelApplies = entry.channel.isEmpty() || entry.channel == m_appVersionChannel; - bool platformApplies = entry.platform.isEmpty() || entry.platform == m_appPlatform; - bool fromApplies = - entry.from.isEmpty() || entry.from == m_appFullVersion || !versionLessThan(m_appFullVersion, entry.from); - bool toApplies = - entry.to.isEmpty() || entry.to == m_appFullVersion || !versionLessThan(entry.to, m_appFullVersion); - return channelApplies && platformApplies && fromApplies && toApplies; + bool channelApplies = entry.channel.isEmpty() || entry.channel == m_appVersionChannel; + bool platformApplies = entry.platform.isEmpty() || entry.platform == m_appPlatform; + bool fromApplies = + entry.from.isEmpty() || entry.from == m_appFullVersion || !versionLessThan(m_appFullVersion, entry.from); + bool toApplies = + entry.to.isEmpty() || entry.to == m_appFullVersion || !versionLessThan(entry.to, m_appFullVersion); + return channelApplies && platformApplies && fromApplies && toApplies; } diff --git a/api/logic/notifications/NotificationChecker.h b/api/logic/notifications/NotificationChecker.h index c8e831d5..4b1b893d 100644 --- a/api/logic/notifications/NotificationChecker.h +++ b/api/logic/notifications/NotificationChecker.h @@ -9,55 +9,55 @@ class MULTIMC_LOGIC_EXPORT NotificationChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit NotificationChecker(QObject *parent = 0); - - void setNotificationsUrl(const QUrl ¬ificationsUrl); - void setApplicationPlatform(QString platform); - void setApplicationChannel(QString channel); - void setApplicationFullVersion(QString version); - - struct NotificationEntry - { - int id; - QString message; - enum - { - Critical, - Warning, - Information - } type; - QString channel; - QString platform; - QString from; - QString to; - }; - - QList notificationEntries() const; + explicit NotificationChecker(QObject *parent = 0); + + void setNotificationsUrl(const QUrl ¬ificationsUrl); + void setApplicationPlatform(QString platform); + void setApplicationChannel(QString channel); + void setApplicationFullVersion(QString version); + + struct NotificationEntry + { + int id; + QString message; + enum + { + Critical, + Warning, + Information + } type; + QString channel; + QString platform; + QString from; + QString to; + }; + + QList notificationEntries() const; public slots: - void checkForNotifications(); + void checkForNotifications(); private slots: - void downloadSucceeded(int); + void downloadSucceeded(int); signals: - void notificationCheckFinished(); + void notificationCheckFinished(); private: - bool entryApplies(const NotificationEntry &entry) const; + bool entryApplies(const NotificationEntry &entry) const; private: - QList m_entries; - QUrl m_notificationsUrl; - NetJobPtr m_checkJob; - Net::Download::Ptr m_download; - - QString m_appVersionChannel; - QString m_appPlatform; - QString m_appFullVersion; + QList m_entries; + QUrl m_notificationsUrl; + NetJobPtr m_checkJob; + Net::Download::Ptr m_download; + + QString m_appVersionChannel; + QString m_appPlatform; + QString m_appFullVersion; }; diff --git a/api/logic/pathmatcher/FSTreeMatcher.h b/api/logic/pathmatcher/FSTreeMatcher.h index a5bed57c..361924af 100644 --- a/api/logic/pathmatcher/FSTreeMatcher.h +++ b/api/logic/pathmatcher/FSTreeMatcher.h @@ -7,15 +7,15 @@ class FSTreeMatcher : public IPathMatcher { public: - virtual ~FSTreeMatcher() {}; - FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) - { - } + virtual ~FSTreeMatcher() {}; + FSTreeMatcher(SeparatorPrefixTree<'/'> & tree) : m_fsTree(tree) + { + } - virtual bool matches(const QString &string) const override - { - return m_fsTree.covers(string); - } + virtual bool matches(const QString &string) const override + { + return m_fsTree.covers(string); + } - SeparatorPrefixTree<'/'> & m_fsTree; + SeparatorPrefixTree<'/'> & m_fsTree; }; diff --git a/api/logic/pathmatcher/IPathMatcher.h b/api/logic/pathmatcher/IPathMatcher.h index 1d410947..b60621c9 100644 --- a/api/logic/pathmatcher/IPathMatcher.h +++ b/api/logic/pathmatcher/IPathMatcher.h @@ -4,9 +4,9 @@ class IPathMatcher { public: - typedef std::shared_ptr Ptr; + typedef std::shared_ptr Ptr; public: - virtual ~IPathMatcher(){}; - virtual bool matches(const QString &string) const = 0; + virtual ~IPathMatcher(){}; + virtual bool matches(const QString &string) const = 0; }; diff --git a/api/logic/pathmatcher/MultiMatcher.h b/api/logic/pathmatcher/MultiMatcher.h index 91f70aa4..8bc1b6ee 100644 --- a/api/logic/pathmatcher/MultiMatcher.h +++ b/api/logic/pathmatcher/MultiMatcher.h @@ -5,27 +5,27 @@ class MultiMatcher : public IPathMatcher { public: - virtual ~MultiMatcher() {}; - MultiMatcher() - { - } - MultiMatcher &add(Ptr add) - { - m_matchers.append(add); - return *this; - } + virtual ~MultiMatcher() {}; + MultiMatcher() + { + } + MultiMatcher &add(Ptr add) + { + m_matchers.append(add); + return *this; + } - virtual bool matches(const QString &string) const override - { - for(auto iter: m_matchers) - { - if(iter->matches(string)) - { - return true; - } - } - return false; - } + virtual bool matches(const QString &string) const override + { + for(auto iter: m_matchers) + { + if(iter->matches(string)) + { + return true; + } + } + return false; + } - QList m_matchers; + QList m_matchers; }; diff --git a/api/logic/pathmatcher/RegexpMatcher.h b/api/logic/pathmatcher/RegexpMatcher.h index da552123..825d488c 100644 --- a/api/logic/pathmatcher/RegexpMatcher.h +++ b/api/logic/pathmatcher/RegexpMatcher.h @@ -4,39 +4,39 @@ class RegexpMatcher : public IPathMatcher { public: - virtual ~RegexpMatcher() {}; - RegexpMatcher(const QString ®exp) - { - m_regexp.setPattern(regexp); - m_onlyFilenamePart = !regexp.contains('/'); - } + virtual ~RegexpMatcher() {}; + RegexpMatcher(const QString ®exp) + { + m_regexp.setPattern(regexp); + m_onlyFilenamePart = !regexp.contains('/'); + } - RegexpMatcher &caseSensitive(bool cs = true) - { - if(cs) - { - m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); - } - else - { - m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); - } - return *this; - } + RegexpMatcher &caseSensitive(bool cs = true) + { + if(cs) + { + m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + } + else + { + m_regexp.setPatternOptions(QRegularExpression::NoPatternOption); + } + return *this; + } - virtual bool matches(const QString &string) const override - { - if(m_onlyFilenamePart) - { - auto slash = string.lastIndexOf('/'); - if(slash != -1) - { - auto part = string.mid(slash + 1); - return m_regexp.match(part).hasMatch(); - } - } - return m_regexp.match(string).hasMatch(); - } - QRegularExpression m_regexp; - bool m_onlyFilenamePart = false; + virtual bool matches(const QString &string) const override + { + if(m_onlyFilenamePart) + { + auto slash = string.lastIndexOf('/'); + if(slash != -1) + { + auto part = string.mid(slash + 1); + return m_regexp.match(part).hasMatch(); + } + } + return m_regexp.match(string).hasMatch(); + } + QRegularExpression m_regexp; + bool m_onlyFilenamePart = false; }; diff --git a/api/logic/screenshots/ImgurAlbumCreation.cpp b/api/logic/screenshots/ImgurAlbumCreation.cpp index 3724e3df..3d32f597 100644 --- a/api/logic/screenshots/ImgurAlbumCreation.cpp +++ b/api/logic/screenshots/ImgurAlbumCreation.cpp @@ -12,77 +12,77 @@ ImgurAlbumCreation::ImgurAlbumCreation(QList screenshots) : NetAction(), m_screenshots(screenshots) { - m_url = URLConstants::IMGUR_BASE_URL + "album.json"; - m_status = Job_NotStarted; + m_url = URLConstants::IMGUR_BASE_URL + "album.json"; + m_status = Job_NotStarted; } void ImgurAlbumCreation::start() { - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); - request.setRawHeader("Accept", "application/json"); + m_status = Job_InProgress; + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); + request.setRawHeader("Accept", "application/json"); - QStringList hashes; - for (auto shot : m_screenshots) - { - hashes.append(shot->m_imgurDeleteHash); - } + QStringList hashes; + for (auto shot : m_screenshots) + { + hashes.append(shot->m_imgurDeleteHash); + } - const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; + const QByteArray data = "deletehashes=" + hashes.join(',').toUtf8() + "&title=Minecraft%20Screenshots&privacy=hidden"; - QNetworkReply *rep = ENV.qnam().post(request, data); + QNetworkReply *rep = ENV.qnam().post(request, data); - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); + m_reply.reset(rep); + connect(rep, &QNetworkReply::uploadProgress, this, &ImgurAlbumCreation::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &ImgurAlbumCreation::downloadFinished); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error) { - qDebug() << m_reply->errorString(); - m_status = Job_Failed; + qDebug() << m_reply->errorString(); + m_status = Job_Failed; } void ImgurAlbumCreation::downloadFinished() { - if (m_status != Job_Failed) - { - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << jsonError.errorString(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << doc.toJson(); - emit failed(m_index_within_job); - return; - } - m_deleteHash = object.value("data").toObject().value("deletehash").toString(); - m_id = object.value("data").toObject().value("id").toString(); - m_status = Job_Finished; - emit succeeded(m_index_within_job); - return; - } - else - { - qDebug() << m_reply->readAll(); - m_reply.reset(); - emit failed(m_index_within_job); - return; - } + if (m_status != Job_Failed) + { + QByteArray data = m_reply->readAll(); + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + qDebug() << jsonError.errorString(); + emit failed(m_index_within_job); + return; + } + auto object = doc.object(); + if (!object.value("success").toBool()) + { + qDebug() << doc.toJson(); + emit failed(m_index_within_job); + return; + } + m_deleteHash = object.value("data").toObject().value("deletehash").toString(); + m_id = object.value("data").toObject().value("id").toString(); + m_status = Job_Finished; + emit succeeded(m_index_within_job); + return; + } + else + { + qDebug() << m_reply->readAll(); + m_reply.reset(); + emit failed(m_index_within_job); + return; + } } void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/api/logic/screenshots/ImgurAlbumCreation.h b/api/logic/screenshots/ImgurAlbumCreation.h index 469174e4..55478021 100644 --- a/api/logic/screenshots/ImgurAlbumCreation.h +++ b/api/logic/screenshots/ImgurAlbumCreation.h @@ -8,37 +8,37 @@ typedef std::shared_ptr ImgurAlbumCreationPtr; class MULTIMC_LOGIC_EXPORT ImgurAlbumCreation : public NetAction { public: - explicit ImgurAlbumCreation(QList screenshots); - static ImgurAlbumCreationPtr make(QList screenshots) - { - return ImgurAlbumCreationPtr(new ImgurAlbumCreation(screenshots)); - } + explicit ImgurAlbumCreation(QList screenshots); + static ImgurAlbumCreationPtr make(QList screenshots) + { + return ImgurAlbumCreationPtr(new ImgurAlbumCreation(screenshots)); + } - QString deleteHash() const - { - return m_deleteHash; - } - QString id() const - { - return m_id; - } + QString deleteHash() const + { + return m_deleteHash; + } + QString id() const + { + return m_id; + } protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead() + { + } public slots: - virtual void start(); + virtual void start(); private: - QList m_screenshots; + QList m_screenshots; - QString m_deleteHash; - QString m_id; + QString m_deleteHash; + QString m_id; }; diff --git a/api/logic/screenshots/ImgurUpload.cpp b/api/logic/screenshots/ImgurUpload.cpp index 659879f6..74165869 100644 --- a/api/logic/screenshots/ImgurUpload.cpp +++ b/api/logic/screenshots/ImgurUpload.cpp @@ -14,101 +14,101 @@ ImgurUpload::ImgurUpload(ScreenshotPtr shot) : NetAction(), m_shot(shot) { - m_url = URLConstants::IMGUR_BASE_URL + "upload.json"; - m_status = Job_NotStarted; + m_url = URLConstants::IMGUR_BASE_URL + "upload.json"; + m_status = Job_NotStarted; } void ImgurUpload::start() { - finished = false; - m_status = Job_InProgress; - QNetworkRequest request(m_url); - request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); - request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); - request.setRawHeader("Accept", "application/json"); + finished = false; + m_status = Job_InProgress; + QNetworkRequest request(m_url); + request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)"); + request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3"); + request.setRawHeader("Accept", "application/json"); - QFile f(m_shot->m_file.absoluteFilePath()); - if (!f.open(QFile::ReadOnly)) - { - emit failed(m_index_within_job); - return; - } + QFile f(m_shot->m_file.absoluteFilePath()); + if (!f.open(QFile::ReadOnly)) + { + emit failed(m_index_within_job); + return; + } - QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - QHttpPart filePart; - filePart.setBody(f.readAll().toBase64()); - filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); - filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"image\""); - multipart->append(filePart); - QHttpPart typePart; - typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); - typePart.setBody("base64"); - multipart->append(typePart); - QHttpPart namePart; - namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); - namePart.setBody(m_shot->m_file.baseName().toUtf8()); - multipart->append(namePart); + QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + QHttpPart filePart; + filePart.setBody(f.readAll().toBase64()); + filePart.setHeader(QNetworkRequest::ContentTypeHeader, "image/png"); + filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"image\""); + multipart->append(filePart); + QHttpPart typePart; + typePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"type\""); + typePart.setBody("base64"); + multipart->append(typePart); + QHttpPart namePart; + namePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"name\""); + namePart.setBody(m_shot->m_file.baseName().toUtf8()); + multipart->append(namePart); - QNetworkReply *rep = ENV.qnam().post(request, multipart); + QNetworkReply *rep = ENV.qnam().post(request, multipart); - m_reply.reset(rep); - connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); - connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); - connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), - SLOT(downloadError(QNetworkReply::NetworkError))); + m_reply.reset(rep); + connect(rep, &QNetworkReply::uploadProgress, this, &ImgurUpload::downloadProgress); + connect(rep, &QNetworkReply::finished, this, &ImgurUpload::downloadFinished); + connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), + SLOT(downloadError(QNetworkReply::NetworkError))); } void ImgurUpload::downloadError(QNetworkReply::NetworkError error) { - qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - m_status = Job_Failed; - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); + qCritical() << "ImgurUpload failed with error" << m_reply->errorString() << "Server reply:\n" << m_reply->readAll(); + if(finished) + { + qCritical() << "Double finished ImgurUpload!"; + return; + } + m_status = Job_Failed; + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); } void ImgurUpload::downloadFinished() { - if(finished) - { - qCritical() << "Double finished ImgurUpload!"; - return; - } - QByteArray data = m_reply->readAll(); - m_reply.reset(); - QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - auto object = doc.object(); - if (!object.value("success").toBool()) - { - qDebug() << "Screenshot upload not successful:" << doc.toJson(); - finished = true; - m_reply.reset(); - emit failed(m_index_within_job); - return; - } - m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); - m_shot->m_url = object.value("data").toObject().value("link").toString(); - m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); - m_status = Job_Finished; - finished = true; - emit succeeded(m_index_within_job); - return; + if(finished) + { + qCritical() << "Double finished ImgurUpload!"; + return; + } + QByteArray data = m_reply->readAll(); + m_reply.reset(); + QJsonParseError jsonError; + QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + qDebug() << "imgur server did not reply with JSON" << jsonError.errorString(); + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + auto object = doc.object(); + if (!object.value("success").toBool()) + { + qDebug() << "Screenshot upload not successful:" << doc.toJson(); + finished = true; + m_reply.reset(); + emit failed(m_index_within_job); + return; + } + m_shot->m_imgurId = object.value("data").toObject().value("id").toString(); + m_shot->m_url = object.value("data").toObject().value("link").toString(); + m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString(); + m_status = Job_Finished; + finished = true; + emit succeeded(m_index_within_job); + return; } void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - m_total_progress = bytesTotal; - m_progress = bytesReceived; - emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); + m_total_progress = bytesTotal; + m_progress = bytesReceived; + emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal); } diff --git a/api/logic/screenshots/ImgurUpload.h b/api/logic/screenshots/ImgurUpload.h index 0a766b8f..d79807f2 100644 --- a/api/logic/screenshots/ImgurUpload.h +++ b/api/logic/screenshots/ImgurUpload.h @@ -8,26 +8,26 @@ typedef std::shared_ptr ImgurUploadPtr; class MULTIMC_LOGIC_EXPORT ImgurUpload : public NetAction { public: - explicit ImgurUpload(ScreenshotPtr shot); - static ImgurUploadPtr make(ScreenshotPtr shot) - { - return ImgurUploadPtr(new ImgurUpload(shot)); - } + explicit ImgurUpload(ScreenshotPtr shot); + static ImgurUploadPtr make(ScreenshotPtr shot) + { + return ImgurUploadPtr(new ImgurUpload(shot)); + } protected slots: - virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - virtual void downloadError(QNetworkReply::NetworkError error); - virtual void downloadFinished(); - virtual void downloadReadyRead() - { - } + virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + virtual void downloadError(QNetworkReply::NetworkError error); + virtual void downloadFinished(); + virtual void downloadReadyRead() + { + } public slots: - virtual void start(); + virtual void start(); private: - ScreenshotPtr m_shot; - bool finished = true; + ScreenshotPtr m_shot; + bool finished = true; }; diff --git a/api/logic/screenshots/Screenshot.h b/api/logic/screenshots/Screenshot.h index 2c70ecf5..9db3a8a1 100644 --- a/api/logic/screenshots/Screenshot.h +++ b/api/logic/screenshots/Screenshot.h @@ -7,14 +7,14 @@ struct ScreenShot { - ScreenShot(QFileInfo file) - { - m_file = file; - } - QFileInfo m_file; - QString m_url; - QString m_imgurId; - QString m_imgurDeleteHash; + ScreenShot(QFileInfo file) + { + m_file = file; + } + QFileInfo m_file; + QString m_url; + QString m_imgurId; + QString m_imgurDeleteHash; }; typedef std::shared_ptr ScreenshotPtr; diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp index 9e97f861..ff6d5cf3 100644 --- a/api/logic/settings/INIFile.cpp +++ b/api/logic/settings/INIFile.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,124 +28,136 @@ INIFile::INIFile() QString INIFile::unescape(QString orig) { - QString out; - QChar prev = 0; - for(auto c: orig) - { - if(prev == '\\') - { - if(c == 'n') - out += '\n'; - else if (c == 't') - out += '\t'; - else - out += c; - prev = 0; - } - else - { - if(c == '\\') - { - prev = c; - continue; - } - out += c; - prev = 0; - } - } - return out; + QString out; + QChar prev = 0; + for(auto c: orig) + { + if(prev == '\\') + { + if(c == 'n') + out += '\n'; + else if(c == 't') + out += '\t'; + else if(c == '#') + out += '#'; + else + out += c; + prev = 0; + } + else + { + if(c == '\\') + { + prev = c; + continue; + } + out += c; + prev = 0; + } + } + return out; } QString INIFile::escape(QString orig) { - QString out; - for(auto c: orig) - { - if(c == '\n') - out += "\\n"; - else if (c == '\t') - out += "\\t"; - else if(c == '\\') - out += "\\\\"; - else - out += c; - } - return out; + QString out; + for(auto c: orig) + { + if(c == '\n') + out += "\\n"; + else if (c == '\t') + out += "\\t"; + else if(c == '\\') + out += "\\\\"; + else if(c == '#') + out += "\\#"; + else + out += c; + } + return out; } bool INIFile::saveFile(QString fileName) { - QByteArray outArray; - for (Iterator iter = begin(); iter != end(); iter++) - { - QString value = iter.value().toString(); - value = escape(value); - outArray.append(iter.key().toUtf8()); - outArray.append('='); - outArray.append(value.toUtf8()); - outArray.append('\n'); - } - - try - { - FS::write(fileName, outArray); - } - catch (Exception & e) - { - qCritical() << e.what(); - return false; - } - - return true; + QByteArray outArray; + for (Iterator iter = begin(); iter != end(); iter++) + { + QString value = iter.value().toString(); + value = escape(value); + outArray.append(iter.key().toUtf8()); + outArray.append('='); + outArray.append(value.toUtf8()); + outArray.append('\n'); + } + + try + { + FS::write(fileName, outArray); + } + catch (const Exception &e) + { + qCritical() << e.what(); + return false; + } + + return true; } bool INIFile::loadFile(QString fileName) { - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) - return false; - bool success = loadFile(file.readAll()); - file.close(); - return success; + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) + return false; + bool success = loadFile(file.readAll()); + file.close(); + return success; } bool INIFile::loadFile(QByteArray file) { - QTextStream in(file); - in.setCodec("UTF-8"); - - QStringList lines = in.readAll().split('\n'); - for (int i = 0; i < lines.count(); i++) - { - QString &lineRaw = lines[i]; - // Ignore comments. - QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed(); - - int eqPos = line.indexOf('='); - if (eqPos == -1) - continue; - QString key = line.left(eqPos).trimmed(); - QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); - - valueStr = unescape(valueStr); - - QVariant value(valueStr); - this->operator[](key) = value; - } - - return true; + QTextStream in(file); + in.setCodec("UTF-8"); + + QStringList lines = in.readAll().split('\n'); + for (int i = 0; i < lines.count(); i++) + { + QString &lineRaw = lines[i]; + // Ignore comments. + int commentIndex = 0; + QString line = lineRaw; + // Search for comments until no more escaped # are available + while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) { + if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') { + continue; + } + line = line.left(lineRaw.indexOf('#')).trimmed(); + } + + int eqPos = line.indexOf('='); + if (eqPos == -1) + continue; + QString key = line.left(eqPos).trimmed(); + QString valueStr = line.right(line.length() - eqPos - 1).trimmed(); + + valueStr = unescape(valueStr); + + QVariant value(valueStr); + this->operator[](key) = value; + } + + return true; } QVariant INIFile::get(QString key, QVariant def) const { - if (!this->contains(key)) - return def; - else - return this->operator[](key); + if (!this->contains(key)) + return def; + else + return this->operator[](key); } void INIFile::set(QString key, QVariant val) { - this->operator[](key) = val; + this->operator[](key) = val; } diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h index f0c63d3c..9406f7bd 100644 --- a/api/logic/settings/INIFile.h +++ b/api/logic/settings/INIFile.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,14 +25,14 @@ class MULTIMC_LOGIC_EXPORT INIFile : public QMap { public: - explicit INIFile(); + explicit INIFile(); - bool loadFile(QByteArray file); - bool loadFile(QString fileName); - bool saveFile(QString fileName); + bool loadFile(QByteArray file); + bool loadFile(QString fileName); + bool saveFile(QString fileName); - QVariant get(QString key, QVariant def) const; - void set(QString key, QVariant val); - static QString unescape(QString orig); - static QString escape(QString orig); + QVariant get(QString key, QVariant def) const; + void set(QString key, QVariant val); + static QString unescape(QString orig); + static QString escape(QString orig); }; diff --git a/api/logic/settings/INIFile_test.cpp b/api/logic/settings/INIFile_test.cpp index b3ae7375..08c2155e 100644 --- a/api/logic/settings/INIFile_test.cpp +++ b/api/logic/settings/INIFile_test.cpp @@ -5,56 +5,57 @@ class IniFileTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - void test_Escape_data() - { - QTest::addColumn("through"); - - QTest::newRow("unix path") << "/abc/def/ghi/jkl"; - QTest::newRow("windows path") << "C:\\Program files\\terrible\\name\\of something\\"; - QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet."; - QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet."; - QTest::newRow("Escape sequences 2") << "\"\n\n\""; - } - void test_Escape() - { - QFETCH(QString, through); - - QString there = INIFile::escape(through); - QString back = INIFile::unescape(there); - - QCOMPARE(back, through); - } - - void test_SaveLoad() - { - QString a = "a"; - QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\"; - QString filename = "test_SaveLoad.ini"; - - // save - INIFile f; - f.set("a", a); - f.set("b", b); - f.saveFile(filename); - - // load - INIFile f2; - f2.loadFile(filename); - QCOMPARE(a, f2.get("a","NOT SET").toString()); - QCOMPARE(b, f2.get("b","NOT SET").toString()); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void test_Escape_data() + { + QTest::addColumn("through"); + + QTest::newRow("unix path") << "/abc/def/ghi/jkl"; + QTest::newRow("windows path") << "C:\\Program files\\terrible\\name\\of something\\"; + QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet."; + QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet."; + QTest::newRow("Escape sequences 2") << "\"\n\n\""; + QTest::newRow("Hashtags") << "some data#something"; + } + void test_Escape() + { + QFETCH(QString, through); + + QString there = INIFile::escape(through); + QString back = INIFile::unescape(there); + + QCOMPARE(back, through); + } + + void test_SaveLoad() + { + QString a = "a"; + QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment"; + QString filename = "test_SaveLoad.ini"; + + // save + INIFile f; + f.set("a", a); + f.set("b", b); + f.saveFile(filename); + + // load + INIFile f2; + f2.loadFile(filename); + QCOMPARE(a, f2.get("a","NOT SET").toString()); + QCOMPARE(b, f2.get("b","NOT SET").toString()); + } }; QTEST_GUILESS_MAIN(IniFileTest) diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp index ff2cee31..f04a66b5 100644 --- a/api/logic/settings/INISettingsObject.cpp +++ b/api/logic/settings/INISettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,91 +17,91 @@ #include "Setting.h" INISettingsObject::INISettingsObject(const QString &path, QObject *parent) - : SettingsObject(parent) + : SettingsObject(parent) { - m_filePath = path; - m_ini.loadFile(path); + m_filePath = path; + m_ini.loadFile(path); } void INISettingsObject::setFilePath(const QString &filePath) { - m_filePath = filePath; + m_filePath = filePath; } bool INISettingsObject::reload() { - return m_ini.loadFile(m_filePath) && SettingsObject::reload(); + return m_ini.loadFile(m_filePath) && SettingsObject::reload(); } void INISettingsObject::suspendSave() { - m_suspendSave = true; + m_suspendSave = true; } void INISettingsObject::resumeSave() { - m_suspendSave = false; - if(m_doSave) - { - m_ini.saveFile(m_filePath); - } + m_suspendSave = false; + if(m_doSave) + { + m_ini.saveFile(m_filePath); + } } void INISettingsObject::changeSetting(const Setting &setting, QVariant value) { - if (contains(setting.id())) - { - // valid value -> set the main config, remove all the sysnonyms - if (value.isValid()) - { - auto list = setting.configKeys(); - m_ini.set(list.takeFirst(), value); - for(auto iter: list) - m_ini.remove(iter); - } - // invalid -> remove all (just like resetSetting) - else - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - } - doSave(); - } + if (contains(setting.id())) + { + // valid value -> set the main config, remove all the sysnonyms + if (value.isValid()) + { + auto list = setting.configKeys(); + m_ini.set(list.takeFirst(), value); + for(auto iter: list) + m_ini.remove(iter); + } + // invalid -> remove all (just like resetSetting) + else + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + } + doSave(); + } } void INISettingsObject::doSave() { - if(m_suspendSave) - { - m_doSave = true; - } - else - { - m_ini.saveFile(m_filePath); - } + if(m_suspendSave) + { + m_doSave = true; + } + else + { + m_ini.saveFile(m_filePath); + } } void INISettingsObject::resetSetting(const Setting &setting) { - // if we have the setting, remove all the synonyms. ALL OF THEM - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - m_ini.remove(iter); - doSave(); - } + // if we have the setting, remove all the synonyms. ALL OF THEM + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + m_ini.remove(iter); + doSave(); + } } QVariant INISettingsObject::retrieveValue(const Setting &setting) { - // if we have the setting, return value of the first matching synonym - if (contains(setting.id())) - { - for(auto iter: setting.configKeys()) - { - if(m_ini.contains(iter)) - return m_ini[iter]; - } - } - return QVariant(); + // if we have the setting, return value of the first matching synonym + if (contains(setting.id())) + { + for(auto iter: setting.configKeys()) + { + if(m_ini.contains(iter)) + return m_ini[iter]; + } + } + return QVariant(); } diff --git a/api/logic/settings/INISettingsObject.h b/api/logic/settings/INISettingsObject.h index 111215e6..c8ea99ca 100644 --- a/api/logic/settings/INISettingsObject.h +++ b/api/logic/settings/INISettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,39 +28,39 @@ */ class MULTIMC_LOGIC_EXPORT INISettingsObject : public SettingsObject { - Q_OBJECT + Q_OBJECT public: - explicit INISettingsObject(const QString &path, QObject *parent = 0); + explicit INISettingsObject(const QString &path, QObject *parent = 0); - /*! - * \brief Gets the path to the INI file. - * \return The path to the INI file. - */ - virtual QString filePath() const - { - return m_filePath; - } + /*! + * \brief Gets the path to the INI file. + * \return The path to the INI file. + */ + virtual QString filePath() const + { + return m_filePath; + } - /*! - * \brief Sets the path to the INI file and reloads it. - * \param filePath The INI file's new path. - */ - virtual void setFilePath(const QString &filePath); + /*! + * \brief Sets the path to the INI file and reloads it. + * \param filePath The INI file's new path. + */ + virtual void setFilePath(const QString &filePath); - bool reload() override; + bool reload() override; - void suspendSave() override; - void resumeSave() override; + void suspendSave() override; + void resumeSave() override; protected slots: - virtual void changeSetting(const Setting &setting, QVariant value) override; - virtual void resetSetting(const Setting &setting) override; + virtual void changeSetting(const Setting &setting, QVariant value) override; + virtual void resetSetting(const Setting &setting) override; protected: - virtual QVariant retrieveValue(const Setting &setting) override; - void doSave(); + virtual QVariant retrieveValue(const Setting &setting) override; + void doSave(); protected: - INIFile m_ini; - QString m_filePath; + INIFile m_ini; + QString m_filePath; }; diff --git a/api/logic/settings/OverrideSetting.cpp b/api/logic/settings/OverrideSetting.cpp index a3d48e03..1efdebac 100644 --- a/api/logic/settings/OverrideSetting.cpp +++ b/api/logic/settings/OverrideSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,39 +16,39 @@ #include "OverrideSetting.h" OverrideSetting::OverrideSetting(std::shared_ptr other, std::shared_ptr gate) - : Setting(other->configKeys(), QVariant()) + : Setting(other->configKeys(), QVariant()) { - Q_ASSERT(other); - Q_ASSERT(gate); - m_other = other; - m_gate = gate; + Q_ASSERT(other); + Q_ASSERT(gate); + m_other = other; + m_gate = gate; } bool OverrideSetting::isOverriding() const { - return m_gate->get().toBool(); + return m_gate->get().toBool(); } QVariant OverrideSetting::defValue() const { - return m_other->get(); + return m_other->get(); } QVariant OverrideSetting::get() const { - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); + if(isOverriding()) + { + return Setting::get(); + } + return m_other->get(); } void OverrideSetting::reset() { - Setting::reset(); + Setting::reset(); } void OverrideSetting::set(QVariant value) { - Setting::set(value); + Setting::set(value); } diff --git a/api/logic/settings/OverrideSetting.h b/api/logic/settings/OverrideSetting.h index f2cbc5dc..f99caff1 100644 --- a/api/logic/settings/OverrideSetting.h +++ b/api/logic/settings/OverrideSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,19 +28,19 @@ */ class OverrideSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - explicit OverrideSetting(std::shared_ptr overriden, std::shared_ptr gate); + explicit OverrideSetting(std::shared_ptr overriden, std::shared_ptr gate); - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); + virtual QVariant defValue() const; + virtual QVariant get() const; + virtual void set (QVariant value); + virtual void reset(); private: - bool isOverriding() const; + bool isOverriding() const; protected: - std::shared_ptr m_other; - std::shared_ptr m_gate; + std::shared_ptr m_other; + std::shared_ptr m_gate; }; diff --git a/api/logic/settings/PassthroughSetting.cpp b/api/logic/settings/PassthroughSetting.cpp index 5da5d11c..beee4152 100644 --- a/api/logic/settings/PassthroughSetting.cpp +++ b/api/logic/settings/PassthroughSetting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,54 +16,54 @@ #include "PassthroughSetting.h" PassthroughSetting::PassthroughSetting(std::shared_ptr other, std::shared_ptr gate) - : Setting(other->configKeys(), QVariant()) + : Setting(other->configKeys(), QVariant()) { - Q_ASSERT(other); - m_other = other; - m_gate = gate; + Q_ASSERT(other); + m_other = other; + m_gate = gate; } bool PassthroughSetting::isOverriding() const { - if(!m_gate) - { - return false; - } - return m_gate->get().toBool(); + if(!m_gate) + { + return false; + } + return m_gate->get().toBool(); } QVariant PassthroughSetting::defValue() const { - if(isOverriding()) - { - return m_other->get(); - } - return m_other->defValue(); + if(isOverriding()) + { + return m_other->get(); + } + return m_other->defValue(); } QVariant PassthroughSetting::get() const { - if(isOverriding()) - { - return Setting::get(); - } - return m_other->get(); + if(isOverriding()) + { + return Setting::get(); + } + return m_other->get(); } void PassthroughSetting::reset() { - if(isOverriding()) - { - Setting::reset(); - } - m_other->reset(); + if(isOverriding()) + { + Setting::reset(); + } + m_other->reset(); } void PassthroughSetting::set(QVariant value) { - if(isOverriding()) - { - Setting::set(value); - } - m_other->set(value); + if(isOverriding()) + { + Setting::set(value); + } + m_other->set(value); } diff --git a/api/logic/settings/PassthroughSetting.h b/api/logic/settings/PassthroughSetting.h index ee844da4..00212072 100644 --- a/api/logic/settings/PassthroughSetting.h +++ b/api/logic/settings/PassthroughSetting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,19 +27,19 @@ */ class PassthroughSetting : public Setting { - Q_OBJECT + Q_OBJECT public: - explicit PassthroughSetting(std::shared_ptr overriden, std::shared_ptr gate); + explicit PassthroughSetting(std::shared_ptr overriden, std::shared_ptr gate); - virtual QVariant defValue() const; - virtual QVariant get() const; - virtual void set (QVariant value); - virtual void reset(); + virtual QVariant defValue() const; + virtual QVariant get() const; + virtual void set (QVariant value); + virtual void reset(); private: - bool isOverriding() const; + bool isOverriding() const; protected: - std::shared_ptr m_other; - std::shared_ptr m_gate; + std::shared_ptr m_other; + std::shared_ptr m_gate; }; diff --git a/api/logic/settings/Setting.cpp b/api/logic/settings/Setting.cpp index fa0041e0..419dfa2f 100644 --- a/api/logic/settings/Setting.cpp +++ b/api/logic/settings/Setting.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,37 +17,37 @@ #include "settings/SettingsObject.h" Setting::Setting(QStringList synonyms, QVariant defVal) - : QObject(), m_synonyms(synonyms), m_defVal(defVal) + : QObject(), m_synonyms(synonyms), m_defVal(defVal) { } QVariant Setting::get() const { - SettingsObject *sbase = m_storage; - if (!sbase) - { - return defValue(); - } - else - { - QVariant test = sbase->retrieveValue(*this); - if (!test.isValid()) - return defValue(); - return test; - } + SettingsObject *sbase = m_storage; + if (!sbase) + { + return defValue(); + } + else + { + QVariant test = sbase->retrieveValue(*this); + if (!test.isValid()) + return defValue(); + return test; + } } QVariant Setting::defValue() const { - return m_defVal; + return m_defVal; } void Setting::set(QVariant value) { - emit SettingChanged(*this, value); + emit SettingChanged(*this, value); } void Setting::reset() { - emit settingReset(*this); + emit settingReset(*this); } diff --git a/api/logic/settings/Setting.h b/api/logic/settings/Setting.h index 3edea7be..9d827c72 100644 --- a/api/logic/settings/Setting.h +++ b/api/logic/settings/Setting.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,91 +29,91 @@ class SettingsObject; */ class MULTIMC_LOGIC_EXPORT Setting : public QObject { - Q_OBJECT + Q_OBJECT public: - /** - * Construct a Setting - * - * Synonyms are all the possible names used in the settings object, in order of preference. - * First synonym is the ID, which identifies the setting in MultiMC. - * - * defVal is the default value that will be returned when the settings object - * doesn't have any value for this setting. - */ - explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); + /** + * Construct a Setting + * + * Synonyms are all the possible names used in the settings object, in order of preference. + * First synonym is the ID, which identifies the setting in MultiMC. + * + * defVal is the default value that will be returned when the settings object + * doesn't have any value for this setting. + */ + explicit Setting(QStringList synonyms, QVariant defVal = QVariant()); - /*! - * \brief Gets this setting's ID. - * This is used to refer to the setting within the application. - * \warning Changing the ID while the setting is registered with a SettingsObject results in - * undefined behavior. - * \return The ID of the setting. - */ - virtual QString id() const - { - return m_synonyms.first(); - } + /*! + * \brief Gets this setting's ID. + * This is used to refer to the setting within the application. + * \warning Changing the ID while the setting is registered with a SettingsObject results in + * undefined behavior. + * \return The ID of the setting. + */ + virtual QString id() const + { + return m_synonyms.first(); + } - /*! - * \brief Gets this setting's config file key. - * This is used to store the setting's value in the config file. It is usually - * the same as the setting's ID, but it can be different. - * \return The setting's config file key. - */ - virtual QStringList configKeys() const - { - return m_synonyms; - } + /*! + * \brief Gets this setting's config file key. + * This is used to store the setting's value in the config file. It is usually + * the same as the setting's ID, but it can be different. + * \return The setting's config file key. + */ + virtual QStringList configKeys() const + { + return m_synonyms; + } - /*! - * \brief Gets this setting's value as a QVariant. - * This is done by calling the SettingsObject's retrieveValue() function. - * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. - * \return QVariant containing this setting's value. - * \sa value() - */ - virtual QVariant get() const; + /*! + * \brief Gets this setting's value as a QVariant. + * This is done by calling the SettingsObject's retrieveValue() function. + * If this Setting doesn't have a SettingsObject, this returns an invalid QVariant. + * \return QVariant containing this setting's value. + * \sa value() + */ + virtual QVariant get() const; - /*! - * \brief Gets this setting's default value. - * \return The default value of this setting. - */ - virtual QVariant defValue() const; + /*! + * \brief Gets this setting's default value. + * \return The default value of this setting. + */ + virtual QVariant defValue() const; signals: - /*! - * \brief Signal emitted when this Setting object's value changes. - * \param setting A reference to the Setting that changed. - * \param value This Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); + /*! + * \brief Signal emitted when this Setting object's value changes. + * \param setting A reference to the Setting that changed. + * \param value This Setting object's new value. + */ + void SettingChanged(const Setting &setting, QVariant value); - /*! - * \brief Signal emitted when this Setting object's value resets to default. - * \param setting A reference to the Setting that changed. - */ - void settingReset(const Setting &setting); + /*! + * \brief Signal emitted when this Setting object's value resets to default. + * \param setting A reference to the Setting that changed. + */ + void settingReset(const Setting &setting); public slots: - /*! - * \brief Changes the setting's value. - * This is done by emitting the SettingChanged() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - * \param value The new value. - */ - virtual void set(QVariant value); + /*! + * \brief Changes the setting's value. + * This is done by emitting the SettingChanged() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + * \param value The new value. + */ + virtual void set(QVariant value); - /*! - * \brief Reset the setting to default - * This is done by emitting the settingReset() signal which will then be - * handled by the SettingsObject object and cause the setting to change. - */ - virtual void reset(); + /*! + * \brief Reset the setting to default + * This is done by emitting the settingReset() signal which will then be + * handled by the SettingsObject object and cause the setting to change. + */ + virtual void reset(); protected: - friend class SettingsObject; - SettingsObject * m_storage; - QStringList m_synonyms; - QVariant m_defVal; + friend class SettingsObject; + SettingsObject * m_storage; + QStringList m_synonyms; + QVariant m_defVal; }; diff --git a/api/logic/settings/SettingsObject.cpp b/api/logic/settings/SettingsObject.cpp index 87a8c2a8..b4fc5a68 100644 --- a/api/logic/settings/SettingsObject.cpp +++ b/api/logic/settings/SettingsObject.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,116 +27,116 @@ SettingsObject::SettingsObject(QObject *parent) : QObject(parent) SettingsObject::~SettingsObject() { - m_settings.clear(); + m_settings.clear(); } std::shared_ptr SettingsObject::registerOverride(std::shared_ptr original, - std::shared_ptr gate) + std::shared_ptr gate) { - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto override = std::make_shared(original, gate); - override->m_storage = this; - connectSignals(*override); - m_settings.insert(override->id(), override); - return override; + if (contains(original->id())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(original->id()); + return nullptr; // Fail + } + auto override = std::make_shared(original, gate); + override->m_storage = this; + connectSignals(*override); + m_settings.insert(override->id(), override); + return override; } std::shared_ptr SettingsObject::registerPassthrough(std::shared_ptr original, - std::shared_ptr gate) + std::shared_ptr gate) { - if (contains(original->id())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(original->id()); - return nullptr; // Fail - } - auto passthrough = std::make_shared(original, gate); - passthrough->m_storage = this; - connectSignals(*passthrough); - m_settings.insert(passthrough->id(), passthrough); - return passthrough; + if (contains(original->id())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(original->id()); + return nullptr; // Fail + } + auto passthrough = std::make_shared(original, gate); + passthrough->m_storage = this; + connectSignals(*passthrough); + m_settings.insert(passthrough->id(), passthrough); + return passthrough; } std::shared_ptr SettingsObject::registerSetting(QStringList synonyms, QVariant defVal) { - if (synonyms.empty()) - return nullptr; - if (contains(synonyms.first())) - { - qCritical() << QString("Failed to register setting %1. ID already exists.") - .arg(synonyms.first()); - return nullptr; // Fail - } - auto setting = std::make_shared(synonyms, defVal); - setting->m_storage = this; - connectSignals(*setting); - m_settings.insert(setting->id(), setting); - return setting; + if (synonyms.empty()) + return nullptr; + if (contains(synonyms.first())) + { + qCritical() << QString("Failed to register setting %1. ID already exists.") + .arg(synonyms.first()); + return nullptr; // Fail + } + auto setting = std::make_shared(synonyms, defVal); + setting->m_storage = this; + connectSignals(*setting); + m_settings.insert(setting->id(), setting); + return setting; } std::shared_ptr SettingsObject::getSetting(const QString &id) const { - // Make sure there is a setting with the given ID. - if (!m_settings.contains(id)) - return NULL; + // Make sure there is a setting with the given ID. + if (!m_settings.contains(id)) + return NULL; - return m_settings[id]; + return m_settings[id]; } QVariant SettingsObject::get(const QString &id) const { - auto setting = getSetting(id); - return (setting ? setting->get() : QVariant()); + auto setting = getSetting(id); + return (setting ? setting->get() : QVariant()); } bool SettingsObject::set(const QString &id, QVariant value) { - auto setting = getSetting(id); - if (!setting) - { - qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id); - return false; - } - else - { - setting->set(value); - return true; - } + auto setting = getSetting(id); + if (!setting) + { + qCritical() << QString("Error changing setting %1. Setting doesn't exist.").arg(id); + return false; + } + else + { + setting->set(value); + return true; + } } void SettingsObject::reset(const QString &id) const { - auto setting = getSetting(id); - if (setting) - setting->reset(); + auto setting = getSetting(id); + if (setting) + setting->reset(); } bool SettingsObject::contains(const QString &id) { - return m_settings.contains(id); + return m_settings.contains(id); } bool SettingsObject::reload() { - for (auto setting : m_settings.values()) - { - setting->set(setting->get()); - } - return true; + for (auto setting : m_settings.values()) + { + setting->set(setting->get()); + } + return true; } void SettingsObject::connectSignals(const Setting &setting) { - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SLOT(changeSetting(const Setting &, QVariant))); - connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), - SIGNAL(SettingChanged(const Setting &, QVariant))); + connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), + SLOT(changeSetting(const Setting &, QVariant))); + connect(&setting, SIGNAL(SettingChanged(const Setting &, QVariant)), + SIGNAL(SettingChanged(const Setting &, QVariant))); - connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); - connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); + connect(&setting, SIGNAL(settingReset(Setting)), SLOT(resetSetting(const Setting &))); + connect(&setting, SIGNAL(settingReset(Setting)), SIGNAL(settingReset(const Setting &))); } diff --git a/api/logic/settings/SettingsObject.h b/api/logic/settings/SettingsObject.h index 8582d8ad..11dc50ca 100644 --- a/api/logic/settings/SettingsObject.h +++ b/api/logic/settings/SettingsObject.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,173 +42,173 @@ typedef std::shared_ptr SettingsObjectPtr; */ class MULTIMC_LOGIC_EXPORT SettingsObject : public QObject { - Q_OBJECT + Q_OBJECT public: - class Lock - { - public: - Lock(SettingsObjectPtr locked) - :m_locked(locked) - { - m_locked->suspendSave(); - } - ~Lock() - { - m_locked->resumeSave(); - } - private: - SettingsObjectPtr m_locked; - }; + class Lock + { + public: + Lock(SettingsObjectPtr locked) + :m_locked(locked) + { + m_locked->suspendSave(); + } + ~Lock() + { + m_locked->resumeSave(); + } + private: + SettingsObjectPtr m_locked; + }; public: - explicit SettingsObject(QObject *parent = 0); - virtual ~SettingsObject(); - /*! - * Registers an override setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerOverride(std::shared_ptr original, std::shared_ptr gate); - - /*! - * Registers a passthorugh setting for the given original setting in this settings object - * gate decides if the passthrough (true) or the original (false) is used for value - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerPassthrough(std::shared_ptr original, std::shared_ptr gate); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerSetting(QStringList synonyms, - QVariant defVal = QVariant()); - - /*! - * Registers the given setting with this SettingsObject and connects the necessary signals. - * - * This will fail if there is already a setting with the same ID as - * the one that is being registered. - * \return A valid Setting shared pointer if successful. - */ - std::shared_ptr registerSetting(QString id, QVariant defVal = QVariant()) - { - return registerSetting(QStringList(id), defVal); - } - - /*! - * \brief Gets the setting with the given ID. - * \param id The ID of the setting to get. - * \return A pointer to the setting with the given ID. - * Returns null if there is no setting with the given ID. - * \sa operator []() - */ - std::shared_ptr getSetting(const QString &id) const; - - /*! - * \brief Gets the value of the setting with the given ID. - * \param id The ID of the setting to get. - * \return The setting's value as a QVariant. - * If no setting with the given ID exists, returns an invalid QVariant. - */ - QVariant get(const QString &id) const; - - /*! - * \brief Sets the value of the setting with the given ID. - * If no setting with the given ID exists, returns false - * \param id The ID of the setting to change. - * \param value The new value of the setting. - * \return True if successful, false if it failed. - */ - bool set(const QString &id, QVariant value); - - /*! - * \brief Reverts the setting with the given ID to default. - * \param id The ID of the setting to reset. - */ - void reset(const QString &id) const; - - /*! - * \brief Checks if this SettingsObject contains a setting with the given ID. - * \param id The ID to check for. - * \return True if the SettingsObject has a setting with the given ID. - */ - bool contains(const QString &id); - - /*! - * \brief Reloads the settings and emit signals for changed settings - * \return True if reloading was successful - */ - virtual bool reload(); - - virtual void suspendSave() = 0; - virtual void resumeSave() = 0; + explicit SettingsObject(QObject *parent = 0); + virtual ~SettingsObject(); + /*! + * Registers an override setting for the given original setting in this settings object + * gate decides if the passthrough (true) or the original (false) is used for value + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerOverride(std::shared_ptr original, std::shared_ptr gate); + + /*! + * Registers a passthorugh setting for the given original setting in this settings object + * gate decides if the passthrough (true) or the original (false) is used for value + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerPassthrough(std::shared_ptr original, std::shared_ptr gate); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerSetting(QStringList synonyms, + QVariant defVal = QVariant()); + + /*! + * Registers the given setting with this SettingsObject and connects the necessary signals. + * + * This will fail if there is already a setting with the same ID as + * the one that is being registered. + * \return A valid Setting shared pointer if successful. + */ + std::shared_ptr registerSetting(QString id, QVariant defVal = QVariant()) + { + return registerSetting(QStringList(id), defVal); + } + + /*! + * \brief Gets the setting with the given ID. + * \param id The ID of the setting to get. + * \return A pointer to the setting with the given ID. + * Returns null if there is no setting with the given ID. + * \sa operator []() + */ + std::shared_ptr getSetting(const QString &id) const; + + /*! + * \brief Gets the value of the setting with the given ID. + * \param id The ID of the setting to get. + * \return The setting's value as a QVariant. + * If no setting with the given ID exists, returns an invalid QVariant. + */ + QVariant get(const QString &id) const; + + /*! + * \brief Sets the value of the setting with the given ID. + * If no setting with the given ID exists, returns false + * \param id The ID of the setting to change. + * \param value The new value of the setting. + * \return True if successful, false if it failed. + */ + bool set(const QString &id, QVariant value); + + /*! + * \brief Reverts the setting with the given ID to default. + * \param id The ID of the setting to reset. + */ + void reset(const QString &id) const; + + /*! + * \brief Checks if this SettingsObject contains a setting with the given ID. + * \param id The ID to check for. + * \return True if the SettingsObject has a setting with the given ID. + */ + bool contains(const QString &id); + + /*! + * \brief Reloads the settings and emit signals for changed settings + * \return True if reloading was successful + */ + virtual bool reload(); + + virtual void suspendSave() = 0; + virtual void resumeSave() = 0; signals: - /*! - * \brief Signal emitted when one of this SettingsObject object's settings changes. - * This is usually just connected directly to each Setting object's - * SettingChanged() signals. - * \param setting A reference to the Setting object that changed. - * \param value The Setting object's new value. - */ - void SettingChanged(const Setting &setting, QVariant value); - - /*! - * \brief Signal emitted when one of this SettingsObject object's settings resets. - * This is usually just connected directly to each Setting object's - * settingReset() signals. - * \param setting A reference to the Setting object that changed. - */ - void settingReset(const Setting &setting); + /*! + * \brief Signal emitted when one of this SettingsObject object's settings changes. + * This is usually just connected directly to each Setting object's + * SettingChanged() signals. + * \param setting A reference to the Setting object that changed. + * \param value The Setting object's new value. + */ + void SettingChanged(const Setting &setting, QVariant value); + + /*! + * \brief Signal emitted when one of this SettingsObject object's settings resets. + * This is usually just connected directly to each Setting object's + * settingReset() signals. + * \param setting A reference to the Setting object that changed. + */ + void settingReset(const Setting &setting); protected slots: - /*! - * \brief Changes a setting. - * This slot is usually connected to each Setting object's - * SettingChanged() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - * \param value The setting's new value. - */ - virtual void changeSetting(const Setting &setting, QVariant value) = 0; - - /*! - * \brief Resets a setting. - * This slot is usually connected to each Setting object's - * settingReset() signal. The signal is emitted, causing this slot - * to update the setting's value in the config file. - * \param setting A reference to the Setting object that changed. - */ - virtual void resetSetting(const Setting &setting) = 0; + /*! + * \brief Changes a setting. + * This slot is usually connected to each Setting object's + * SettingChanged() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + * \param value The setting's new value. + */ + virtual void changeSetting(const Setting &setting, QVariant value) = 0; + + /*! + * \brief Resets a setting. + * This slot is usually connected to each Setting object's + * settingReset() signal. The signal is emitted, causing this slot + * to update the setting's value in the config file. + * \param setting A reference to the Setting object that changed. + */ + virtual void resetSetting(const Setting &setting) = 0; protected: - /*! - * \brief Connects the necessary signals to the given Setting. - * \param setting The setting to connect. - */ - void connectSignals(const Setting &setting); + /*! + * \brief Connects the necessary signals to the given Setting. + * \param setting The setting to connect. + */ + void connectSignals(const Setting &setting); - /*! - * \brief Function used by Setting objects to get their values from the SettingsObject. - * \param setting The - * \return - */ - virtual QVariant retrieveValue(const Setting &setting) = 0; + /*! + * \brief Function used by Setting objects to get their values from the SettingsObject. + * \param setting The + * \return + */ + virtual QVariant retrieveValue(const Setting &setting) = 0; - friend class Setting; + friend class Setting; private: - QMap> m_settings; + QMap> m_settings; protected: - bool m_suspendSave = false; - bool m_doSave = false; + bool m_suspendSave = false; + bool m_doSave = false; }; diff --git a/api/logic/status/StatusChecker.cpp b/api/logic/status/StatusChecker.cpp index bff9fda9..f18ed364 100644 --- a/api/logic/status/StatusChecker.cpp +++ b/api/logic/status/StatusChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,121 +28,121 @@ StatusChecker::StatusChecker() void StatusChecker::timerEvent(QTimerEvent *e) { - QObject::timerEvent(e); - reloadStatus(); + QObject::timerEvent(e); + reloadStatus(); } void StatusChecker::reloadStatus() { - if (isLoadingStatus()) - { - // qDebug() << "Ignored request to reload status. Currently reloading already."; - return; - } - - // qDebug() << "Reloading status."; - - NetJob* job = new NetJob("Status JSON"); - job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink)); - QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); - QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); - m_statusNetJob.reset(job); - emit statusLoading(true); - job->start(); + if (isLoadingStatus()) + { + // qDebug() << "Ignored request to reload status. Currently reloading already."; + return; + } + + // qDebug() << "Reloading status."; + + NetJob* job = new NetJob("Status JSON"); + job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink)); + QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished); + QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed); + m_statusNetJob.reset(job); + emit statusLoading(true); + job->start(); } void StatusChecker::statusDownloadFinished() { - qDebug() << "Finished loading status JSON."; - m_statusEntries.clear(); - m_statusNetJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError); - - if (jsonError.error != QJsonParseError::NoError) - { - fail("Error parsing status JSON:" + jsonError.errorString()); - return; - } - - if (!jsonDoc.isArray()) - { - fail("Error parsing status JSON: JSON root is not an array"); - return; - } - - QJsonArray root = jsonDoc.array(); - - for(auto status = root.begin(); status != root.end(); ++status) - { - QVariantMap map = (*status).toObject().toVariantMap(); - - for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) - { - QString key = iter.key(); - QVariant value = iter.value(); - - if(value.type() == QVariant::Type::String) - { - m_statusEntries.insert(key, value.toString()); - //qDebug() << "Status JSON object: " << key << m_statusEntries[key]; - } - else - { - fail("Malformed status JSON: expected status type to be a string."); - return; - } - } - } - - succeed(); + qDebug() << "Finished loading status JSON."; + m_statusEntries.clear(); + m_statusNetJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(dataSink, &jsonError); + + if (jsonError.error != QJsonParseError::NoError) + { + fail("Error parsing status JSON:" + jsonError.errorString()); + return; + } + + if (!jsonDoc.isArray()) + { + fail("Error parsing status JSON: JSON root is not an array"); + return; + } + + QJsonArray root = jsonDoc.array(); + + for(auto status = root.begin(); status != root.end(); ++status) + { + QVariantMap map = (*status).toObject().toVariantMap(); + + for (QVariantMap::const_iterator iter = map.begin(); iter != map.end(); ++iter) + { + QString key = iter.key(); + QVariant value = iter.value(); + + if(value.type() == QVariant::Type::String) + { + m_statusEntries.insert(key, value.toString()); + //qDebug() << "Status JSON object: " << key << m_statusEntries[key]; + } + else + { + fail("Malformed status JSON: expected status type to be a string."); + return; + } + } + } + + succeed(); } void StatusChecker::statusDownloadFailed(QString reason) { - fail(tr("Failed to load status JSON:\n%1").arg(reason)); + fail(tr("Failed to load status JSON:\n%1").arg(reason)); } QMap StatusChecker::getStatusEntries() const { - return m_statusEntries; + return m_statusEntries; } bool StatusChecker::isLoadingStatus() const { - return m_statusNetJob.get() != nullptr; + return m_statusNetJob.get() != nullptr; } QString StatusChecker::getLastLoadErrorMsg() const { - return m_lastLoadError; + return m_lastLoadError; } void StatusChecker::succeed() { - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = ""; - qDebug() << "Status loading succeeded."; - m_statusNetJob.reset(); - emit statusLoading(false); + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } + m_lastLoadError = ""; + qDebug() << "Status loading succeeded."; + m_statusNetJob.reset(); + emit statusLoading(false); } void StatusChecker::fail(const QString& errorMsg) { - if(m_prevEntries != m_statusEntries) - { - emit statusChanged(m_statusEntries); - m_prevEntries = m_statusEntries; - } - m_lastLoadError = errorMsg; - qDebug() << "Failed to load status:" << errorMsg; - m_statusNetJob.reset(); - emit statusLoading(false); + if(m_prevEntries != m_statusEntries) + { + emit statusChanged(m_statusEntries); + m_prevEntries = m_statusEntries; + } + m_lastLoadError = errorMsg; + qDebug() << "Failed to load status:" << errorMsg; + m_statusNetJob.reset(); + emit statusLoading(false); } diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h index f19aba9a..829b139d 100644 --- a/api/logic/status/StatusChecker.h +++ b/api/logic/status/StatusChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,36 +25,36 @@ class MULTIMC_LOGIC_EXPORT StatusChecker : public QObject { - Q_OBJECT + Q_OBJECT public: /* con/des */ - StatusChecker(); + StatusChecker(); public: /* methods */ - QString getLastLoadErrorMsg() const; - bool isLoadingStatus() const; - QMap getStatusEntries() const; + QString getLastLoadErrorMsg() const; + bool isLoadingStatus() const; + QMap getStatusEntries() const; signals: - void statusLoading(bool loading); - void statusChanged(QMap newStatus); + void statusLoading(bool loading); + void statusChanged(QMap newStatus); public slots: - void reloadStatus(); + void reloadStatus(); protected: /* methods */ - virtual void timerEvent(QTimerEvent *); + virtual void timerEvent(QTimerEvent *); protected slots: - void statusDownloadFinished(); - void statusDownloadFailed(QString reason); - void succeed(); - void fail(const QString& errorMsg); + void statusDownloadFinished(); + void statusDownloadFailed(QString reason); + void succeed(); + void fail(const QString& errorMsg); protected: /* data */ - QMap m_prevEntries; - QMap m_statusEntries; - NetJobPtr m_statusNetJob; - QString m_lastLoadError; - QByteArray dataSink; + QMap m_prevEntries; + QMap m_statusEntries; + NetJobPtr m_statusNetJob; + QString m_lastLoadError; + QByteArray dataSink; }; diff --git a/api/logic/tasks/SequentialTask.cpp b/api/logic/tasks/SequentialTask.cpp index ac0e7820..d0777132 100644 --- a/api/logic/tasks/SequentialTask.cpp +++ b/api/logic/tasks/SequentialTask.cpp @@ -6,50 +6,50 @@ SequentialTask::SequentialTask(QObject *parent) : Task(parent), m_currentIndex(- void SequentialTask::addTask(std::shared_ptr task) { - m_queue.append(task); + m_queue.append(task); } void SequentialTask::executeTask() { - m_currentIndex = -1; - startNext(); + m_currentIndex = -1; + startNext(); } void SequentialTask::startNext() { - if (m_currentIndex != -1) - { - std::shared_ptr previous = m_queue[m_currentIndex]; - disconnect(previous.get(), 0, this, 0); - } - m_currentIndex++; - if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) - { - emitSucceeded(); - return; - } - std::shared_ptr next = m_queue[m_currentIndex]; - connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); - connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); - connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); - connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); - next->start(); + if (m_currentIndex != -1) + { + std::shared_ptr previous = m_queue[m_currentIndex]; + disconnect(previous.get(), 0, this, 0); + } + m_currentIndex++; + if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) + { + emitSucceeded(); + return; + } + std::shared_ptr next = m_queue[m_currentIndex]; + connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); + connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); + connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); + connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); + next->start(); } void SequentialTask::subTaskFailed(const QString &msg) { - emitFailed(msg); + emitFailed(msg); } void SequentialTask::subTaskStatus(const QString &msg) { - setStatus(msg); + setStatus(msg); } void SequentialTask::subTaskProgress(qint64 current, qint64 total) { - if(total == 0) - { - setProgress(0, 100); - return; - } - setProgress(current, total); + if(total == 0) + { + setProgress(0, 100); + return; + } + setProgress(current, total); } diff --git a/api/logic/tasks/SequentialTask.h b/api/logic/tasks/SequentialTask.h index 69031095..2ca77c00 100644 --- a/api/logic/tasks/SequentialTask.h +++ b/api/logic/tasks/SequentialTask.h @@ -9,23 +9,24 @@ class MULTIMC_LOGIC_EXPORT SequentialTask : public Task { - Q_OBJECT + Q_OBJECT public: - explicit SequentialTask(QObject *parent = 0); + explicit SequentialTask(QObject *parent = 0); + virtual ~SequentialTask() {}; - void addTask(std::shared_ptr task); + void addTask(std::shared_ptr task); protected: - void executeTask(); + void executeTask(); private slots: - void startNext(); - void subTaskFailed(const QString &msg); - void subTaskStatus(const QString &msg); - void subTaskProgress(qint64 current, qint64 total); + void startNext(); + void subTaskFailed(const QString &msg); + void subTaskStatus(const QString &msg); + void subTaskProgress(qint64 current, qint64 total); private: - QQueue > m_queue; - int m_currentIndex; + QQueue > m_queue; + int m_currentIndex; }; diff --git a/api/logic/tasks/Task.cpp b/api/logic/tasks/Task.cpp index 2523aeb2..accb39b6 100644 --- a/api/logic/tasks/Task.cpp +++ b/api/logic/tasks/Task.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,124 +23,146 @@ Task::Task(QObject *parent) : QObject(parent) void Task::setStatus(const QString &new_status) { - if(m_status != new_status) - { - m_status = new_status; - emit status(m_status); - } + if(m_status != new_status) + { + m_status = new_status; + emit status(m_status); + } } void Task::setProgress(qint64 current, qint64 total) { - m_progress = current; - m_progressTotal = total; - emit progress(m_progress, m_progressTotal); + m_progress = current; + m_progressTotal = total; + emit progress(m_progress, m_progressTotal); } void Task::start() { - m_running = true; - emit started(); - qDebug() << "Task" << describe() << "started"; - executeTask(); + switch(m_state) + { + case State::Inactive: + { + qDebug() << "Task" << describe() << "starting for the first time"; + break; + } + case State::AbortedByUser: + { + qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + break; + } + case State::Failed: + { + qDebug() << "Task" << describe() << "restarting for after failing at first"; + break; + } + case State::Succeeded: + { + qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + break; + } + case State::Running: + { + qWarning() << "MultiMC tried to start task" << describe() << "while it was already running!"; + return; + } + } + // NOTE: only fall thorugh to here in end states + m_state = State::Running; + emit started(); + executeTask(); } void Task::emitFailed(QString reason) { - // Don't fail twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; - return; - } - m_running = false; - m_finished = true; - m_succeeded = false; - m_failReason = reason; - qCritical() << "Task" << describe() << "failed: " << reason; - emit failed(reason); - emit finished(); + // Don't fail twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason; + return; + } + m_state = State::Failed; + m_failReason = reason; + qCritical() << "Task" << describe() << "failed: " << reason; + emit failed(reason); + emit finished(); } void Task::emitAborted() { - // Don't abort twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "aborted while not running!!!!"; - return; - } - m_running = false; - m_finished = true; - m_succeeded = false; - m_failReason = "Aborted."; - qDebug() << "Task" << describe() << "aborted."; - emit failed(m_failReason); - emit finished(); + // Don't abort twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "aborted while not running!!!!"; + return; + } + m_state = State::AbortedByUser; + m_failReason = "Aborted."; + qDebug() << "Task" << describe() << "aborted."; + emit failed(m_failReason); + emit finished(); } void Task::emitSucceeded() { - // Don't succeed twice. - if (!m_running) - { - qCritical() << "Task" << describe() << "succeeded while not running!!!!"; - return; - } - m_running = false; - m_finished = true; - m_succeeded = true; - qDebug() << "Task" << describe() << "succeeded"; - emit succeeded(); - emit finished(); + // Don't succeed twice. + if (!isRunning()) + { + qCritical() << "Task" << describe() << "succeeded while not running!!!!"; + return; + } + m_state = State::Succeeded; + qDebug() << "Task" << describe() << "succeeded"; + emit succeeded(); + emit finished(); } QString Task::describe() { - QString outStr; - QTextStream out(&outStr); - out << metaObject()->className() << QChar('('); - auto name = objectName(); - if(name.isEmpty()) - { - out << QString("0x%1").arg((quintptr)this, 0, 16); - } - else - { - out << name; - } - out << QChar(')'); - out.flush(); - return outStr; + QString outStr; + QTextStream out(&outStr); + out << metaObject()->className() << QChar('('); + auto name = objectName(); + if(name.isEmpty()) + { + out << QString("0x%1").arg((quintptr)this, 0, 16); + } + else + { + out << name; + } + out << QChar(')'); + out.flush(); + return outStr; } bool Task::isRunning() const { - return m_running; + return m_state == State::Running; } bool Task::isFinished() const { - return m_finished; + return m_state != State::Running && m_state != State::Inactive; } bool Task::wasSuccessful() const { - return m_succeeded; + return m_state == State::Succeeded; } QString Task::failReason() const { - return m_failReason; + return m_failReason; } void Task::logWarning(const QString& line) { - qWarning() << line; - m_Warnings.append(line); + qWarning() << line; + m_Warnings.append(line); } QStringList Task::warnings() const { - return m_Warnings; + return m_Warnings; } diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h index 643f8510..72bfeca8 100644 --- a/api/logic/tasks/Task.h +++ b/api/logic/tasks/Task.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,78 +23,86 @@ class MULTIMC_LOGIC_EXPORT Task : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit Task(QObject *parent = 0); - virtual ~Task() {}; + enum class State + { + Inactive, + Running, + Succeeded, + Failed, + AbortedByUser + }; - bool isRunning() const; - bool isFinished() const; - bool wasSuccessful() const; +public: + explicit Task(QObject *parent = 0); + virtual ~Task() {}; + + bool isRunning() const; + bool isFinished() const; + bool wasSuccessful() const; - /*! - * Returns the string that was passed to emitFailed as the error message when the task failed. - * If the task hasn't failed, returns an empty string. - */ - QString failReason() const; + /*! + * Returns the string that was passed to emitFailed as the error message when the task failed. + * If the task hasn't failed, returns an empty string. + */ + QString failReason() const; - virtual QStringList warnings() const; + virtual QStringList warnings() const; - virtual bool canAbort() const { return false; } + virtual bool canAbort() const { return false; } - QString getStatus() - { - return m_status; - } + QString getStatus() + { + return m_status; + } - qint64 getProgress() - { - return m_progress; - } + qint64 getProgress() + { + return m_progress; + } - qint64 getTotalProgress() - { - return m_progressTotal; - } + qint64 getTotalProgress() + { + return m_progressTotal; + } protected: - void logWarning(const QString & line); + void logWarning(const QString & line); private: - QString describe(); + QString describe(); signals: - void started(); - void progress(qint64 current, qint64 total); - void finished(); - void succeeded(); - void failed(QString reason); - void status(QString status); + void started(); + void progress(qint64 current, qint64 total); + void finished(); + void succeeded(); + void failed(QString reason); + void status(QString status); public slots: - virtual void start(); - virtual bool abort() { return false; }; + virtual void start(); + virtual bool abort() { return false; }; protected: - virtual void executeTask() = 0; + virtual void executeTask() = 0; protected slots: - virtual void emitSucceeded(); - virtual void emitAborted(); - virtual void emitFailed(QString reason); + virtual void emitSucceeded(); + virtual void emitAborted(); + virtual void emitFailed(QString reason); public slots: - void setStatus(const QString &status); - void setProgress(qint64 current, qint64 total); + void setStatus(const QString &status); + void setProgress(qint64 current, qint64 total); private: - bool m_running = false; - bool m_finished = false; - bool m_succeeded = false; - QStringList m_Warnings; - QString m_failReason = ""; - QString m_status; - int m_progress = 0; - int m_progressTotal = 100; + State m_state = State::Inactive; + QStringList m_Warnings; + QString m_failReason = ""; + QString m_status; + int m_progress = 0; + int m_progressTotal = 100; }; diff --git a/api/logic/tools/BaseExternalTool.cpp b/api/logic/tools/BaseExternalTool.cpp index 2b97c3c9..38d81788 100644 --- a/api/logic/tools/BaseExternalTool.cpp +++ b/api/logic/tools/BaseExternalTool.cpp @@ -10,7 +10,7 @@ #include "BaseInstance.h" BaseExternalTool::BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : QObject(parent), m_instance(instance), globalSettings(settings) + : QObject(parent), m_instance(instance), globalSettings(settings) { } @@ -19,14 +19,14 @@ BaseExternalTool::~BaseExternalTool() } BaseDetachedTool::BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) + : BaseExternalTool(settings, instance, parent) { } void BaseDetachedTool::run() { - runImpl(); + runImpl(); } @@ -35,7 +35,7 @@ BaseExternalToolFactory::~BaseExternalToolFactory() } BaseDetachedTool *BaseDetachedToolFactory::createDetachedTool(InstancePtr instance, - QObject *parent) + QObject *parent) { - return qobject_cast(createTool(instance, parent)); + return qobject_cast(createTool(instance, parent)); } diff --git a/api/logic/tools/BaseExternalTool.h b/api/logic/tools/BaseExternalTool.h index fe1b5dc6..b393b9ef 100644 --- a/api/logic/tools/BaseExternalTool.h +++ b/api/logic/tools/BaseExternalTool.h @@ -11,50 +11,50 @@ class QProcess; class MULTIMC_LOGIC_EXPORT BaseExternalTool : public QObject { - Q_OBJECT + Q_OBJECT public: - explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); - virtual ~BaseExternalTool(); + explicit BaseExternalTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + virtual ~BaseExternalTool(); protected: - InstancePtr m_instance; - SettingsObjectPtr globalSettings; + InstancePtr m_instance; + SettingsObjectPtr globalSettings; }; class MULTIMC_LOGIC_EXPORT BaseDetachedTool : public BaseExternalTool { - Q_OBJECT + Q_OBJECT public: - explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + explicit BaseDetachedTool(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); public slots: - void run(); + void run(); protected: - virtual void runImpl() = 0; + virtual void runImpl() = 0; }; class MULTIMC_LOGIC_EXPORT BaseExternalToolFactory { public: - virtual ~BaseExternalToolFactory(); + virtual ~BaseExternalToolFactory(); - virtual QString name() const = 0; + virtual QString name() const = 0; - virtual void registerSettings(SettingsObjectPtr settings) = 0; + virtual void registerSettings(SettingsObjectPtr settings) = 0; - virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0; + virtual BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) = 0; - virtual bool check(QString *error) = 0; - virtual bool check(const QString &path, QString *error) = 0; + virtual bool check(QString *error) = 0; + virtual bool check(const QString &path, QString *error) = 0; protected: - SettingsObjectPtr globalSettings; + SettingsObjectPtr globalSettings; }; class MULTIMC_LOGIC_EXPORT BaseDetachedToolFactory : public BaseExternalToolFactory { public: - virtual BaseDetachedTool *createDetachedTool(InstancePtr instance, QObject *parent = 0); + virtual BaseDetachedTool *createDetachedTool(InstancePtr instance, QObject *parent = 0); }; diff --git a/api/logic/tools/BaseProfiler.cpp b/api/logic/tools/BaseProfiler.cpp index 5ff0fa44..300d1a73 100644 --- a/api/logic/tools/BaseProfiler.cpp +++ b/api/logic/tools/BaseProfiler.cpp @@ -1,35 +1,36 @@ #include "BaseProfiler.h" +#include "QObjectPtr.h" #include BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseExternalTool(settings, instance, parent) + : BaseExternalTool(settings, instance, parent) { } -void BaseProfiler::beginProfiling(std::shared_ptr process) +void BaseProfiler::beginProfiling(shared_qobject_ptr process) { - beginProfilingImpl(process); + beginProfilingImpl(process); } void BaseProfiler::abortProfiling() { - abortProfilingImpl(); + abortProfilingImpl(); } void BaseProfiler::abortProfilingImpl() { - if (!m_profilerProcess) - { - return; - } - m_profilerProcess->terminate(); - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - emit abortLaunch(tr("Profiler aborted")); + if (!m_profilerProcess) + { + return; + } + m_profilerProcess->terminate(); + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + emit abortLaunch(tr("Profiler aborted")); } BaseProfiler *BaseProfilerFactory::createProfiler(InstancePtr instance, QObject *parent) { - return qobject_cast(createTool(instance, parent)); + return qobject_cast(createTool(instance, parent)); } diff --git a/api/logic/tools/BaseProfiler.h b/api/logic/tools/BaseProfiler.h index 3340b7e4..da817f52 100644 --- a/api/logic/tools/BaseProfiler.h +++ b/api/logic/tools/BaseProfiler.h @@ -1,6 +1,7 @@ #pragma once #include "BaseExternalTool.h" +#include "QObjectPtr.h" #include "multimc_logic_export.h" @@ -11,28 +12,28 @@ class QProcess; class MULTIMC_LOGIC_EXPORT BaseProfiler : public BaseExternalTool { - Q_OBJECT + Q_OBJECT public: - explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + explicit BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); public slots: - void beginProfiling(std::shared_ptr process); - void abortProfiling(); + void beginProfiling(shared_qobject_ptr process); + void abortProfiling(); protected: - QProcess *m_profilerProcess; + QProcess *m_profilerProcess; - virtual void beginProfilingImpl(std::shared_ptr process) = 0; - virtual void abortProfilingImpl(); + virtual void beginProfilingImpl(shared_qobject_ptr process) = 0; + virtual void abortProfilingImpl(); signals: - void readyToLaunch(const QString &message); - void abortLaunch(const QString &message); + void readyToLaunch(const QString &message); + void abortLaunch(const QString &message); }; class MULTIMC_LOGIC_EXPORT BaseProfilerFactory : public BaseExternalToolFactory { public: - virtual BaseProfiler *createProfiler(InstancePtr instance, QObject *parent = 0); + virtual BaseProfiler *createProfiler(InstancePtr instance, QObject *parent = 0); }; diff --git a/api/logic/tools/JProfiler.cpp b/api/logic/tools/JProfiler.cpp index a0e3c895..1dc0d109 100644 --- a/api/logic/tools/JProfiler.cpp +++ b/api/logic/tools/JProfiler.cpp @@ -8,109 +8,109 @@ class JProfiler : public BaseProfiler { - Q_OBJECT + Q_OBJECT public: - JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + JProfiler(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); + void profilerStarted(); + void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(std::shared_ptr process); + void beginProfilingImpl(shared_qobject_ptr process); private: - int listeningPort = 0; + int listeningPort = 0; }; JProfiler::JProfiler(SettingsObjectPtr settings, InstancePtr instance, - QObject *parent) - : BaseProfiler(settings, instance, parent) + QObject *parent) + : BaseProfiler(settings, instance, parent) { } void JProfiler::profilerStarted() { - emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort)); + emit readyToLaunch(tr("Listening on port: %1").arg(listeningPort)); } void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status) { - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } + if (status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } } -void JProfiler::beginProfilingImpl(std::shared_ptr process) +void JProfiler::beginProfilingImpl(shared_qobject_ptr process) { - listeningPort = globalSettings->get("JProfilerPort").toInt(); - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "-d", QString::number(process->pid()), - "--gui", - "-p", QString::number(listeningPort) - }; - auto basePath = globalSettings->get("JProfilerPath").toString(); + listeningPort = globalSettings->get("JProfilerPort").toInt(); + QProcess *profiler = new QProcess(this); + QStringList profilerArgs = + { + "-d", QString::number(process->pid()), + "--gui", + "-p", QString::number(listeningPort) + }; + auto basePath = globalSettings->get("JProfilerPath").toString(); #ifdef Q_OS_WIN - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable.exe"); + QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable.exe"); #else - QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable"); + QString profilerProgram = QDir(basePath).absoluteFilePath("bin/jpenable"); #endif - profiler->setArguments(profilerArgs); - profiler->setProgram(profilerProgram); + profiler->setArguments(profilerArgs); + profiler->setProgram(profilerProgram); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); + connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - m_profilerProcess = profiler; - profiler->start(); + m_profilerProcess = profiler; + profiler->start(); } void JProfilerFactory::registerSettings(SettingsObjectPtr settings) { - settings->registerSetting("JProfilerPath"); - settings->registerSetting("JProfilerPort", 42042); - globalSettings = settings; + settings->registerSetting("JProfilerPath"); + settings->registerSetting("JProfilerPort", 42042); + globalSettings = settings; } BaseExternalTool *JProfilerFactory::createTool(InstancePtr instance, QObject *parent) { - return new JProfiler(globalSettings, instance, parent); + return new JProfiler(globalSettings, instance, parent); } bool JProfilerFactory::check(QString *error) { - return check(globalSettings->get("JProfilerPath").toString(), error); + return check(globalSettings->get("JProfilerPath").toString(), error); } bool JProfilerFactory::check(const QString &path, QString *error) { - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QDir dir(path); - if (!dir.exists()) - { - *error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("bin") || !(dir.exists("bin/jprofiler") || dir.exists("bin/jprofiler.exe")) || !dir.exists("bin/agent.jar")) - { - *error = QObject::tr("Invalid JProfiler install"); - return false; - } - return true; + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QDir dir(path); + if (!dir.exists()) + { + *error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("bin") || !(dir.exists("bin/jprofiler") || dir.exists("bin/jprofiler.exe")) || !dir.exists("bin/agent.jar")) + { + *error = QObject::tr("Invalid JProfiler install"); + return false; + } + return true; } #include "JProfiler.moc" diff --git a/api/logic/tools/JProfiler.h b/api/logic/tools/JProfiler.h index d658d6c2..f211ddbf 100644 --- a/api/logic/tools/JProfiler.h +++ b/api/logic/tools/JProfiler.h @@ -7,9 +7,9 @@ class MULTIMC_LOGIC_EXPORT JProfilerFactory : public BaseProfilerFactory { public: - QString name() const override { return "JProfiler"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; + QString name() const override { return "JProfiler"; } + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; }; diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp index 8fdb594f..b1acc3c0 100644 --- a/api/logic/tools/JVisualVM.cpp +++ b/api/logic/tools/JVisualVM.cpp @@ -9,96 +9,96 @@ class JVisualVM : public BaseProfiler { - Q_OBJECT + Q_OBJECT public: - JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); + JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent = 0); private slots: - void profilerStarted(); - void profilerFinished(int exit, QProcess::ExitStatus status); + void profilerStarted(); + void profilerFinished(int exit, QProcess::ExitStatus status); protected: - void beginProfilingImpl(std::shared_ptr process); + void beginProfilingImpl(shared_qobject_ptr process); }; JVisualVM::JVisualVM(SettingsObjectPtr settings, InstancePtr instance, QObject *parent) - : BaseProfiler(settings, instance, parent) + : BaseProfiler(settings, instance, parent) { } void JVisualVM::profilerStarted() { - emit readyToLaunch(tr("JVisualVM started")); + emit readyToLaunch(tr("JVisualVM started")); } void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status) { - if (status == QProcess::CrashExit) - { - emit abortLaunch(tr("Profiler aborted")); - } - if (m_profilerProcess) - { - m_profilerProcess->deleteLater(); - m_profilerProcess = 0; - } + if (status == QProcess::CrashExit) + { + emit abortLaunch(tr("Profiler aborted")); + } + if (m_profilerProcess) + { + m_profilerProcess->deleteLater(); + m_profilerProcess = 0; + } } -void JVisualVM::beginProfilingImpl(std::shared_ptr process) +void JVisualVM::beginProfilingImpl(shared_qobject_ptr process) { - QProcess *profiler = new QProcess(this); - QStringList profilerArgs = - { - "--openpid", QString::number(process->pid()) - }; - auto programPath = globalSettings->get("JVisualVMPath").toString(); + QProcess *profiler = new QProcess(this); + QStringList profilerArgs = + { + "--openpid", QString::number(process->pid()) + }; + auto programPath = globalSettings->get("JVisualVMPath").toString(); - profiler->setArguments(profilerArgs); - profiler->setProgram(programPath); + profiler->setArguments(profilerArgs); + profiler->setProgram(programPath); - connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); - connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); + connect(profiler, SIGNAL(started()), SLOT(profilerStarted())); + connect(profiler, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(profilerFinished(int,QProcess::ExitStatus))); - profiler->start(); - m_profilerProcess = profiler; + profiler->start(); + m_profilerProcess = profiler; } void JVisualVMFactory::registerSettings(SettingsObjectPtr settings) { - QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); - if (defaultValue.isNull()) - { - defaultValue = QStandardPaths::findExecutable("visualvm"); - } - settings->registerSetting("JVisualVMPath", defaultValue); - globalSettings = settings; + QString defaultValue = QStandardPaths::findExecutable("jvisualvm"); + if (defaultValue.isNull()) + { + defaultValue = QStandardPaths::findExecutable("visualvm"); + } + settings->registerSetting("JVisualVMPath", defaultValue); + globalSettings = settings; } BaseExternalTool *JVisualVMFactory::createTool(InstancePtr instance, QObject *parent) { - return new JVisualVM(globalSettings, instance, parent); + return new JVisualVM(globalSettings, instance, parent); } bool JVisualVMFactory::check(QString *error) { - return check(globalSettings->get("JVisualVMPath").toString(), error); + return check(globalSettings->get("JVisualVMPath").toString(), error); } bool JVisualVMFactory::check(const QString &path, QString *error) { - if (path.isEmpty()) - { - *error = QObject::tr("Empty path"); - return false; - } - QFileInfo finfo(path); - if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) - { - *error = QObject::tr("Invalid path to JVisualVM"); - return false; - } - return true; + if (path.isEmpty()) + { + *error = QObject::tr("Empty path"); + return false; + } + QFileInfo finfo(path); + if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm")) + { + *error = QObject::tr("Invalid path to JVisualVM"); + return false; + } + return true; } #include "JVisualVM.moc" diff --git a/api/logic/tools/JVisualVM.h b/api/logic/tools/JVisualVM.h index 0674da13..91d48d94 100644 --- a/api/logic/tools/JVisualVM.h +++ b/api/logic/tools/JVisualVM.h @@ -7,9 +7,9 @@ class MULTIMC_LOGIC_EXPORT JVisualVMFactory : public BaseProfilerFactory { public: - QString name() const override { return "JVisualVM"; } - void registerSettings(SettingsObjectPtr settings) override; - BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; - bool check(QString *error) override; - bool check(const QString &path, QString *error) override; + QString name() const override { return "JVisualVM"; } + void registerSettings(SettingsObjectPtr settings) override; + BaseExternalTool *createTool(InstancePtr instance, QObject *parent = 0) override; + bool check(QString *error) override; + bool check(const QString &path, QString *error) override; }; diff --git a/api/logic/tools/MCEditTool.cpp b/api/logic/tools/MCEditTool.cpp index 74715d3f..880327c7 100644 --- a/api/logic/tools/MCEditTool.cpp +++ b/api/logic/tools/MCEditTool.cpp @@ -10,68 +10,68 @@ MCEditTool::MCEditTool(SettingsObjectPtr settings) { - settings->registerSetting("MCEditPath"); - m_settings = settings; + settings->registerSetting("MCEditPath"); + m_settings = settings; } void MCEditTool::setPath(QString& path) { - m_settings->set("MCEditPath", path); + m_settings->set("MCEditPath", path); } QString MCEditTool::path() const { - return m_settings->get("MCEditPath").toString(); + return m_settings->get("MCEditPath").toString(); } bool MCEditTool::check(const QString& toolPath, QString& error) { - if (toolPath.isEmpty()) - { - error = QObject::tr("Path is empty"); - return false; - } - const QDir dir(toolPath); - if (!dir.exists()) - { - error = QObject::tr("Path does not exist"); - return false; - } - if (!dir.exists("mcedit.sh") && !dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents") && !dir.exists("mcedit2.exe")) - { - error = QObject::tr("Path does not seem to be a MCEdit path"); - return false; - } - return true; + if (toolPath.isEmpty()) + { + error = QObject::tr("Path is empty"); + return false; + } + const QDir dir(toolPath); + if (!dir.exists()) + { + error = QObject::tr("Path does not exist"); + return false; + } + if (!dir.exists("mcedit.sh") && !dir.exists("mcedit.py") && !dir.exists("mcedit.exe") && !dir.exists("Contents") && !dir.exists("mcedit2.exe")) + { + error = QObject::tr("Path does not seem to be a MCEdit path"); + return false; + } + return true; } QString MCEditTool::getProgramPath() { #ifdef Q_OS_OSX - return path(); + return path(); #else - const QString mceditPath = path(); - QDir mceditDir(mceditPath); + const QString mceditPath = path(); + QDir mceditDir(mceditPath); #ifdef Q_OS_LINUX - if (mceditDir.exists("mcedit.sh")) - { - return mceditDir.absoluteFilePath("mcedit.sh"); - } - else if (mceditDir.exists("mcedit.py")) - { - return mceditDir.absoluteFilePath("mcedit.py"); - } - return QString(); + if (mceditDir.exists("mcedit.sh")) + { + return mceditDir.absoluteFilePath("mcedit.sh"); + } + else if (mceditDir.exists("mcedit.py")) + { + return mceditDir.absoluteFilePath("mcedit.py"); + } + return QString(); #elif defined(Q_OS_WIN32) - if (mceditDir.exists("mcedit.exe")) - { - return mceditDir.absoluteFilePath("mcedit.exe"); - } - else if (mceditDir.exists("mcedit2.exe")) - { - return mceditDir.absoluteFilePath("mcedit2.exe"); - } - return QString(); + if (mceditDir.exists("mcedit.exe")) + { + return mceditDir.absoluteFilePath("mcedit.exe"); + } + else if (mceditDir.exists("mcedit2.exe")) + { + return mceditDir.absoluteFilePath("mcedit2.exe"); + } + return QString(); #endif #endif } diff --git a/api/logic/tools/MCEditTool.h b/api/logic/tools/MCEditTool.h index 51feb1fe..1465494e 100644 --- a/api/logic/tools/MCEditTool.h +++ b/api/logic/tools/MCEditTool.h @@ -7,11 +7,11 @@ class MULTIMC_LOGIC_EXPORT MCEditTool { public: - MCEditTool(SettingsObjectPtr settings); - void setPath(QString & path); - QString path() const; - bool check(const QString &toolPath, QString &error); - QString getProgramPath(); + MCEditTool(SettingsObjectPtr settings); + void setPath(QString & path); + QString path() const; + bool check(const QString &toolPath, QString &error); + QString getProgramPath(); private: - SettingsObjectPtr m_settings; + SettingsObjectPtr m_settings; }; diff --git a/api/logic/translations/POTranslator.cpp b/api/logic/translations/POTranslator.cpp new file mode 100644 index 00000000..1ffcb9a4 --- /dev/null +++ b/api/logic/translations/POTranslator.cpp @@ -0,0 +1,373 @@ +#include "POTranslator.h" + +#include +#include "FileSystem.h" + +struct POEntry +{ + QString text; + bool fuzzy; +}; + +struct POTranslatorPrivate +{ + QString filename; + QHash mapping; + QHash mapping_disambiguatrion; + bool loaded = false; + + void reload(); +}; + +class ParserArray : public QByteArray +{ +public: + ParserArray(const QByteArray &in) : QByteArray(in) + { + } + bool chomp(const char * data, int length) + { + if(startsWith(data)) + { + remove(0, length); + return true; + } + return false; + } + bool chompString(QByteArray & appendHere) + { + QByteArray msg; + bool escape = false; + if(size() < 2) + { + qDebug() << "String fragment is too short"; + return false; + } + if(!startsWith('"')) + { + qDebug() << "String fragment does not start with \""; + return false; + } + if(!endsWith('"')) + { + qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1); + return false; + } + for(int i = 1; i < size() - 1; i++) + { + char c = operator[](i); + if(escape) + { + switch(c) + { + case 'r': + msg += '\r'; + break; + case 'n': + msg += '\n'; + break; + case 't': + msg += '\t'; + break; + case 'v': + msg += '\v'; + break; + case 'a': + msg += '\a'; + break; + case 'b': + msg += '\b'; + break; + case 'f': + msg += '\f'; + break; + case '"': + msg += '"'; + break; + case '\\': + msg.append('\\'); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int octal_start = i; + while ((c = operator[](i)) >= '0' && c <= '7') + { + i++; + if (i == length() - 1) + { + qDebug() << "Something went bad while parsing an octal escape string..."; + return false; + } + } + msg += mid(octal_start, i - octal_start).toUInt(0, 8); + break; + } + case 'x': + { + // chomp the 'x' + i++; + int hex_start = i; + while (isxdigit(operator[](i))) + { + i++; + if (i == length() - 1) + { + qDebug() << "Something went bad while parsing a hex escape string..."; + return false; + } + } + msg += mid(hex_start, i - hex_start).toUInt(0, 16); + break; + } + default: + { + qDebug() << "Invalid escape sequence character:" << c; + return false; + } + } + escape = false; + } + else if(c == '\\') + { + escape = true; + } + else + { + msg += c; + } + } + if(escape) + { + qDebug() << "Unterminated escape sequence..."; + return false; + } + appendHere += msg; + return true; + } +}; + +void POTranslatorPrivate::reload() +{ + QFile file(filename); + if(!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text)) + { + qDebug() << "Failed to open PO file:" << filename; + return; + } + + QByteArray context; + QByteArray disambiguation; + QByteArray id; + QByteArray str; + bool fuzzy = false; + bool nextFuzzy = false; + + enum class Mode + { + First, + MessageContext, + MessageId, + MessageString + } mode = Mode::First; + + int lineNumber = 0; + QHash newMapping; + QHash newMapping_disambiguation; + auto endEntry = [&]() { + auto strStr = QString::fromUtf8(str); + // NOTE: PO header has empty id. We skip it. + if(!id.isEmpty()) + { + auto normalKey = context + "|" + id; + newMapping.insert(normalKey, {strStr, fuzzy}); + if(!disambiguation.isEmpty()) + { + auto disambiguationKey = context + "|" + id + "@" + disambiguation; + newMapping_disambiguation.insert(disambiguationKey, {strStr, fuzzy}); + } + } + context.clear(); + disambiguation.clear(); + id.clear(); + str.clear(); + fuzzy = nextFuzzy; + nextFuzzy = false; + }; + while (!file.atEnd()) + { + ParserArray line = file.readLine(); + if(line.endsWith('\n')) + { + line.resize(line.size() - 1); + } + if(line.endsWith('\r')) + { + line.resize(line.size() - 1); + } + + if(!line.size()) + { + // NIL + } + else if(line[0] == '#') + { + if(line.contains(", fuzzy")) + { + nextFuzzy = true; + } + } + else if(line.startsWith('"')) + { + QByteArray temp; + QByteArray *out = &temp; + + switch(mode) + { + case Mode::First: + qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber; + return; + case Mode::MessageString: + out = &str; + break; + case Mode::MessageContext: + out = &context; + break; + case Mode::MessageId: + out = &id; + break; + } + if(!line.chompString(*out)) + { + qDebug() << "Badly formatted string on line:" << lineNumber; + return; + } + } + else if(line.chomp("msgctxt ", 8)) + { + switch(mode) + { + case Mode::First: + break; + case Mode::MessageString: + endEntry(); + break; + case Mode::MessageContext: + case Mode::MessageId: + qDebug() << "Unexpected msgctxt line:" << lineNumber; + return; + } + if(line.chompString(context)) + { + auto parts = context.split('|'); + context = parts[0]; + if(parts.size() > 1 && !parts[1].isEmpty()) + { + disambiguation = parts[1]; + } + mode = Mode::MessageContext; + } + } + else if (line.chomp("msgid ", 6)) + { + switch(mode) + { + case Mode::MessageContext: + case Mode::First: + break; + case Mode::MessageString: + endEntry(); + break; + case Mode::MessageId: + qDebug() << "Unexpected msgid line:" << lineNumber; + return; + } + if(line.chompString(id)) + { + mode = Mode::MessageId; + } + } + else if (line.chomp("msgstr ", 7)) + { + switch(mode) + { + case Mode::First: + case Mode::MessageString: + case Mode::MessageContext: + qDebug() << "Unexpected msgstr line:" << lineNumber; + return; + case Mode::MessageId: + break; + } + if(line.chompString(str)) + { + mode = Mode::MessageString; + } + } + else + { + qDebug() << "I did not understand line: " << lineNumber << ":" << QString::fromUtf8(line); + } + lineNumber++; + } + endEntry(); + mapping = std::move(newMapping); + mapping_disambiguatrion = std::move(newMapping_disambiguation); + loaded = true; +} + +POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent) +{ + d = new POTranslatorPrivate; + d->filename = filename; + d->reload(); +} + +QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const +{ + if(disambiguation) + { + auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation); + auto iter = d->mapping_disambiguatrion.find(disambiguationKey); + if(iter != d->mapping_disambiguatrion.end()) + { + auto & entry = *iter; + if(entry.text.isEmpty()) + { + qDebug() << "Translation entry has no content:" << disambiguationKey; + } + if(entry.fuzzy) + { + qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text; + } + return entry.text; + } + } + auto key = QByteArray(context) + "|" + QByteArray(sourceText); + auto iter = d->mapping.find(key); + if(iter != d->mapping.end()) + { + auto & entry = *iter; + if(entry.text.isEmpty()) + { + qDebug() << "Translation entry has no content:" << key; + } + if(entry.fuzzy) + { + qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text; + } + return entry.text; + } + return QString(); +} + +bool POTranslator::isEmpty() const +{ + return !d->loaded; +} diff --git a/api/logic/translations/POTranslator.h b/api/logic/translations/POTranslator.h new file mode 100644 index 00000000..6d518560 --- /dev/null +++ b/api/logic/translations/POTranslator.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct POTranslatorPrivate; + +class POTranslator : public QTranslator +{ + Q_OBJECT +public: + explicit POTranslator(const QString& filename, QObject * parent = nullptr); + QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override; + bool isEmpty() const override; +private: + POTranslatorPrivate * d; +}; diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp index 868aa98f..0d9eccbf 100644 --- a/api/logic/translations/TranslationsModel.cpp +++ b/api/logic/translations/TranslationsModel.cpp @@ -8,312 +8,642 @@ #include #include #include +#include #include #include +#include "Json.h" + +#include "POTranslator.h" const static QLatin1Literal defaultLangCode("en"); +enum class FileType +{ + NONE, + QM, + PO +}; + struct Language { - QString key; - QLocale locale; - bool updated; + Language() + { + updated = true; + } + Language(const QString & _key) + { + key = _key; + locale = QLocale(key); + updated = (key == defaultLangCode); + } + + float percentTranslated() const + { + if (total == 0) + { + return 100.0f; + } + return 100.0f * float(translated) / float(total); + } + + void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) + { + translated = _translated; + untranslated = _untranslated; + fuzzy = _fuzzy; + total = translated + untranslated + fuzzy; + } + + bool isOfSameNameAs(const Language& other) const + { + return key == other.key; + } + + bool isIdenticalTo(const Language& other) const + { + return + ( + key == other.key && + file_name == other.file_name && + file_size == other.file_size && + file_sha1 == other.file_sha1 && + translated == other.translated && + fuzzy == other.fuzzy && + total == other.fuzzy && + localFileType == other.localFileType + ); + } + + Language & apply(Language & other) + { + if(!isOfSameNameAs(other)) + { + return *this; + } + file_name = other.file_name; + file_size = other.file_size; + file_sha1 = other.file_sha1; + translated = other.translated; + fuzzy = other.fuzzy; + total = other.total; + localFileType = other.localFileType; + return *this; + } + + QString key; + QLocale locale; + bool updated; + + QString file_name = QString(); + std::size_t file_size = 0; + QString file_sha1 = QString(); + + unsigned translated = 0; + unsigned untranslated = 0; + unsigned fuzzy = 0; + unsigned total = 0; + + FileType localFileType = FileType::NONE; }; + + struct TranslationsModel::Private { - QDir m_dir; - - // initial state is just english - QVector m_languages = {{defaultLangCode, QLocale(defaultLangCode), false}}; - QString m_selectedLanguage = defaultLangCode; - std::unique_ptr m_qt_translator; - std::unique_ptr m_app_translator; - - std::shared_ptr m_index_task; - QString m_downloadingTranslation; - NetJobPtr m_dl_job; - NetJobPtr m_index_job; - QString m_nextDownload; + QDir m_dir; + + // initial state is just english + QVector m_languages = {Language (defaultLangCode)}; + + QString m_selectedLanguage = defaultLangCode; + std::unique_ptr m_qt_translator; + std::unique_ptr m_app_translator; + + std::shared_ptr m_index_task; + QString m_downloadingTranslation; + NetJobPtr m_dl_job; + NetJobPtr m_index_job; + QString m_nextDownload; + + std::unique_ptr m_po_translator; + QFileSystemWatcher *watcher; }; TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent) { - d.reset(new Private); - d->m_dir.setPath(path); - loadLocalIndex(); + d.reset(new Private); + d->m_dir.setPath(path); + FS::ensureFolderPathExists(path); + reloadLocalFiles(); + + d->watcher = new QFileSystemWatcher(this); + connect(d->watcher, &QFileSystemWatcher::directoryChanged, this, &TranslationsModel::translationDirChanged); + d->watcher->addPath(d->m_dir.canonicalPath()); } TranslationsModel::~TranslationsModel() { } +void TranslationsModel::translationDirChanged(const QString& path) +{ + qDebug() << "Dir changed:" << path; + reloadLocalFiles(); + selectLanguage(selectedLanguage()); +} + +void TranslationsModel::indexReceived() +{ + qDebug() << "Got translations index!"; + d->m_index_job.reset(); + if(d->m_selectedLanguage != defaultLangCode) + { + downloadTranslation(d->m_selectedLanguage); + } +} + +namespace { +void readIndex(const QString & path, QMap& languages) +{ + QByteArray data; + try + { + data = FS::read(path); + } + catch (const Exception &e) + { + qCritical() << "Translations Download Failed: index file not readable"; + return; + } + + int index = 1; + try + { + auto doc = Json::requireObject(Json::requireDocument(data)); + auto file_type = Json::requireString(doc, "file_type"); + if(file_type != "MMC-TRANSLATION-INDEX") + { + qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; + return; + } + auto version = Json::requireInteger(doc, "version"); + if(version > 2) + { + qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; + return; + } + auto langObjs = Json::requireObject(doc, "languages"); + for(auto iter = langObjs.begin(); iter != langObjs.end(); iter++) + { + Language lang(iter.key()); + + auto langObj = Json::requireObject(iter.value()); + lang.setTranslationStats( + Json::ensureInteger(langObj, "translated", 0), + Json::ensureInteger(langObj, "untranslated", 0), + Json::ensureInteger(langObj, "fuzzy", 0) + ); + lang.file_name = Json::requireString(langObj, "file"); + lang.file_sha1 = Json::requireString(langObj, "sha1"); + lang.file_size = Json::requireInteger(langObj, "size"); + + languages.insert(lang.key, lang); + index++; + } + } + catch (Json::JsonException & e) + { + qCritical() << "Translations Download Failed: index file could not be parsed as json"; + } +} +} + +void TranslationsModel::reloadLocalFiles() +{ + QMap languages = {{defaultLangCode, Language(defaultLangCode)}}; + + readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); + auto entries = d->m_dir.entryInfoList({"mmc_*.qm", "*.po"}, QDir::Files | QDir::NoDotAndDotDot); + for(auto & entry: entries) + { + auto completeSuffix = entry.completeSuffix(); + QString langCode; + FileType fileType = FileType::NONE; + if(completeSuffix == "qm") + { + langCode = entry.baseName().remove(0,4); + fileType = FileType::QM; + } + else if(completeSuffix == "po") + { + langCode = entry.baseName(); + fileType = FileType::PO; + } + else + { + continue; + } + + auto langIter = languages.find(langCode); + if(langIter != languages.end()) + { + auto & language = *langIter; + if(int(fileType) > int(language.localFileType)) + { + language.localFileType = fileType; + } + } + else + { + if(fileType == FileType::PO) + { + Language localFound(langCode); + localFound.localFileType = FileType::PO; + languages.insert(langCode, localFound); + } + } + } + + // changed and removed languages + for(auto iter = d->m_languages.begin(); iter != d->m_languages.end();) + { + auto &language = *iter; + auto row = iter - d->m_languages.begin(); + + auto updatedLanguageIter = languages.find(language.key); + if(updatedLanguageIter != languages.end()) + { + if(language.isIdenticalTo(*updatedLanguageIter)) + { + languages.remove(language.key); + } + else + { + language.apply(*updatedLanguageIter); + emit dataChanged(index(row), index(row)); + languages.remove(language.key); + } + iter++; + } + else + { + beginRemoveRows(QModelIndex(), row, row); + iter = d->m_languages.erase(iter); + endRemoveRows(); + } + } + // added languages + if(languages.isEmpty()) + { + return; + } + beginInsertRows(QModelIndex(), d->m_languages.size(), d->m_languages.size() + languages.size() - 1); + for(auto & language: languages) + { + d->m_languages.append(language); + } + endInsertRows(); +} + +namespace { +enum class Column +{ + Language, + Completeness +}; +} + + QVariant TranslationsModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - return QVariant(); - - int row = index.row(); - - if (row < 0 || row >= d->m_languages.size()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - return d->m_languages[row].locale.nativeLanguageName(); - case Qt::UserRole: - return d->m_languages[row].key; - default: - return QVariant(); - } + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + auto column = static_cast(index.column()); + + if (row < 0 || row >= d->m_languages.size()) + return QVariant(); + + auto & lang = d->m_languages[row]; + switch (role) + { + case Qt::DisplayRole: + { + switch(column) + { + case Column::Language: + { + return d->m_languages[row].locale.nativeLanguageName(); + } + case Column::Completeness: + { + QString text; + text.sprintf("%3.1f %%", lang.percentTranslated()); + return text; + } + } + } + case Qt::ToolTipRole: + { + return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total)); + } + case Qt::UserRole: + return d->m_languages[row].key; + default: + return QVariant(); + } } -int TranslationsModel::rowCount(const QModelIndex& parent) const +QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const { - return d->m_languages.size(); + auto column = static_cast(section); + if(role == Qt::DisplayRole) + { + switch(column) + { + case Column::Language: + { + return tr("Language"); + } + case Column::Completeness: + { + return tr("Completeness"); + } + } + } + else if(role == Qt::ToolTipRole) + { + switch(column) + { + case Column::Language: + { + return tr("The native language name."); + } + case Column::Completeness: + { + return tr("Completeness is the percentage of fully translated strings, not counting automatically guessed ones."); + } + } + } + return QAbstractListModel::headerData(section, orientation, role); } -Language * TranslationsModel::findLanguage(const QString& key) +int TranslationsModel::rowCount(const QModelIndex& parent) const { - auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) - { - return lang.key == key; - }); - if(found == d->m_languages.end()) - { - return nullptr; - } - else - { - return found; - } + return d->m_languages.size(); } -bool TranslationsModel::selectLanguage(QString key) +int TranslationsModel::columnCount(const QModelIndex& parent) const { - QString &langCode = key; - auto langPtr = findLanguage(key); - if(!langPtr) - { - qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; - langCode = defaultLangCode; - } - else - { - langCode = langPtr->key; - } - - // uninstall existing translators if there are any - if (d->m_app_translator) - { - QCoreApplication::removeTranslator(d->m_app_translator.get()); - d->m_app_translator.reset(); - } - if (d->m_qt_translator) - { - QCoreApplication::removeTranslator(d->m_qt_translator.get()); - d->m_qt_translator.reset(); - } - - /* - * FIXME: potential source of crashes: - * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. - * This function is not reentrant. - */ - QLocale locale(langCode); - QLocale::setDefault(locale); - - // if it's the default UI language, finish - if(langCode == defaultLangCode) - { - d->m_selectedLanguage = langCode; - return true; - } - - // otherwise install new translations - bool successful = false; - // FIXME: this is likely never present. FIX IT. - d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) - { - qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) - { - qCritical() << "Loading Qt Language File failed."; - d->m_qt_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_qt_translator.reset(); - } - - d->m_app_translator.reset(new QTranslator()); - if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) - { - qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_app_translator.get())) - { - qCritical() << "Loading Application Language File failed."; - d->m_app_translator.reset(); - } - else - { - successful = true; - } - } - else - { - d->m_app_translator.reset(); - } - d->m_selectedLanguage = langCode; - return successful; + return 2; } -QModelIndex TranslationsModel::selectedIndex() +Language * TranslationsModel::findLanguage(const QString& key) { - auto found = findLanguage(d->m_selectedLanguage); - if(found) - { - // QVector iterator freely converts to pointer to contained type - return index(found - d->m_languages.begin(), 0, QModelIndex()); - } - return QModelIndex(); + auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang) + { + return lang.key == key; + }); + if(found == d->m_languages.end()) + { + return nullptr; + } + else + { + return found; + } } -QString TranslationsModel::selectedLanguage() +bool TranslationsModel::selectLanguage(QString key) { - return d->m_selectedLanguage; + QString &langCode = key; + auto langPtr = findLanguage(key); + if(!langPtr) + { + qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; + langCode = defaultLangCode; + } + else + { + langCode = langPtr->key; + } + + // uninstall existing translators if there are any + if (d->m_app_translator) + { + QCoreApplication::removeTranslator(d->m_app_translator.get()); + d->m_app_translator.reset(); + } + if (d->m_qt_translator) + { + QCoreApplication::removeTranslator(d->m_qt_translator.get()); + d->m_qt_translator.reset(); + } + + /* + * FIXME: potential source of crashes: + * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. + * This function is not reentrant. + */ + QLocale locale(langCode); + QLocale::setDefault(locale); + + // if it's the default UI language, finish + if(langCode == defaultLangCode) + { + d->m_selectedLanguage = langCode; + return true; + } + + // otherwise install new translations + bool successful = false; + // FIXME: this is likely never present. FIX IT. + d->m_qt_translator.reset(new QTranslator()); + if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) + { + qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) + { + qCritical() << "Loading Qt Language File failed."; + d->m_qt_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_qt_translator.reset(); + } + + if(langPtr->localFileType == FileType::PO) + { + qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; + auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); + if(!poTranslator->isEmpty()) + { + if (!QCoreApplication::installTranslator(poTranslator)) + { + delete poTranslator; + qCritical() << "Installing Application Language File failed."; + } + else + { + d->m_app_translator.reset(poTranslator); + successful = true; + } + } + else + { + qCritical() << "Loading Application Language File failed."; + d->m_app_translator.reset(); + } + } + else if(langPtr->localFileType == FileType::QM) + { + d->m_app_translator.reset(new QTranslator()); + if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) + { + qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; + if (!QCoreApplication::installTranslator(d->m_app_translator.get())) + { + qCritical() << "Installing Application Language File failed."; + d->m_app_translator.reset(); + } + else + { + successful = true; + } + } + else + { + d->m_app_translator.reset(); + } + } + else + { + d->m_app_translator.reset(); + } + d->m_selectedLanguage = langCode; + return successful; } -void TranslationsModel::downloadIndex() +QModelIndex TranslationsModel::selectedIndex() { - if(d->m_index_job || d->m_dl_job) - { - return; - } - qDebug() << "Downloading Translations Index..."; - d->m_index_job.reset(new NetJob("Translations Index")); - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index"); - d->m_index_task = Net::Download::makeCached(QUrl("http://files.multimc.org/translations/index"), entry); - d->m_index_job->addNetAction(d->m_index_task); - connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexRecieved); - d->m_index_job->start(); + auto found = findLanguage(d->m_selectedLanguage); + if(found) + { + // QVector iterator freely converts to pointer to contained type + return index(found - d->m_languages.begin(), 0, QModelIndex()); + } + return QModelIndex(); } -void TranslationsModel::indexRecieved() +QString TranslationsModel::selectedLanguage() { - qDebug() << "Got translations index!"; - d->m_index_job.reset(); - loadLocalIndex(); - if(d->m_selectedLanguage != defaultLangCode) - { - downloadTranslation(d->m_selectedLanguage); - } + return d->m_selectedLanguage; } -void TranslationsModel::loadLocalIndex() +void TranslationsModel::downloadIndex() { - QByteArray data; - try - { - data = FS::read(d->m_dir.absoluteFilePath("index")); - } - catch (Exception &e) - { - qCritical() << "Translations Download Failed: index file not readable"; - return; - } - QVector languages; - QList lines = data.split('\n'); - // add the default english. - languages.append({defaultLangCode, QLocale(defaultLangCode), true}); - for (const auto line : lines) - { - if(!line.isEmpty()) - { - auto str = QString::fromLatin1(line); - str.remove(".qm"); - languages.append({str, QLocale(str), false}); - } - } - beginResetModel(); - d->m_languages.swap(languages); - endResetModel(); + if(d->m_index_job || d->m_dl_job) + { + return; + } + qDebug() << "Downloading Translations Index..."; + d->m_index_job.reset(new NetJob("Translations Index")); + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index_v2.json"); + entry->setStale(true); + d->m_index_task = Net::Download::makeCached(QUrl("https://files.multimc.org/translations/index_v2.json"), entry); + d->m_index_job->addNetAction(d->m_index_task); + connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); + d->m_index_job->start(); } void TranslationsModel::updateLanguage(QString key) { - if(key == defaultLangCode) - { - qWarning() << "Cannot update builtin language" << key; - return; - } - auto found = findLanguage(key); - if(!found) - { - qWarning() << "Cannot update invalid language" << key; - return; - } - if(!found->updated) - { - downloadTranslation(key); - } + if(key == defaultLangCode) + { + qWarning() << "Cannot update builtin language" << key; + return; + } + auto found = findLanguage(key); + if(!found) + { + qWarning() << "Cannot update invalid language" << key; + return; + } + if(!found->updated) + { + downloadTranslation(key); + } } void TranslationsModel::downloadTranslation(QString key) { - if(d->m_dl_job) - { - d->m_nextDownload = key; - return; - } - d->m_downloadingTranslation = key; - MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); - entry->setStale(true); - d->m_dl_job.reset(new NetJob("Translation for " + key)); - d->m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + key + ".qm"), entry)); - connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - d->m_dl_job->start(); + if(d->m_dl_job) + { + d->m_nextDownload = key; + return; + } + auto lang = findLanguage(key); + if(!lang) + { + qWarning() << "Will not download an unknown translation" << key; + return; + } + + d->m_downloadingTranslation = key; + MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + entry->setStale(true); + + auto dl = Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + lang->file_name), entry); + auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1()); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash)); + dl->m_total_progress = lang->file_size; + + d->m_dl_job.reset(new NetJob("Translation for " + key)); + d->m_dl_job->addNetAction(dl); + + connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + + d->m_dl_job->start(); } void TranslationsModel::downloadNext() { - if(!d->m_nextDownload.isEmpty()) - { - downloadTranslation(d->m_nextDownload); - d->m_nextDownload.clear(); - } + if(!d->m_nextDownload.isEmpty()) + { + downloadTranslation(d->m_nextDownload); + d->m_nextDownload.clear(); + } } void TranslationsModel::dlFailed(QString reason) { - qCritical() << "Translations Download Failed:" << reason; - d->m_dl_job.reset(); - downloadNext(); + qCritical() << "Translations Download Failed:" << reason; + d->m_dl_job.reset(); + downloadNext(); } void TranslationsModel::dlGood() { - qDebug() << "Got translation:" << d->m_downloadingTranslation; - - if(d->m_downloadingTranslation == d->m_selectedLanguage) - { - selectLanguage(d->m_selectedLanguage); - } - d->m_dl_job.reset(); - downloadNext(); + qDebug() << "Got translation:" << d->m_downloadingTranslation; + + if(d->m_downloadingTranslation == d->m_selectedLanguage) + { + selectLanguage(d->m_selectedLanguage); + } + d->m_dl_job.reset(); + downloadNext(); } void TranslationsModel::indexFailed(QString reason) { - qCritical() << "Translations Index Download Failed:" << reason; - d->m_index_job.reset(); + qCritical() << "Translations Index Download Failed:" << reason; + d->m_index_job.reset(); } diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h index bd481134..a0327fcd 100644 --- a/api/logic/translations/TranslationsModel.h +++ b/api/logic/translations/TranslationsModel.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,39 +23,43 @@ struct Language; class MULTIMC_LOGIC_EXPORT TranslationsModel : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: - explicit TranslationsModel(QString path, QObject *parent = 0); - virtual ~TranslationsModel(); + explicit TranslationsModel(QString path, QObject *parent = 0); + virtual ~TranslationsModel(); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & parent) const override; - bool selectLanguage(QString key); - void updateLanguage(QString key); - QModelIndex selectedIndex(); - QString selectedLanguage(); + bool selectLanguage(QString key); + void updateLanguage(QString key); + QModelIndex selectedIndex(); + QString selectedLanguage(); - void downloadIndex(); + void downloadIndex(); private: - Language *findLanguage(const QString & key); - void loadLocalIndex(); - void downloadTranslation(QString key); - void downloadNext(); + Language *findLanguage(const QString & key); + void reloadLocalFiles(); + void downloadTranslation(QString key); + void downloadNext(); - // hide copy constructor - TranslationsModel(const TranslationsModel &) = delete; - // hide assign op - TranslationsModel &operator=(const TranslationsModel &) = delete; + // hide copy constructor + TranslationsModel(const TranslationsModel &) = delete; + // hide assign op + TranslationsModel &operator=(const TranslationsModel &) = delete; private slots: - void indexRecieved(); - void indexFailed(QString reason); - void dlFailed(QString reason); - void dlGood(); + void indexReceived(); + void indexFailed(QString reason); + void dlFailed(QString reason); + void dlGood(); + void translationDirChanged(const QString &path); + private: /* data */ - struct Private; - std::unique_ptr d; + struct Private; + std::unique_ptr d; }; diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp index e0adf593..dbd8c65a 100644 --- a/api/logic/updater/DownloadTask.cpp +++ b/api/logic/updater/DownloadTask.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,140 +27,147 @@ namespace GoUpdate { DownloadTask::DownloadTask(Status status, QString target, QObject *parent) - : Task(parent), m_updateFilesDir(target) + : Task(parent), m_updateFilesDir(target) { - m_status = status; + m_status = status; - m_updateFilesDir.setAutoRemove(false); + m_updateFilesDir.setAutoRemove(false); } void DownloadTask::executeTask() { - loadVersionInfo(); + loadVersionInfo(); } void DownloadTask::loadVersionInfo() { - setStatus(tr("Loading version information...")); + setStatus(tr("Loading version information...")); - NetJob *netJob = new NetJob("Version Info"); + NetJob *netJob = new NetJob("Version Info"); - // Find the index URL. - QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json"); - qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl; + // Find the index URL. + QUrl newIndexUrl = QUrl(m_status.newRepoUrl).resolved(QString::number(m_status.newVersionId) + ".json"); + qDebug() << m_status.newRepoUrl << " turns into " << newIndexUrl; - netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData)); + netJob->addNetAction(m_newVersionFileListDownload = Net::Download::makeByteArray(newIndexUrl, &newVersionFileListData)); - // If we have a current version URL, get that one too. - if (!m_status.currentRepoUrl.isEmpty()) - { - QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json"); - netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, ¤tVersionFileListData)); - qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl; - } + // If we have a current version URL, get that one too. + if (!m_status.currentRepoUrl.isEmpty()) + { + QUrl cIndexUrl = QUrl(m_status.currentRepoUrl).resolved(QString::number(m_status.currentVersionId) + ".json"); + netJob->addNetAction(m_currentVersionFileListDownload = Net::Download::makeByteArray(cIndexUrl, ¤tVersionFileListData)); + qDebug() << m_status.currentRepoUrl << " turns into " << cIndexUrl; + } - // connect signals and start the job - connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo); - connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed); - m_vinfoNetJob.reset(netJob); - netJob->start(); + // connect signals and start the job + connect(netJob, &NetJob::succeeded, this, &DownloadTask::processDownloadedVersionInfo); + connect(netJob, &NetJob::failed, this, &DownloadTask::vinfoDownloadFailed); + m_vinfoNetJob.reset(netJob); + netJob->start(); } void DownloadTask::vinfoDownloadFailed() { - // Something failed. We really need the second download (current version info), so parse - // downloads anyways as long as the first one succeeded. - if (m_newVersionFileListDownload->wasSuccessful()) - { - processDownloadedVersionInfo(); - return; - } - - // TODO: Give a more detailed error message. - qCritical() << "Failed to download version info files."; - emitFailed(tr("Failed to download version info files.")); + // Something failed. We really need the second download (current version info), so parse + // downloads anyways as long as the first one succeeded. + if (m_newVersionFileListDownload->wasSuccessful()) + { + processDownloadedVersionInfo(); + return; + } + + // TODO: Give a more detailed error message. + qCritical() << "Failed to download version info files."; + emitFailed(tr("Failed to download version info files.")); } void DownloadTask::processDownloadedVersionInfo() { - VersionFileList m_currentVersionFileList; - VersionFileList m_newVersionFileList; - - setStatus(tr("Reading file list for new version...")); - qDebug() << "Reading file list for new version..."; - QString error; - if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error)) - { - qCritical() << error; - emitFailed(error); - return; - } - - // if we have the current version info, use it. - if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) - { - setStatus(tr("Reading file list for current version...")); - qDebug() << "Reading file list for current version..."; - // if this fails, it's not a complete loss. - QString error; - if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error)) - { - qDebug() << error << "This is not a fatal error."; - } - } - - // We don't need this any more. - m_currentVersionFileListDownload.reset(); - m_newVersionFileListDownload.reset(); - m_vinfoNetJob.reset(); - - setStatus(tr("Processing file lists - figuring out how to install the update...")); - - // make a new netjob for the actual update files - NetJobPtr netJob (new NetJob("Update Files")); - - // fill netJob and operationList - if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations)) - { - emitFailed(tr("Failed to process update lists...")); - return; - } - - // Now start the download. - QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished); - QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged); - QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed); - - setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size()))); - qDebug() << "Begin downloading update files to" << m_updateFilesDir.path(); - m_filesNetJob = netJob; - m_filesNetJob->start(); + VersionFileList m_currentVersionFileList; + VersionFileList m_newVersionFileList; + + setStatus(tr("Reading file list for new version...")); + qDebug() << "Reading file list for new version..."; + QString error; + if (!parseVersionInfo(newVersionFileListData, m_newVersionFileList, error)) + { + qCritical() << error; + emitFailed(error); + return; + } + + // if we have the current version info, use it. + if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful()) + { + setStatus(tr("Reading file list for current version...")); + qDebug() << "Reading file list for current version..."; + // if this fails, it's not a complete loss. + QString error; + if(!parseVersionInfo( currentVersionFileListData, m_currentVersionFileList, error)) + { + qDebug() << error << "This is not a fatal error."; + } + } + + // We don't need this any more. + m_currentVersionFileListDownload.reset(); + m_newVersionFileListDownload.reset(); + m_vinfoNetJob.reset(); + + setStatus(tr("Processing file lists - figuring out how to install the update...")); + + // make a new netjob for the actual update files + NetJobPtr netJob (new NetJob("Update Files")); + + // fill netJob and operationList + if (!processFileLists(m_currentVersionFileList, m_newVersionFileList, m_status.rootPath, m_updateFilesDir.path(), netJob, m_operations)) + { + emitFailed(tr("Failed to process update lists...")); + return; + } + + // Now start the download. + QObject::connect(netJob.get(), &NetJob::succeeded, this, &DownloadTask::fileDownloadFinished); + QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged); + QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed); + + if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701 + { + setStatus(tr("Downloading one update file.")); + } + else + { + setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size()))); + } + qDebug() << "Begin downloading update files to" << m_updateFilesDir.path(); + m_filesNetJob = netJob; + m_filesNetJob->start(); } void DownloadTask::fileDownloadFinished() { - emitSucceeded(); + emitSucceeded(); } void DownloadTask::fileDownloadFailed(QString reason) { - qCritical() << "Failed to download update files:" << reason; - emitFailed(tr("Failed to download update files: %1").arg(reason)); + qCritical() << "Failed to download update files:" << reason; + emitFailed(tr("Failed to download update files: %1").arg(reason)); } void DownloadTask::fileDownloadProgressChanged(qint64 current, qint64 total) { - setProgress(current, total); + setProgress(current, total); } QString DownloadTask::updateFilesDir() { - return m_updateFilesDir.path(); + return m_updateFilesDir.path(); } OperationList DownloadTask::operations() { - return m_operations; + return m_operations; } } \ No newline at end of file diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h index 186826e2..5c973912 100644 --- a/api/logic/updater/DownloadTask.h +++ b/api/logic/updater/DownloadTask.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,69 +29,70 @@ namespace GoUpdate */ class MULTIMC_LOGIC_EXPORT DownloadTask : public Task { - Q_OBJECT + Q_OBJECT public: - /** - * Create a download task - * - * target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness - */ - explicit DownloadTask(Status status, QString target, QObject* parent = 0); + /** + * Create a download task + * + * target is a template - XXXXXX at the end will be replaced with a random generated string, ensuring uniqueness + */ + explicit DownloadTask(Status status, QString target, QObject* parent = 0); + virtual ~DownloadTask() {}; - /// Get the directory that will contain the update files. - QString updateFilesDir(); + /// Get the directory that will contain the update files. + QString updateFilesDir(); - /// Get the list of operations that should be done - OperationList operations(); + /// Get the list of operations that should be done + OperationList operations(); - /// set updater download behavior - void setUseLocalUpdater(bool useLocal); + /// set updater download behavior + void setUseLocalUpdater(bool useLocal); protected: - //! Entry point for tasks. - virtual void executeTask() override; - - /*! - * Downloads the version info files from the repository. - * The files for both the current build, and the build that we're updating to need to be downloaded. - * If the current version's info file can't be found, MultiMC will not delete files that - * were removed between versions. It will still replace files that have changed, however. - * Note that although the repository URL for the current version is not given to the update task, - * the task will attempt to look it up in the UpdateChecker's channel list. - * If an error occurs here, the function will call emitFailed and return false. - */ - void loadVersionInfo(); - - NetJobPtr m_vinfoNetJob; - QByteArray currentVersionFileListData; - QByteArray newVersionFileListData; - Net::Download::Ptr m_currentVersionFileListDownload; - Net::Download::Ptr m_newVersionFileListDownload; - - NetJobPtr m_filesNetJob; - - Status m_status; - - OperationList m_operations; - - /*! - * Temporary directory to store update files in. - * This will be set to not auto delete. Task will fail if this fails to be created. - */ - QTemporaryDir m_updateFilesDir; + //! Entry point for tasks. + virtual void executeTask() override; + + /*! + * Downloads the version info files from the repository. + * The files for both the current build, and the build that we're updating to need to be downloaded. + * If the current version's info file can't be found, MultiMC will not delete files that + * were removed between versions. It will still replace files that have changed, however. + * Note that although the repository URL for the current version is not given to the update task, + * the task will attempt to look it up in the UpdateChecker's channel list. + * If an error occurs here, the function will call emitFailed and return false. + */ + void loadVersionInfo(); + + NetJobPtr m_vinfoNetJob; + QByteArray currentVersionFileListData; + QByteArray newVersionFileListData; + Net::Download::Ptr m_currentVersionFileListDownload; + Net::Download::Ptr m_newVersionFileListDownload; + + NetJobPtr m_filesNetJob; + + Status m_status; + + OperationList m_operations; + + /*! + * Temporary directory to store update files in. + * This will be set to not auto delete. Task will fail if this fails to be created. + */ + QTemporaryDir m_updateFilesDir; protected slots: - /*! - * This function is called when version information is finished downloading - * and at least the new file list download succeeded - */ - void processDownloadedVersionInfo(); - void vinfoDownloadFailed(); - - void fileDownloadFinished(); - void fileDownloadFailed(QString reason); - void fileDownloadProgressChanged(qint64 current, qint64 total); + /*! + * This function is called when version information is finished downloading + * and at least the new file list download succeeded + */ + void processDownloadedVersionInfo(); + void vinfoDownloadFailed(); + + void fileDownloadFinished(); + void fileDownloadFailed(QString reason); + void fileDownloadProgressChanged(qint64 current, qint64 total); }; } \ No newline at end of file diff --git a/api/logic/updater/DownloadTask_test.cpp b/api/logic/updater/DownloadTask_test.cpp index e75c3ffa..531b2527 100644 --- a/api/logic/updater/DownloadTask_test.cpp +++ b/api/logic/updater/DownloadTask_test.cpp @@ -12,11 +12,11 @@ using namespace GoUpdate; FileSourceList encodeBaseFile(const char *suffix) { - auto base = QDir::currentPath(); - QUrl localFile = QUrl::fromLocalFile(base + suffix); - QString localUrlString = localFile.toString(QUrl::FullyEncoded); - auto item = FileSource("http", localUrlString); - return FileSourceList({item}); + auto base = QDir::currentPath(); + QUrl localFile = QUrl::fromLocalFile(base + suffix); + QString localUrlString = localFile.toString(QUrl::FullyEncoded); + auto item = FileSource("http", localUrlString); + return FileSourceList({item}); } Q_DECLARE_METATYPE(VersionFileList) @@ -24,191 +24,191 @@ Q_DECLARE_METATYPE(Operation) QDebug operator<<(QDebug dbg, const FileSource &f) { - dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url - << " comp=" << f.compressionType << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url + << " comp=" << f.compressionType << ")"; + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const VersionFileEntry &v) { - dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode - << " md5=" << v.md5 << " sources=" << v.sources << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode + << " md5=" << v.md5 << " sources=" << v.sources << ")"; + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const Operation::Type &t) { - switch (t) - { - case Operation::OP_REPLACE: - dbg << "OP_COPY"; - break; - case Operation::OP_DELETE: - dbg << "OP_DELETE"; - break; - } - return dbg.maybeSpace(); + switch (t) + { + case Operation::OP_REPLACE: + dbg << "OP_COPY"; + break; + case Operation::OP_DELETE: + dbg << "OP_DELETE"; + break; + } + return dbg.maybeSpace(); } QDebug operator<<(QDebug dbg, const Operation &u) { - dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source - << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "Operation(type=" << u.type << " file=" << u.source + << " dest=" << u.destination << " mode=" << u.destinationMode << ")"; + return dbg.maybeSpace(); } class DownloadTaskTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - } - void cleanupTestCase() - { - } - - void test_parseVersionInfo_data() - { - QTest::addColumn("data"); - QTest::addColumn("list"); - QTest::addColumn("error"); - QTest::addColumn("ret"); - - QTest::newRow("one") - << MULTIMC_GET_TEST_FILE("data/1.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneA"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{"fileThree", - 750, - encodeBaseFile("/data/fileThree"), - "f12df554b21e320be6471d7154130e70"}) - << QString() << true; - QTest::newRow("two") - << MULTIMC_GET_TEST_FILE("data/2.json") - << (VersionFileList() - << VersionFileEntry{"fileOne", - 493, - encodeBaseFile("/data/fileOneB"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{"fileTwo", - 644, - encodeBaseFile("/data/fileTwo"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << QString() << true; - } - void test_parseVersionInfo() - { - QFETCH(QByteArray, data); - QFETCH(VersionFileList, list); - QFETCH(QString, error); - QFETCH(bool, ret); - - VersionFileList outList; - QString outError; - bool outRet = parseVersionInfo(data, outList, outError); - QCOMPARE(outRet, ret); - QCOMPARE(outList, list); - QCOMPARE(outError, error); - } - - void test_processFileLists_data() - { - QTest::addColumn("tempFolder"); - QTest::addColumn("currentVersion"); - QTest::addColumn("newVersion"); - QTest::addColumn("expectedOperations"); - - QTemporaryDir tempFolderObj; - QString tempFolder = tempFolderObj.path(); - // update fileOne, keep fileTwo, remove fileThree - QTest::newRow("test 1") - << tempFolder << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource( - "http", "http://host/path/fileOne-1"), - "9eb84090956c484e32cb6c08455a667b"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource( - "http", "http://host/path/fileTwo-1"), - "38f94f54fa3eb72b0ea836538c10b043"} - << VersionFileEntry{ - "data/fileThree", 420, - FileSourceList() - << FileSource( - "http", "http://host/path/fileThree-1"), - "f12df554b21e320be6471d7154130e70"}) - << (VersionFileList() - << VersionFileEntry{ - "data/fileOne", 493, - FileSourceList() - << FileSource("http", - "http://host/path/fileOne-2"), - "42915a71277c9016668cce7b82c6b577"} - << VersionFileEntry{ - "data/fileTwo", 644, - FileSourceList() - << FileSource("http", - "http://host/path/fileTwo-2"), - "38f94f54fa3eb72b0ea836538c10b043"}) - << (OperationList() - << Operation::DeleteOp("data/fileThree") - << Operation::CopyOp( - FS::PathCombine(tempFolder, - QString("data/fileOne").replace("/", "_")), - "data/fileOne", 493)); - } - void test_processFileLists() - { - QFETCH(QString, tempFolder); - QFETCH(VersionFileList, currentVersion); - QFETCH(VersionFileList, newVersion); - QFETCH(OperationList, expectedOperations); - - OperationList operations; - - processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy"), operations); - qDebug() << (operations == expectedOperations); - qDebug() << operations; - qDebug() << expectedOperations; - QCOMPARE(operations, expectedOperations); - } - - void test_OSXPathFixup() - { - QString path, pathOrig; - bool result; - // Proper OSX path - pathOrig = path = "MultiMC.app/Foo/Bar/Baz"; - qDebug() << "Proper OSX path: " << path; - result = fixPathForOSX(path); - QCOMPARE(path, QString("Foo/Bar/Baz")); - QCOMPARE(result, true); - - // Bad OSX path - pathOrig = path = "translations/klingon.lol"; - qDebug() << "Bad OSX path: " << path; - result = fixPathForOSX(path); - QCOMPARE(path, pathOrig); - QCOMPARE(result, false); - } + void initTestCase() + { + } + void cleanupTestCase() + { + } + + void test_parseVersionInfo_data() + { + QTest::addColumn("data"); + QTest::addColumn("list"); + QTest::addColumn("error"); + QTest::addColumn("ret"); + + QTest::newRow("one") + << MULTIMC_GET_TEST_FILE("data/1.json") + << (VersionFileList() + << VersionFileEntry{"fileOne", + 493, + encodeBaseFile("/data/fileOneA"), + "9eb84090956c484e32cb6c08455a667b"} + << VersionFileEntry{"fileTwo", + 644, + encodeBaseFile("/data/fileTwo"), + "38f94f54fa3eb72b0ea836538c10b043"} + << VersionFileEntry{"fileThree", + 750, + encodeBaseFile("/data/fileThree"), + "f12df554b21e320be6471d7154130e70"}) + << QString() << true; + QTest::newRow("two") + << MULTIMC_GET_TEST_FILE("data/2.json") + << (VersionFileList() + << VersionFileEntry{"fileOne", + 493, + encodeBaseFile("/data/fileOneB"), + "42915a71277c9016668cce7b82c6b577"} + << VersionFileEntry{"fileTwo", + 644, + encodeBaseFile("/data/fileTwo"), + "38f94f54fa3eb72b0ea836538c10b043"}) + << QString() << true; + } + void test_parseVersionInfo() + { + QFETCH(QByteArray, data); + QFETCH(VersionFileList, list); + QFETCH(QString, error); + QFETCH(bool, ret); + + VersionFileList outList; + QString outError; + bool outRet = parseVersionInfo(data, outList, outError); + QCOMPARE(outRet, ret); + QCOMPARE(outList, list); + QCOMPARE(outError, error); + } + + void test_processFileLists_data() + { + QTest::addColumn("tempFolder"); + QTest::addColumn("currentVersion"); + QTest::addColumn("newVersion"); + QTest::addColumn("expectedOperations"); + + QTemporaryDir tempFolderObj; + QString tempFolder = tempFolderObj.path(); + // update fileOne, keep fileTwo, remove fileThree + QTest::newRow("test 1") + << tempFolder << (VersionFileList() + << VersionFileEntry{ + "data/fileOne", 493, + FileSourceList() + << FileSource( + "http", "http://host/path/fileOne-1"), + "9eb84090956c484e32cb6c08455a667b"} + << VersionFileEntry{ + "data/fileTwo", 644, + FileSourceList() + << FileSource( + "http", "http://host/path/fileTwo-1"), + "38f94f54fa3eb72b0ea836538c10b043"} + << VersionFileEntry{ + "data/fileThree", 420, + FileSourceList() + << FileSource( + "http", "http://host/path/fileThree-1"), + "f12df554b21e320be6471d7154130e70"}) + << (VersionFileList() + << VersionFileEntry{ + "data/fileOne", 493, + FileSourceList() + << FileSource("http", + "http://host/path/fileOne-2"), + "42915a71277c9016668cce7b82c6b577"} + << VersionFileEntry{ + "data/fileTwo", 644, + FileSourceList() + << FileSource("http", + "http://host/path/fileTwo-2"), + "38f94f54fa3eb72b0ea836538c10b043"}) + << (OperationList() + << Operation::DeleteOp("data/fileThree") + << Operation::CopyOp( + FS::PathCombine(tempFolder, + QString("data/fileOne").replace("/", "_")), + "data/fileOne", 493)); + } + void test_processFileLists() + { + QFETCH(QString, tempFolder); + QFETCH(VersionFileList, currentVersion); + QFETCH(VersionFileList, newVersion); + QFETCH(OperationList, expectedOperations); + + OperationList operations; + + processFileLists(currentVersion, newVersion, QDir::currentPath(), tempFolder, new NetJob("Dummy"), operations); + qDebug() << (operations == expectedOperations); + qDebug() << operations; + qDebug() << expectedOperations; + QCOMPARE(operations, expectedOperations); + } + + void test_OSXPathFixup() + { + QString path, pathOrig; + bool result; + // Proper OSX path + pathOrig = path = "MultiMC.app/Foo/Bar/Baz"; + qDebug() << "Proper OSX path: " << path; + result = fixPathForOSX(path); + QCOMPARE(path, QString("Foo/Bar/Baz")); + QCOMPARE(result, true); + + // Bad OSX path + pathOrig = path = "translations/klingon.lol"; + qDebug() << "Bad OSX path: " << path; + result = fixPathForOSX(path); + QCOMPARE(path, pathOrig); + QCOMPARE(result, false); + } }; extern "C" { - QTEST_GUILESS_MAIN(DownloadTaskTest) + QTEST_GUILESS_MAIN(DownloadTaskTest) } #include "DownloadTask_test.moc" diff --git a/api/logic/updater/GoUpdate.cpp b/api/logic/updater/GoUpdate.cpp index 716048a0..ef040db6 100644 --- a/api/logic/updater/GoUpdate.cpp +++ b/api/logic/updater/GoUpdate.cpp @@ -12,208 +12,208 @@ namespace GoUpdate bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &error) { - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); - if (jsonError.error != QJsonParseError::NoError) - { - error = QString("Failed to parse version info JSON: %1 at %2") - .arg(jsonError.errorString()) - .arg(jsonError.offset); - qCritical() << error; - return false; - } - - QJsonObject json = jsonDoc.object(); - - qDebug() << data; - qDebug() << "Loading version info from JSON."; - QJsonArray filesArray = json.value("Files").toArray(); - for (QJsonValue fileValue : filesArray) - { - QJsonObject fileObj = fileValue.toObject(); - - QString file_path = fileObj.value("Path").toString(); + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError); + if (jsonError.error != QJsonParseError::NoError) + { + error = QString("Failed to parse version info JSON: %1 at %2") + .arg(jsonError.errorString()) + .arg(jsonError.offset); + qCritical() << error; + return false; + } + + QJsonObject json = jsonDoc.object(); + + qDebug() << data; + qDebug() << "Loading version info from JSON."; + QJsonArray filesArray = json.value("Files").toArray(); + for (QJsonValue fileValue : filesArray) + { + QJsonObject fileObj = fileValue.toObject(); + + QString file_path = fileObj.value("Path").toString(); #ifdef Q_OS_MAC - // On OSX, the paths for the updater need to be fixed. - // basically, anything that isn't in the .app folder is ignored. - // everything else is changed so the code that processes the files actually finds - // them and puts the replacements in the right spots. - fixPathForOSX(file_path); + // On OSX, the paths for the updater need to be fixed. + // basically, anything that isn't in the .app folder is ignored. + // everything else is changed so the code that processes the files actually finds + // them and puts the replacements in the right spots. + fixPathForOSX(file_path); #endif - VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(), - FileSourceList(), fileObj.value("MD5").toString(), }; - qDebug() << "File" << file.path << "with perms" << file.mode; - - QJsonArray sourceArray = fileObj.value("Sources").toArray(); - for (QJsonValue val : sourceArray) - { - QJsonObject sourceObj = val.toObject(); - - QString type = sourceObj.value("SourceType").toString(); - if (type == "http") - { - file.sources.append(FileSource("http", sourceObj.value("Url").toString())); - } - else - { - qWarning() << "Unknown source type" << type << "ignored."; - } - } - - qDebug() << "Loaded info for" << file.path; - - list.append(file); - } - - return true; + VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(), + FileSourceList(), fileObj.value("MD5").toString(), }; + qDebug() << "File" << file.path << "with perms" << file.mode; + + QJsonArray sourceArray = fileObj.value("Sources").toArray(); + for (QJsonValue val : sourceArray) + { + QJsonObject sourceObj = val.toObject(); + + QString type = sourceObj.value("SourceType").toString(); + if (type == "http") + { + file.sources.append(FileSource("http", sourceObj.value("Url").toString())); + } + else + { + qWarning() << "Unknown source type" << type << "ignored."; + } + } + + qDebug() << "Loaded info for" << file.path; + + list.append(file); + } + + return true; } bool processFileLists ( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops + const VersionFileList ¤tVersion, + const VersionFileList &newVersion, + const QString &rootPath, + const QString &tempPath, + NetJobPtr job, + OperationList &ops ) { - // First, if we've loaded the current version's file list, we need to iterate through it and - // delete anything in the current one version's list that isn't in the new version's list. - for (VersionFileEntry entry : currentVersion) - { - QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); - if (!toDelete.exists()) - { - qCritical() << "Expected file " << toDelete.absoluteFilePath() - << " doesn't exist!"; - } - bool keep = false; - - // - for (VersionFileEntry newEntry : newVersion) - { - if (newEntry.path == entry.path) - { - qDebug() << "Not deleting" << entry.path - << "because it is still present in the new version."; - keep = true; - break; - } - } - - // If the loop reaches the end and we didn't find a match, delete the file. - if (!keep) - { - if (toDelete.exists()) - ops.append(Operation::DeleteOp(entry.path)); - } - } - - // Next, check each file in MultiMC's folder and see if we need to update them. - for (VersionFileEntry entry : newVersion) - { - // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a - // way to do this in the background. - QString fileMD5; - QString realEntryPath = FS::PathCombine(rootPath, entry.path); - QFile entryFile(realEntryPath); - QFileInfo entryInfo(realEntryPath); - - bool needs_upgrade = false; - if (!entryFile.exists()) - { - needs_upgrade = true; - } - else - { - bool pass = true; - if (!entryInfo.isReadable()) - { - qCritical() << "File " << realEntryPath << " is not readable."; - pass = false; - } - if (!entryInfo.isWritable()) - { - qCritical() << "File " << realEntryPath << " is not writable."; - pass = false; - } - if (!entryFile.open(QFile::ReadOnly)) - { - qCritical() << "File " << realEntryPath << " cannot be opened for reading."; - pass = false; - } - if (!pass) - { - ops.clear(); - return false; - } - } - - if(!needs_upgrade) - { - QCryptographicHash hash(QCryptographicHash::Md5); - auto foo = entryFile.readAll(); - - hash.addData(foo); - fileMD5 = hash.result().toHex(); - if ((fileMD5 != entry.md5)) - { - qDebug() << "MD5Sum does not match!"; - qDebug() << "Expected:'" << entry.md5 << "'"; - qDebug() << "Got: '" << fileMD5 << "'"; - needs_upgrade = true; - } - } - - // skip file. it doesn't need an upgrade. - if (!needs_upgrade) - { - qDebug() << "File" << realEntryPath << " does not need updating."; - continue; - } - - // yep. this file actually needs an upgrade. PROCEED. - qDebug() << "Found file" << realEntryPath << " that needs updating."; - - // Go through the sources list and find one to use. - // TODO: Make a NetAction that takes a source list and tries each of them until one - // works. For now, we'll just use the first http one. - for (FileSource source : entry.sources) - { - if (source.type != "http") - continue; - - qDebug() << "Will download" << entry.path << "from" << source.url; - - // Download it to updatedir/- where filepath is the file's - // path with slashes replaced by underscores. - QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_")); - - // We need to download the file to the updatefiles folder and add a task - // to copy it to its install path. - auto download = Net::Download::makeFile(source.url, dlPath); - auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); - download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); - job->addNetAction(download); - ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); - } - } - return true; + // First, if we've loaded the current version's file list, we need to iterate through it and + // delete anything in the current one version's list that isn't in the new version's list. + for (VersionFileEntry entry : currentVersion) + { + QFileInfo toDelete(FS::PathCombine(rootPath, entry.path)); + if (!toDelete.exists()) + { + qCritical() << "Expected file " << toDelete.absoluteFilePath() + << " doesn't exist!"; + } + bool keep = false; + + // + for (VersionFileEntry newEntry : newVersion) + { + if (newEntry.path == entry.path) + { + qDebug() << "Not deleting" << entry.path + << "because it is still present in the new version."; + keep = true; + break; + } + } + + // If the loop reaches the end and we didn't find a match, delete the file. + if (!keep) + { + if (toDelete.exists()) + ops.append(Operation::DeleteOp(entry.path)); + } + } + + // Next, check each file in MultiMC's folder and see if we need to update them. + for (VersionFileEntry entry : newVersion) + { + // TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a + // way to do this in the background. + QString fileMD5; + QString realEntryPath = FS::PathCombine(rootPath, entry.path); + QFile entryFile(realEntryPath); + QFileInfo entryInfo(realEntryPath); + + bool needs_upgrade = false; + if (!entryFile.exists()) + { + needs_upgrade = true; + } + else + { + bool pass = true; + if (!entryInfo.isReadable()) + { + qCritical() << "File " << realEntryPath << " is not readable."; + pass = false; + } + if (!entryInfo.isWritable()) + { + qCritical() << "File " << realEntryPath << " is not writable."; + pass = false; + } + if (!entryFile.open(QFile::ReadOnly)) + { + qCritical() << "File " << realEntryPath << " cannot be opened for reading."; + pass = false; + } + if (!pass) + { + ops.clear(); + return false; + } + } + + if(!needs_upgrade) + { + QCryptographicHash hash(QCryptographicHash::Md5); + auto foo = entryFile.readAll(); + + hash.addData(foo); + fileMD5 = hash.result().toHex(); + if ((fileMD5 != entry.md5)) + { + qDebug() << "MD5Sum does not match!"; + qDebug() << "Expected:'" << entry.md5 << "'"; + qDebug() << "Got: '" << fileMD5 << "'"; + needs_upgrade = true; + } + } + + // skip file. it doesn't need an upgrade. + if (!needs_upgrade) + { + qDebug() << "File" << realEntryPath << " does not need updating."; + continue; + } + + // yep. this file actually needs an upgrade. PROCEED. + qDebug() << "Found file" << realEntryPath << " that needs updating."; + + // Go through the sources list and find one to use. + // TODO: Make a NetAction that takes a source list and tries each of them until one + // works. For now, we'll just use the first http one. + for (FileSource source : entry.sources) + { + if (source.type != "http") + continue; + + qDebug() << "Will download" << entry.path << "from" << source.url; + + // Download it to updatedir/- where filepath is the file's + // path with slashes replaced by underscores. + QString dlPath = FS::PathCombine(tempPath, QString(entry.path).replace("/", "_")); + + // We need to download the file to the updatefiles folder and add a task + // to copy it to its install path. + auto download = Net::Download::makeFile(source.url, dlPath); + auto rawMd5 = QByteArray::fromHex(entry.md5.toLatin1()); + download->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); + job->addNetAction(download); + ops.append(Operation::CopyOp(dlPath, entry.path, entry.mode)); + } + } + return true; } bool fixPathForOSX(QString &path) { - if (path.startsWith("MultiMC.app/")) - { - // remove the prefix and add a new, more appropriate one. - path.remove(0, 12); - return true; - } - else - { - qCritical() << "Update path not within .app: " << path; - return false; - } + if (path.startsWith("MultiMC.app/")) + { + // remove the prefix and add a new, more appropriate one. + path.remove(0, 12); + return true; + } + else + { + qCritical() << "Update path not within .app: " << path; + return false; + } } } \ No newline at end of file diff --git a/api/logic/updater/GoUpdate.h b/api/logic/updater/GoUpdate.h index 95f26b8c..54559a3c 100644 --- a/api/logic/updater/GoUpdate.h +++ b/api/logic/updater/GoUpdate.h @@ -12,16 +12,16 @@ namespace GoUpdate */ struct MULTIMC_LOGIC_EXPORT Status { - bool updateAvailable = false; + bool updateAvailable = false; - int newVersionId = -1; - QString newRepoUrl; + int newVersionId = -1; + QString newRepoUrl; - int currentVersionId = -1; - QString currentRepoUrl; + int currentVersionId = -1; + QString currentRepoUrl; - // path to the root of the application - QString rootPath; + // path to the root of the application + QString rootPath; }; /** @@ -29,21 +29,21 @@ struct MULTIMC_LOGIC_EXPORT Status */ struct MULTIMC_LOGIC_EXPORT FileSource { - FileSource(QString type, QString url, QString compression="") - { - this->type = type; - this->url = url; - this->compressionType = compression; - } - - bool operator==(const FileSource &f2) const - { - return type == f2.type && url == f2.url && compressionType == f2.compressionType; - } - - QString type; - QString url; - QString compressionType; + FileSource(QString type, QString url, QString compression="") + { + this->type = type; + this->url = url; + this->compressionType = compression; + } + + bool operator==(const FileSource &f2) const + { + return type == f2.type && url == f2.url && compressionType == f2.compressionType; + } + + QString type; + QString url; + QString compressionType; }; typedef QList FileSourceList; @@ -52,14 +52,14 @@ typedef QList FileSourceList; */ struct MULTIMC_LOGIC_EXPORT VersionFileEntry { - QString path; - int mode; - FileSourceList sources; - QString md5; - bool operator==(const VersionFileEntry &v2) const - { - return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5; - } + QString path; + int mode; + FileSourceList sources; + QString md5; + bool operator==(const VersionFileEntry &v2) const + { + return path == v2.path && mode == v2.mode && sources == v2.sources && md5 == v2.md5; + } }; typedef QList VersionFileList; @@ -68,39 +68,39 @@ typedef QList VersionFileList; */ struct MULTIMC_LOGIC_EXPORT Operation { - static Operation CopyOp(QString from, QString to, int fmode=0644) - { - return Operation{OP_REPLACE, from, to, fmode}; - } - static Operation DeleteOp(QString file) - { - return Operation{OP_DELETE, QString(), file, 0644}; - } - - // FIXME: for some types, some of the other fields are irrelevant! - bool operator==(const Operation &u2) const - { - return type == u2.type && - source == u2.source && - destination == u2.destination && - destinationMode == u2.destinationMode; - } - - //! Specifies the type of operation that this is. - enum Type - { - OP_REPLACE, - OP_DELETE, - } type; - - //! The source file, if any - QString source; - - //! The destination file. - QString destination; - - //! The mode to change the destination file to. - int destinationMode; + static Operation CopyOp(QString from, QString to, int fmode=0644) + { + return Operation{OP_REPLACE, from, to, fmode}; + } + static Operation DeleteOp(QString file) + { + return Operation{OP_DELETE, QString(), file, 0644}; + } + + // FIXME: for some types, some of the other fields are irrelevant! + bool operator==(const Operation &u2) const + { + return type == u2.type && + source == u2.source && + destination == u2.destination && + destinationMode == u2.destinationMode; + } + + //! Specifies the type of operation that this is. + enum Type + { + OP_REPLACE, + OP_DELETE, + } type; + + //! The source file, if any + QString source; + + //! The destination file. + QString destination; + + //! The mode to change the destination file to. + int destinationMode; }; typedef QList OperationList; @@ -115,12 +115,12 @@ bool MULTIMC_LOGIC_EXPORT parseVersionInfo(const QByteArray &data, VersionFileLi */ bool MULTIMC_LOGIC_EXPORT processFileLists ( - const VersionFileList ¤tVersion, - const VersionFileList &newVersion, - const QString &rootPath, - const QString &tempPath, - NetJobPtr job, - OperationList &ops + const VersionFileList ¤tVersion, + const VersionFileList &newVersion, + const QString &rootPath, + const QString &tempPath, + NetJobPtr job, + OperationList &ops ); /*! @@ -133,4 +133,4 @@ bool MULTIMC_LOGIC_EXPORT processFileLists bool MULTIMC_LOGIC_EXPORT fixPathForOSX(QString &path); } -Q_DECLARE_METATYPE(GoUpdate::Status); \ No newline at end of file +Q_DECLARE_METATYPE(GoUpdate::Status) diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp index d8be2c1a..b3c2641d 100644 --- a/api/logic/updater/UpdateChecker.cpp +++ b/api/logic/updater/UpdateChecker.cpp @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,236 +25,236 @@ UpdateChecker::UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild) { - m_channelListUrl = channelListUrl; - m_currentChannel = currentChannel; - m_currentBuild = currentBuild; + m_channelListUrl = channelListUrl; + m_currentChannel = currentChannel; + m_currentBuild = currentBuild; } QList UpdateChecker::getChannelList() const { - return m_channels; + return m_channels; } bool UpdateChecker::hasChannels() const { - return !m_channels.isEmpty(); + return !m_channels.isEmpty(); } void UpdateChecker::checkForUpdate(QString updateChannel, bool notifyNoUpdate) { - qDebug() << "Checking for updates."; - - // If the channel list hasn't loaded yet, load it and defer checking for updates until - // later. - if (!m_chanListLoaded) - { - qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring " - "update check."; - m_checkUpdateWaiting = true; - m_deferredUpdateChannel = updateChannel; - updateChanList(notifyNoUpdate); - return; - } - - if (m_updateChecking) - { - qDebug() << "Ignoring update check request. Already checking for updates."; - return; - } - - m_updateChecking = true; - - // Find the desired channel within the channel list and get its repo URL. If if cannot be - // found, error. - m_newRepoUrl = ""; - for (ChannelListEntry entry : m_channels) - { - if (entry.id == updateChannel) - m_newRepoUrl = entry.url; - if (entry.id == m_currentChannel) - m_currentRepoUrl = entry.url; - } - - qDebug() << "m_repoUrl = " << m_newRepoUrl; - - // If we didn't find our channel, error. - if (m_newRepoUrl.isEmpty()) - { - qCritical() << "m_repoUrl is empty!"; - emit updateCheckFailed(); - return; - } - - QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); - - auto job = new NetJob("GoUpdate Repository Index"); - job->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); - connect(job, &NetJob::failed, this, &UpdateChecker::updateCheckFailed); - indexJob.reset(job); - job->start(); + qDebug() << "Checking for updates."; + + // If the channel list hasn't loaded yet, load it and defer checking for updates until + // later. + if (!m_chanListLoaded) + { + qDebug() << "Channel list isn't loaded yet. Loading channel list and deferring " + "update check."; + m_checkUpdateWaiting = true; + m_deferredUpdateChannel = updateChannel; + updateChanList(notifyNoUpdate); + return; + } + + if (m_updateChecking) + { + qDebug() << "Ignoring update check request. Already checking for updates."; + return; + } + + m_updateChecking = true; + + // Find the desired channel within the channel list and get its repo URL. If if cannot be + // found, error. + m_newRepoUrl = ""; + for (ChannelListEntry entry : m_channels) + { + if (entry.id == updateChannel) + m_newRepoUrl = entry.url; + if (entry.id == m_currentChannel) + m_currentRepoUrl = entry.url; + } + + qDebug() << "m_repoUrl = " << m_newRepoUrl; + + // If we didn't find our channel, error. + if (m_newRepoUrl.isEmpty()) + { + qCritical() << "m_repoUrl is empty!"; + emit updateCheckFailed(); + return; + } + + QUrl indexUrl = QUrl(m_newRepoUrl).resolved(QUrl("index.json")); + + auto job = new NetJob("GoUpdate Repository Index"); + job->addNetAction(Net::Download::makeByteArray(indexUrl, &indexData)); + connect(job, &NetJob::succeeded, [this, notifyNoUpdate](){ updateCheckFinished(notifyNoUpdate); }); + connect(job, &NetJob::failed, this, &UpdateChecker::updateCheckFailed); + indexJob.reset(job); + job->start(); } void UpdateChecker::updateCheckFinished(bool notifyNoUpdate) { - qDebug() << "Finished downloading repo index. Checking for new versions."; - - QJsonParseError jsonError; - indexJob.reset(); - - QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError); - indexData.clear(); - if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject()) - { - qCritical() << "Failed to parse GoUpdate repository index. JSON error" - << jsonError.errorString() << "at offset" << jsonError.offset; - m_updateChecking = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int apiVersion = object.value("ApiVersion").toVariant().toInt(&success); - if (apiVersion != API_VERSION || !success) - { - qCritical() << "Failed to check for updates. API version mismatch. We're using" - << API_VERSION << "server has" << apiVersion; - m_updateChecking = false; - return; - } - - qDebug() << "Processing repository version list."; - QJsonObject newestVersion; - QJsonArray versions = object.value("Versions").toArray(); - for (QJsonValue versionVal : versions) - { - QJsonObject version = versionVal.toObject(); - if (newestVersion.value("Id").toVariant().toInt() < - version.value("Id").toVariant().toInt()) - { - newestVersion = version; - } - } - - // We've got the version with the greatest ID number. Now compare it to our current build - // number and update if they're different. - int newBuildNumber = newestVersion.value("Id").toVariant().toInt(); - if (newBuildNumber != m_currentBuild) - { - qDebug() << "Found newer version with ID" << newBuildNumber; - // Update! - GoUpdate::Status updateStatus; - updateStatus.updateAvailable = true; - updateStatus.currentVersionId = m_currentBuild; - updateStatus.currentRepoUrl = m_currentRepoUrl; - updateStatus.newVersionId = newBuildNumber; - updateStatus.newRepoUrl = m_newRepoUrl; - emit updateAvailable(updateStatus); - } - else if (notifyNoUpdate) - { - emit noUpdateFound(); - } - m_updateChecking = false; + qDebug() << "Finished downloading repo index. Checking for new versions."; + + QJsonParseError jsonError; + indexJob.reset(); + + QJsonDocument jsonDoc = QJsonDocument::fromJson(indexData, &jsonError); + indexData.clear(); + if (jsonError.error != QJsonParseError::NoError || !jsonDoc.isObject()) + { + qCritical() << "Failed to parse GoUpdate repository index. JSON error" + << jsonError.errorString() << "at offset" << jsonError.offset; + m_updateChecking = false; + return; + } + + QJsonObject object = jsonDoc.object(); + + bool success = false; + int apiVersion = object.value("ApiVersion").toVariant().toInt(&success); + if (apiVersion != API_VERSION || !success) + { + qCritical() << "Failed to check for updates. API version mismatch. We're using" + << API_VERSION << "server has" << apiVersion; + m_updateChecking = false; + return; + } + + qDebug() << "Processing repository version list."; + QJsonObject newestVersion; + QJsonArray versions = object.value("Versions").toArray(); + for (QJsonValue versionVal : versions) + { + QJsonObject version = versionVal.toObject(); + if (newestVersion.value("Id").toVariant().toInt() < + version.value("Id").toVariant().toInt()) + { + newestVersion = version; + } + } + + // We've got the version with the greatest ID number. Now compare it to our current build + // number and update if they're different. + int newBuildNumber = newestVersion.value("Id").toVariant().toInt(); + if (newBuildNumber != m_currentBuild) + { + qDebug() << "Found newer version with ID" << newBuildNumber; + // Update! + GoUpdate::Status updateStatus; + updateStatus.updateAvailable = true; + updateStatus.currentVersionId = m_currentBuild; + updateStatus.currentRepoUrl = m_currentRepoUrl; + updateStatus.newVersionId = newBuildNumber; + updateStatus.newRepoUrl = m_newRepoUrl; + emit updateAvailable(updateStatus); + } + else if (notifyNoUpdate) + { + emit noUpdateFound(); + } + m_updateChecking = false; } void UpdateChecker::updateCheckFailed() { - qCritical() << "Update check failed for reasons unknown."; + qCritical() << "Update check failed for reasons unknown."; } void UpdateChecker::updateChanList(bool notifyNoUpdate) { - qDebug() << "Loading the channel list."; - - if (m_chanListLoading) - { - qDebug() << "Ignoring channel list update request. Already grabbing channel list."; - return; - } - - if (m_channelListUrl.isEmpty()) - { - qCritical() << "Failed to update channel list. No channel list URL set." - << "If you'd like to use MultiMC's update system, please pass the channel " - "list URL to CMake at compile time."; - return; - } - - m_chanListLoading = true; - NetJob *job = new NetJob("Update System Channel List"); - job->addNetAction(Net::Download::makeByteArray(QUrl(m_channelListUrl), &chanlistData)); - connect(job, &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); }); - QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed); - chanListJob.reset(job); - job->start(); + qDebug() << "Loading the channel list."; + + if (m_chanListLoading) + { + qDebug() << "Ignoring channel list update request. Already grabbing channel list."; + return; + } + + if (m_channelListUrl.isEmpty()) + { + qCritical() << "Failed to update channel list. No channel list URL set." + << "If you'd like to use MultiMC's update system, please pass the channel " + "list URL to CMake at compile time."; + return; + } + + m_chanListLoading = true; + NetJob *job = new NetJob("Update System Channel List"); + job->addNetAction(Net::Download::makeByteArray(QUrl(m_channelListUrl), &chanlistData)); + connect(job, &NetJob::succeeded, [this, notifyNoUpdate]() { chanListDownloadFinished(notifyNoUpdate); }); + QObject::connect(job, &NetJob::failed, this, &UpdateChecker::chanListDownloadFailed); + chanListJob.reset(job); + job->start(); } void UpdateChecker::chanListDownloadFinished(bool notifyNoUpdate) { - chanListJob.reset(); - - QJsonParseError jsonError; - QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError); - chanlistData.clear(); - if (jsonError.error != QJsonParseError::NoError) - { - // TODO: Report errors to the user. - qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset; - m_chanListLoading = false; - return; - } - - QJsonObject object = jsonDoc.object(); - - bool success = false; - int formatVersion = object.value("format_version").toVariant().toInt(&success); - if (formatVersion != CHANLIST_FORMAT || !success) - { - qCritical() - << "Failed to check for updates. Channel list format version mismatch. We're using" - << CHANLIST_FORMAT << "server has" << formatVersion; - m_chanListLoading = false; - return; - } - - // Load channels into a temporary array. - QList loadedChannels; - QJsonArray channelArray = object.value("channels").toArray(); - for (QJsonValue chanVal : channelArray) - { - QJsonObject channelObj = chanVal.toObject(); - ChannelListEntry entry{channelObj.value("id").toVariant().toString(), - channelObj.value("name").toVariant().toString(), - channelObj.value("description").toVariant().toString(), - channelObj.value("url").toVariant().toString()}; - if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty()) - { - qCritical() << "Channel list entry with empty ID, name, or URL. Skipping."; - continue; - } - loadedChannels.append(entry); - } - - // Swap the channel list we just loaded into the object's channel list. - m_channels.swap(loadedChannels); - - m_chanListLoading = false; - m_chanListLoaded = true; - qDebug() << "Successfully loaded UpdateChecker channel list."; - - // If we're waiting to check for updates, do that now. - if (m_checkUpdateWaiting) - checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); - - emit channelListLoaded(); + chanListJob.reset(); + + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(chanlistData, &jsonError); + chanlistData.clear(); + if (jsonError.error != QJsonParseError::NoError) + { + // TODO: Report errors to the user. + qCritical() << "Failed to parse channel list JSON:" << jsonError.errorString() << "at" << jsonError.offset; + m_chanListLoading = false; + return; + } + + QJsonObject object = jsonDoc.object(); + + bool success = false; + int formatVersion = object.value("format_version").toVariant().toInt(&success); + if (formatVersion != CHANLIST_FORMAT || !success) + { + qCritical() + << "Failed to check for updates. Channel list format version mismatch. We're using" + << CHANLIST_FORMAT << "server has" << formatVersion; + m_chanListLoading = false; + return; + } + + // Load channels into a temporary array. + QList loadedChannels; + QJsonArray channelArray = object.value("channels").toArray(); + for (QJsonValue chanVal : channelArray) + { + QJsonObject channelObj = chanVal.toObject(); + ChannelListEntry entry{channelObj.value("id").toVariant().toString(), + channelObj.value("name").toVariant().toString(), + channelObj.value("description").toVariant().toString(), + channelObj.value("url").toVariant().toString()}; + if (entry.id.isEmpty() || entry.name.isEmpty() || entry.url.isEmpty()) + { + qCritical() << "Channel list entry with empty ID, name, or URL. Skipping."; + continue; + } + loadedChannels.append(entry); + } + + // Swap the channel list we just loaded into the object's channel list. + m_channels.swap(loadedChannels); + + m_chanListLoading = false; + m_chanListLoaded = true; + qDebug() << "Successfully loaded UpdateChecker channel list."; + + // If we're waiting to check for updates, do that now. + if (m_checkUpdateWaiting) + checkForUpdate(m_deferredUpdateChannel, notifyNoUpdate); + + emit channelListLoaded(); } void UpdateChecker::chanListDownloadFailed(QString reason) { - m_chanListLoading = false; - qCritical() << QString("Failed to download channel list: %1").arg(reason); - emit channelListLoaded(); + m_chanListLoading = false; + qCritical() << QString("Failed to download channel list: %1").arg(reason); + emit channelListLoaded(); } diff --git a/api/logic/updater/UpdateChecker.h b/api/logic/updater/UpdateChecker.h index 4996da26..4ad39c0b 100644 --- a/api/logic/updater/UpdateChecker.h +++ b/api/logic/updater/UpdateChecker.h @@ -1,4 +1,4 @@ -/* Copyright 2013-2018 MultiMC Contributors +/* Copyright 2013-2019 MultiMC Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,100 +22,100 @@ class MULTIMC_LOGIC_EXPORT UpdateChecker : public QObject { - Q_OBJECT + Q_OBJECT public: - UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild); - void checkForUpdate(QString updateChannel, bool notifyNoUpdate); - - /*! - * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). - * If this isn't called before checkForUpdate(), it will automatically be called. - */ - void updateChanList(bool notifyNoUpdate); - - /*! - * An entry in the channel list. - */ - struct ChannelListEntry - { - QString id; - QString name; - QString description; - QString url; - }; - - /*! - * Returns a the current channel list. - * If the channel list hasn't been loaded, this list will be empty. - */ - QList getChannelList() const; - - /*! - * Returns false if the channel list is empty. - */ - bool hasChannels() const; + UpdateChecker(QString channelListUrl, QString currentChannel, int currentBuild); + void checkForUpdate(QString updateChannel, bool notifyNoUpdate); + + /*! + * Causes the update checker to download the channel list from the URL specified in config.h (generated by CMake). + * If this isn't called before checkForUpdate(), it will automatically be called. + */ + void updateChanList(bool notifyNoUpdate); + + /*! + * An entry in the channel list. + */ + struct ChannelListEntry + { + QString id; + QString name; + QString description; + QString url; + }; + + /*! + * Returns a the current channel list. + * If the channel list hasn't been loaded, this list will be empty. + */ + QList getChannelList() const; + + /*! + * Returns false if the channel list is empty. + */ + bool hasChannels() const; signals: - //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. - void updateAvailable(GoUpdate::Status status); + //! Signal emitted when an update is available. Passes the URL for the repo and the ID and name for the version. + void updateAvailable(GoUpdate::Status status); - //! Signal emitted when the channel list finishes loading or fails to load. - void channelListLoaded(); + //! Signal emitted when the channel list finishes loading or fails to load. + void channelListLoaded(); - void noUpdateFound(); + void noUpdateFound(); private slots: - void updateCheckFinished(bool notifyNoUpdate); - void updateCheckFailed(); + void updateCheckFinished(bool notifyNoUpdate); + void updateCheckFailed(); - void chanListDownloadFinished(bool notifyNoUpdate); - void chanListDownloadFailed(QString reason); + void chanListDownloadFinished(bool notifyNoUpdate); + void chanListDownloadFailed(QString reason); private: - friend class UpdateCheckerTest; - - NetJobPtr indexJob; - QByteArray indexData; - NetJobPtr chanListJob; - QByteArray chanlistData; - - QString m_channelListUrl; - - QList m_channels; - - /*! - * True while the system is checking for updates. - * If checkForUpdate is called while this is true, it will be ignored. - */ - bool m_updateChecking = false; - - /*! - * True if the channel list has loaded. - * If this is false, trying to check for updates will call updateChanList first. - */ - bool m_chanListLoaded = false; - - /*! - * Set to true while the channel list is currently loading. - */ - bool m_chanListLoading = false; - - /*! - * Set to true when checkForUpdate is called while the channel list isn't loaded. - * When the channel list finishes loading, if this is true, the update checker will check for updates. - */ - bool m_checkUpdateWaiting = false; - - /*! - * if m_checkUpdateWaiting, this is the last used update channel - */ - QString m_deferredUpdateChannel; - - int m_currentBuild = -1; - QString m_currentChannel; - QString m_currentRepoUrl; - - QString m_newRepoUrl; + friend class UpdateCheckerTest; + + NetJobPtr indexJob; + QByteArray indexData; + NetJobPtr chanListJob; + QByteArray chanlistData; + + QString m_channelListUrl; + + QList m_channels; + + /*! + * True while the system is checking for updates. + * If checkForUpdate is called while this is true, it will be ignored. + */ + bool m_updateChecking = false; + + /*! + * True if the channel list has loaded. + * If this is false, trying to check for updates will call updateChanList first. + */ + bool m_chanListLoaded = false; + + /*! + * Set to true while the channel list is currently loading. + */ + bool m_chanListLoading = false; + + /*! + * Set to true when checkForUpdate is called while the channel list isn't loaded. + * When the channel list finishes loading, if this is true, the update checker will check for updates. + */ + bool m_checkUpdateWaiting = false; + + /*! + * if m_checkUpdateWaiting, this is the last used update channel + */ + QString m_deferredUpdateChannel; + + int m_currentBuild = -1; + QString m_currentChannel; + QString m_currentRepoUrl; + + QString m_newRepoUrl; }; diff --git a/api/logic/updater/UpdateChecker_test.cpp b/api/logic/updater/UpdateChecker_test.cpp index 16b21614..5702d9c6 100644 --- a/api/logic/updater/UpdateChecker_test.cpp +++ b/api/logic/updater/UpdateChecker_test.cpp @@ -8,137 +8,138 @@ Q_DECLARE_METATYPE(UpdateChecker::ChannelListEntry) bool operator==(const UpdateChecker::ChannelListEntry &e1, const UpdateChecker::ChannelListEntry &e2) { - qDebug() << e1.url << "vs" << e2.url; - return e1.id == e2.id && - e1.name == e2.name && - e1.description == e2.description && - e1.url == e2.url; + qDebug() << e1.url << "vs" << e2.url; + return e1.id == e2.id && + e1.name == e2.name && + e1.description == e2.description && + e1.url == e2.url; } QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c) { - dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; - return dbg.maybeSpace(); + dbg.nospace() << "ChannelListEntry(id=" << c.id << " name=" << c.name << " description=" << c.description << " url=" << c.url << ")"; + return dbg.maybeSpace(); +} + +QString findTestDataUrl(const char *file) +{ + return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); } class UpdateCheckerTest : public QObject { - Q_OBJECT + Q_OBJECT private slots: - void initTestCase() - { - - } - void cleanupTestCase() - { - - } - - static QString findTestDataUrl(const char *file) - { - return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString(); - } - void tst_ChannelListParsing_data() - { - QTest::addColumn("channel"); - QTest::addColumn("channelUrl"); - QTest::addColumn("hasChannels"); - QTest::addColumn("valid"); - QTest::addColumn >("result"); - - QTest::newRow("garbage") - << QString() - << findTestDataUrl("data/garbageChannels.json") - << false - << false - << QList(); - QTest::newRow("errors") - << QString() - << findTestDataUrl("data/errorChannels.json") - << false - << true - << QList(); - QTest::newRow("no channels") - << QString() - << findTestDataUrl("data/noChannels.json") - << false - << true - << QList(); - QTest::newRow("one channel") - << QString("develop") - << findTestDataUrl("data/oneChannel.json") - << true - << true - << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); - QTest::newRow("several channels") - << QString("develop") - << findTestDataUrl("data/channels.json") - << true - << true - << (QList() - << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} - << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); - } - void tst_ChannelListParsing() - { - - QFETCH(QString, channel); - QFETCH(QString, channelUrl); - QFETCH(bool, hasChannels); - QFETCH(bool, valid); - QFETCH(QList, result); - - UpdateChecker checker(channelUrl, channel, 0); - - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - - if (valid) - { - QVERIFY(channelListLoadedSpy.wait()); - QCOMPARE(channelListLoadedSpy.size(), 1); - } - else - { - channelListLoadedSpy.wait(); - QCOMPARE(channelListLoadedSpy.size(), 0); - } - - QCOMPARE(checker.hasChannels(), hasChannels); - QCOMPARE(checker.getChannelList(), result); - } - - void tst_UpdateChecking() - { - QString channel = "develop"; - QString channelUrl = findTestDataUrl("data/channels.json"); - int currentBuild = 2; - - UpdateChecker checker(channelUrl, channel, currentBuild); - - QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); - QVERIFY(updateAvailableSpy.isValid()); - QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); - QVERIFY(channelListLoadedSpy.isValid()); - - checker.updateChanList(false); - QVERIFY(channelListLoadedSpy.wait()); - - qDebug() << "CWD:" << QDir::current().absolutePath(); - checker.m_channels[0].url = findTestDataUrl("data/"); - checker.checkForUpdate(channel, false); - - QVERIFY(updateAvailableSpy.wait()); - - auto status = updateAvailableSpy.first().first().value(); - QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); - QCOMPARE(3, status.newVersionId); - QCOMPARE(currentBuild, status.currentVersionId); - } + void initTestCase() + { + + } + void cleanupTestCase() + { + + } + + void tst_ChannelListParsing_data() + { + QTest::addColumn("channel"); + QTest::addColumn("channelUrl"); + QTest::addColumn("hasChannels"); + QTest::addColumn("valid"); + QTest::addColumn >("result"); + + QTest::newRow("garbage") + << QString() + << findTestDataUrl("data/garbageChannels.json") + << false + << false + << QList(); + QTest::newRow("errors") + << QString() + << findTestDataUrl("data/errorChannels.json") + << false + << true + << QList(); + QTest::newRow("no channels") + << QString() + << findTestDataUrl("data/noChannels.json") + << false + << true + << QList(); + QTest::newRow("one channel") + << QString("develop") + << findTestDataUrl("data/oneChannel.json") + << true + << true + << (QList() << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "http://example.org/stuff"}); + QTest::newRow("several channels") + << QString("develop") + << findTestDataUrl("data/channels.json") + << true + << true + << (QList() + << UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", findTestDataUrl("data")} + << UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", findTestDataUrl("data")} + << UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"}); + } + void tst_ChannelListParsing() + { + + QFETCH(QString, channel); + QFETCH(QString, channelUrl); + QFETCH(bool, hasChannels); + QFETCH(bool, valid); + QFETCH(QList, result); + + UpdateChecker checker(channelUrl, channel, 0); + + QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); + QVERIFY(channelListLoadedSpy.isValid()); + + checker.updateChanList(false); + + if (valid) + { + QVERIFY(channelListLoadedSpy.wait()); + QCOMPARE(channelListLoadedSpy.size(), 1); + } + else + { + channelListLoadedSpy.wait(); + QCOMPARE(channelListLoadedSpy.size(), 0); + } + + QCOMPARE(checker.hasChannels(), hasChannels); + QCOMPARE(checker.getChannelList(), result); + } + + void tst_UpdateChecking() + { + QString channel = "develop"; + QString channelUrl = findTestDataUrl("data/channels.json"); + int currentBuild = 2; + + UpdateChecker checker(channelUrl, channel, currentBuild); + + QSignalSpy updateAvailableSpy(&checker, SIGNAL(updateAvailable(GoUpdate::Status))); + QVERIFY(updateAvailableSpy.isValid()); + QSignalSpy channelListLoadedSpy(&checker, SIGNAL(channelListLoaded())); + QVERIFY(channelListLoadedSpy.isValid()); + + checker.updateChanList(false); + QVERIFY(channelListLoadedSpy.wait()); + + qDebug() << "CWD:" << QDir::current().absolutePath(); + checker.m_channels[0].url = findTestDataUrl("data/"); + checker.checkForUpdate(channel, false); + + QVERIFY(updateAvailableSpy.wait()); + + auto status = updateAvailableSpy.first().first().value(); + QCOMPARE(checker.m_channels[0].url, status.newRepoUrl); + QCOMPARE(3, status.newVersionId); + QCOMPARE(currentBuild, status.currentVersionId); + } }; QTEST_GUILESS_MAIN(UpdateCheckerTest) diff --git a/api/logic/updater/testdata/1.json b/api/logic/updater/testdata/1.json index 3dd189e5..7af7e52d 100644 --- a/api/logic/updater/testdata/1.json +++ b/api/logic/updater/testdata/1.json @@ -1,43 +1,43 @@ { - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneA" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "9eb84090956c484e32cb6c08455a667b" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - }, - { - "Path": "fileThree", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileThree" - } - ], - "Executable": false, - "Perms": "750", - "MD5": "f12df554b21e320be6471d7154130e70" - } - ] + "ApiVersion": 0, + "Id": 1, + "Name": "1.0.1", + "Files": [ + { + "Path": "fileOne", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileOneA" + } + ], + "Executable": true, + "Perms": 493, + "MD5": "9eb84090956c484e32cb6c08455a667b" + }, + { + "Path": "fileTwo", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileTwo" + } + ], + "Executable": false, + "Perms": 644, + "MD5": "38f94f54fa3eb72b0ea836538c10b043" + }, + { + "Path": "fileThree", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileThree" + } + ], + "Executable": false, + "Perms": "750", + "MD5": "f12df554b21e320be6471d7154130e70" + } + ] } diff --git a/api/logic/updater/testdata/2.json b/api/logic/updater/testdata/2.json index a7ba7029..96d430d5 100644 --- a/api/logic/updater/testdata/2.json +++ b/api/logic/updater/testdata/2.json @@ -1,31 +1,31 @@ { - "ApiVersion": 0, - "Id": 1, - "Name": "1.0.1", - "Files": [ - { - "Path": "fileOne", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileOneB" - } - ], - "Executable": true, - "Perms": 493, - "MD5": "42915a71277c9016668cce7b82c6b577" - }, - { - "Path": "fileTwo", - "Sources": [ - { - "SourceType": "http", - "Url": "@TEST_DATA_URL@/fileTwo" - } - ], - "Executable": false, - "Perms": 644, - "MD5": "38f94f54fa3eb72b0ea836538c10b043" - } - ] + "ApiVersion": 0, + "Id": 1, + "Name": "1.0.1", + "Files": [ + { + "Path": "fileOne", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileOneB" + } + ], + "Executable": true, + "Perms": 493, + "MD5": "42915a71277c9016668cce7b82c6b577" + }, + { + "Path": "fileTwo", + "Sources": [ + { + "SourceType": "http", + "Url": "@TEST_DATA_URL@/fileTwo" + } + ], + "Executable": false, + "Perms": 644, + "MD5": "38f94f54fa3eb72b0ea836538c10b043" + } + ] } diff --git a/api/logic/updater/testdata/channels.json b/api/logic/updater/testdata/channels.json index b46c64c8..5c6e42cb 100644 --- a/api/logic/updater/testdata/channels.json +++ b/api/logic/updater/testdata/channels.json @@ -1,23 +1,23 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "@TEST_DATA_URL@" - }, - { - "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "@TEST_DATA_URL@" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "@TEST_DATA_URL@" + }, + { + "id": "stable", + "name": "Stable", + "description": "It's stable at least", + "url": "@TEST_DATA_URL@" + }, + { + "id": "42", + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "https://dent.me/tea" + } + ] } diff --git a/api/logic/updater/testdata/errorChannels.json b/api/logic/updater/testdata/errorChannels.json index 333cd445..a2cb2165 100644 --- a/api/logic/updater/testdata/errorChannels.json +++ b/api/logic/updater/testdata/errorChannels.json @@ -1,23 +1,23 @@ { - "format_version": 0, - "channels": [ - { - "id": "", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - }, - { - "id": "stable", - "name": "", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42", - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "" - } - ] + "format_version": 0, + "channels": [ + { + "id": "", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "http://example.org/stuff" + }, + { + "id": "stable", + "name": "", + "description": "It's stable at least", + "url": "ftp://username@host/path/to/stuff" + }, + { + "id": "42", + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "" + } + ] } diff --git a/api/logic/updater/testdata/garbageChannels.json b/api/logic/updater/testdata/garbageChannels.json index 1450fb9c..34437451 100644 --- a/api/logic/updater/testdata/garbageChannels.json +++ b/api/logic/updater/testdata/garbageChannels.json @@ -1,22 +1,22 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", -aa "url": "http://example.org/stuff" - }, -a "id": "stable", - "name": "Stable", - "description": "It's stable at least", - "url": "ftp://username@host/path/to/stuff" - }, - { - "id": "42"f - "name": "The Channel", - "description": "This is the channel that is going to answer all of your questions", - "url": "https://dent.me/tea" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", +aa "url": "http://example.org/stuff" + }, +a "id": "stable", + "name": "Stable", + "description": "It's stable at least", + "url": "ftp://username@host/path/to/stuff" + }, + { + "id": "42"f + "name": "The Channel", + "description": "This is the channel that is going to answer all of your questions", + "url": "https://dent.me/tea" + } + ] } diff --git a/api/logic/updater/testdata/index.json b/api/logic/updater/testdata/index.json index 20ceb9f4..867bdcfb 100644 --- a/api/logic/updater/testdata/index.json +++ b/api/logic/updater/testdata/index.json @@ -1,9 +1,9 @@ { - "ApiVersion": 0, - "Versions": [ - { "Id": 0, "Name": "1.0.0" }, - { "Id": 1, "Name": "1.0.1" }, - { "Id": 2, "Name": "1.0.2" }, - { "Id": 3, "Name": "1.0.3" } - ] + "ApiVersion": 0, + "Versions": [ + { "Id": 0, "Name": "1.0.0" }, + { "Id": 1, "Name": "1.0.1" }, + { "Id": 2, "Name": "1.0.2" }, + { "Id": 3, "Name": "1.0.3" } + ] } diff --git a/api/logic/updater/testdata/noChannels.json b/api/logic/updater/testdata/noChannels.json index bbb2cb70..76988982 100644 --- a/api/logic/updater/testdata/noChannels.json +++ b/api/logic/updater/testdata/noChannels.json @@ -1,5 +1,5 @@ { - "format_version": 0, - "channels": [ - ] + "format_version": 0, + "channels": [ + ] } diff --git a/api/logic/updater/testdata/oneChannel.json b/api/logic/updater/testdata/oneChannel.json index 84727ac7..cc8ed255 100644 --- a/api/logic/updater/testdata/oneChannel.json +++ b/api/logic/updater/testdata/oneChannel.json @@ -1,11 +1,11 @@ { - "format_version": 0, - "channels": [ - { - "id": "develop", - "name": "Develop", - "description": "The channel called \"develop\"", - "url": "http://example.org/stuff" - } - ] + "format_version": 0, + "channels": [ + { + "id": "develop", + "name": "Develop", + "description": "The channel called \"develop\"", + "url": "http://example.org/stuff" + } + ] } -- cgit v1.2.3