aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--buildconfig/BuildConfig.h3
-rw-r--r--launcher/Application.cpp16
-rw-r--r--launcher/Application.h1
-rw-r--r--launcher/CMakeLists.txt16
-rw-r--r--launcher/InstanceImportTask.cpp250
-rw-r--r--launcher/InstanceImportTask.h51
-rw-r--r--launcher/MMCZip.cpp28
-rw-r--r--launcher/UpdateController.cpp14
-rw-r--r--launcher/main.cpp2
-rw-r--r--launcher/minecraft/AssetsUtils.cpp42
-rw-r--r--launcher/minecraft/GradleSpecifier.h2
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp6
-rw-r--r--launcher/minecraft/VersionFile.cpp3
-rw-r--r--launcher/minecraft/mod/LocalModParseTask.cpp54
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp16
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp9
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp10
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h1
-rw-r--r--launcher/modplatform/flame/PackManifest.cpp15
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h40
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp17
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h17
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp156
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h107
-rw-r--r--launcher/modplatform/technic/SolderPackManifest.cpp2
-rw-r--r--launcher/net/ByteArraySink.h95
-rw-r--r--launcher/net/ChecksumValidator.h83
-rw-r--r--launcher/net/Download.cpp152
-rw-r--r--launcher/net/Download.h101
-rw-r--r--launcher/net/FileSink.cpp122
-rw-r--r--launcher/net/FileSink.h77
-rw-r--r--launcher/net/HttpMetaCache.cpp224
-rw-r--r--launcher/net/HttpMetaCache.h148
-rw-r--r--launcher/net/MetaCacheSink.cpp57
-rw-r--r--launcher/net/MetaCacheSink.h62
-rw-r--r--launcher/net/Mode.h9
-rw-r--r--launcher/net/NetAction.h134
-rw-r--r--launcher/net/NetJob.cpp274
-rw-r--r--launcher/net/NetJob.h110
-rw-r--r--launcher/net/PasteUpload.cpp34
-rw-r--r--launcher/net/PasteUpload.h35
-rw-r--r--launcher/net/Sink.h102
-rw-r--r--launcher/net/Validator.h34
-rw-r--r--launcher/resources/multimc/128x128/instances/flame.pngbin3375 -> 6226 bytes
-rw-r--r--launcher/resources/multimc/128x128/instances/modrinth.pngbin10575 -> 0 bytes
-rw-r--r--launcher/resources/multimc/32x32/instances/flame.pngbin849 -> 0 bytes
-rw-r--r--launcher/resources/multimc/32x32/instances/modrinth.pngbin1913 -> 0 bytes
-rw-r--r--launcher/resources/multimc/multimc.qrc10
-rw-r--r--launcher/resources/multimc/scalable/instances/modrinth.svg4
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.cpp60
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.h49
-rw-r--r--launcher/screenshots/ImgurUpload.cpp60
-rw-r--r--launcher/screenshots/ImgurUpload.h39
-rw-r--r--launcher/tasks/Task.cpp42
-rw-r--r--launcher/tasks/Task.h55
-rw-r--r--launcher/translations/TranslationsModel.cpp37
-rw-r--r--launcher/ui/MainWindow.cpp3
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp8
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp4
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h4
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp2
-rw-r--r--launcher/ui/pages/global/APIPage.cpp4
-rw-r--r--launcher/ui/pages/global/APIPage.ui64
-rw-r--r--launcher/ui/pages/global/ExternalToolsPage.cpp2
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.cpp27
-rw-r--r--launcher/ui/pages/instance/ScreenshotsPage.h1
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.cpp8
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui78
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui192
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h43
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp82
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h61
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp291
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h123
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp241
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h60
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui115
-rw-r--r--libraries/launcher/CMakeLists.txt15
-rw-r--r--libraries/launcher/net/minecraft/Launcher.java112
-rw-r--r--libraries/launcher/org/multimc/EntryPoint.java105
-rw-r--r--libraries/launcher/org/multimc/Launcher.java7
-rw-r--r--libraries/launcher/org/multimc/LauncherFactory.java63
-rw-r--r--libraries/launcher/org/multimc/LegacyFrame.java176
-rw-r--r--libraries/launcher/org/multimc/Utils.java86
-rw-r--r--libraries/launcher/org/multimc/applet/LegacyFrame.java162
-rw-r--r--libraries/launcher/org/multimc/exception/ParameterNotFoundException.java (renamed from libraries/launcher/org/multimc/NotFoundException.java)10
-rw-r--r--libraries/launcher/org/multimc/exception/ParseException.java (renamed from libraries/launcher/org/multimc/ParseException.java)8
-rw-r--r--libraries/launcher/org/multimc/impl/OneSixLauncher.java189
-rw-r--r--libraries/launcher/org/multimc/onesix/OneSixLauncher.java256
-rw-r--r--libraries/launcher/org/multimc/utils/Parameters.java (renamed from libraries/launcher/org/multimc/ParamBucket.java)47
-rw-r--r--libraries/launcher/org/multimc/utils/Utils.java49
93 files changed, 4021 insertions, 1811 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4d3683d7..e07d2aa6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.9.4)
+cmake_minimum_required(VERSION 3.15) # minimum version required by QuaZip
if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
@@ -43,6 +43,8 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
# Fix build with Qt 5.13
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
+
option(ENABLE_LTO "Enable Link Time Optimization" off)
if(ENABLE_LTO)
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index a920a3d4..8594e46d 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -151,6 +151,9 @@ class Config {
*/
QString TECHNIC_API_BUILD = "multimc";
+ QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
+ QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
+
/**
* \brief Converts the Version to a string.
* \return The version number in string format (major.minor.revision.build).
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 11109857..ce62c41a 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -223,9 +223,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(BuildConfig.LAUNCHER_DISPLAYNAME);
setApplicationVersion(BuildConfig.printableVersionString());
- #if (QT_VERSION >= QT_VERSION_CHECK(5,7,0))
- setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
- #endif
+ setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
// Don't quit on hiding the last window
@@ -681,6 +679,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Custom MSA credentials
m_settings->registerSetting("MSAClientIDOverride", "");
+ m_settings->registerSetting("CFKeyOverride", "");
// Init page provider
{
@@ -819,6 +818,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
+ m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
@@ -1509,3 +1509,13 @@ QString Application::getMSAClientID()
return BuildConfig.MSA_CLIENT_ID;
}
+
+QString Application::getCurseKey()
+{
+ QString keyOverride = m_settings->get("CFKeyOverride").toString();
+ if (!keyOverride.isEmpty()) {
+ return keyOverride;
+ }
+
+ return BuildConfig.CURSEFORGE_API_KEY;
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 172321c0..3129b4fb 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -155,6 +155,7 @@ public:
QString getJarsPath();
QString getMSAClientID();
+ QString getCurseKey();
/// this is the root of the 'installation'. Used for automatic updates
const QString &root() {
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index b79f03c8..8e75be20 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -532,6 +532,8 @@ set(FLAME_SOURCES
set(MODRINTH_SOURCES
modplatform/modrinth/ModrinthPackIndex.cpp
modplatform/modrinth/ModrinthPackIndex.h
+ modplatform/modrinth/ModrinthPackManifest.cpp
+ modplatform/modrinth/ModrinthPackManifest.h
)
set(MODPACKSCH_SOURCES
@@ -774,6 +776,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/flame/FlameModPage.cpp
ui/pages/modplatform/flame/FlameModPage.h
+ ui/pages/modplatform/modrinth/ModrinthPage.cpp
+ ui/pages/modplatform/modrinth/ModrinthPage.h
+ ui/pages/modplatform/modrinth/ModrinthModel.cpp
+ ui/pages/modplatform/modrinth/ModrinthModel.h
+
ui/pages/modplatform/technic/TechnicModel.cpp
ui/pages/modplatform/technic/TechnicModel.h
ui/pages/modplatform/technic/TechnicPage.cpp
@@ -782,10 +789,10 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/ImportPage.cpp
ui/pages/modplatform/ImportPage.h
- ui/pages/modplatform/modrinth/ModrinthModel.cpp
- ui/pages/modplatform/modrinth/ModrinthModel.h
- ui/pages/modplatform/modrinth/ModrinthPage.cpp
- ui/pages/modplatform/modrinth/ModrinthPage.h
+ ui/pages/modplatform/modrinth/ModrinthModModel.cpp
+ ui/pages/modplatform/modrinth/ModrinthModModel.h
+ ui/pages/modplatform/modrinth/ModrinthModPage.cpp
+ ui/pages/modplatform/modrinth/ModrinthModPage.h
# GUI - dialogs
ui/dialogs/AboutDialog.cpp
@@ -908,6 +915,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/ftb/FtbPage.ui
+ ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 1a13c997..4bad7251 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -1,43 +1,73 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "InstanceImportTask.h"
+#include <QtConcurrentRun>
+#include "Application.h"
#include "BaseInstance.h"
#include "FileSystem.h"
-#include "Application.h"
#include "MMCZip.h"
#include "NullInstance.h"
-#include "settings/INISettingsObject.h"
+#include "icons/IconList.h"
#include "icons/IconUtils.h"
-#include <QtConcurrentRun>
+#include "settings/INISettingsObject.h"
// FIXME: this does not belong here, it's Minecraft/Flame specific
+#include <quazip/quazipdir.h>
+#include "Json.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h"
-#include "Json.h"
-#include <quazip/quazipdir.h>
+#include "modplatform/modrinth/ModrinthPackManifest.h"
#include "modplatform/technic/TechnicPackProcessor.h"
-#include "icons/IconList.h"
#include "Application.h"
+#include "icons/IconList.h"
+#include "net/ChecksumValidator.h"
+
+#include "ui/dialogs/CustomMessageBox.h"
+
+#include <algorithm>
+#include <iterator>
-InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
+InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent)
{
m_sourceUrl = sourceUrl;
+ m_parent = parent;
}
bool InstanceImportTask::abort()
@@ -109,6 +139,7 @@ void InstanceImportTask::processZipPack()
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
+ QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json");
QString root;
if(!mmcFound.isNull())
{
@@ -132,6 +163,13 @@ void InstanceImportTask::processZipPack()
root = flameFound;
m_modpackType = ModpackType::Flame;
}
+ else if(!modrinthFound.isNull())
+ {
+ // process as Modrinth pack
+ qDebug() << "Modrinth:" << modrinthFound;
+ root = modrinthFound;
+ m_modpackType = ModpackType::Modrinth;
+ }
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
@@ -188,15 +226,18 @@ void InstanceImportTask::extractFinished()
switch(m_modpackType)
{
- case ModpackType::Flame:
- processFlame();
- return;
case ModpackType::MultiMC:
processMultiMC();
return;
case ModpackType::Technic:
processTechnic();
return;
+ case ModpackType::Flame:
+ processFlame();
+ return;
+ case ModpackType::Modrinth:
+ processModrinth();
+ return;
case ModpackType::Unknown:
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
@@ -439,25 +480,174 @@ void InstanceImportTask::processMultiMC()
instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack
- if (m_instIcon != "default")
- {
+ if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
- }
- else
- {
+ } else {
m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
- if (!importIconPath.isNull() && QFile::exists(importIconPath))
- {
+ if (!importIconPath.isNull() && QFile::exists(importIconPath)) {
// import icon
auto iconList = APPLICATION->icons();
- if (iconList->iconFileExists(m_instIcon))
- {
+ if (iconList->iconFileExists(m_instIcon)) {
iconList->deleteIcon(m_instIcon);
}
- iconList->installIcons({importIconPath});
+ iconList->installIcons({ importIconPath });
}
}
emitSucceeded();
}
+
+void InstanceImportTask::processModrinth()
+{
+ std::vector<Modrinth::File> files;
+ QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion;
+ try {
+ QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
+ auto doc = Json::requireDocument(indexPath);
+ auto obj = Json::requireObject(doc, "modrinth.index.json");
+ int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
+ if (formatVersion == 1) {
+ auto game = Json::requireString(obj, "game", "modrinth.index.json");
+ if (game != "minecraft") {
+ throw JSONValidationError("Unknown game: " + game);
+ }
+
+ auto jsonFiles = Json::requireIsArrayOf<QJsonObject>(obj, "files", "modrinth.index.json");
+ bool had_optional = false;
+ for (auto& obj : jsonFiles) {
+ Modrinth::File file;
+ file.path = Json::requireString(obj, "path");
+
+ auto env = Json::ensureObject(obj, "env");
+ QString support = Json::ensureString(env, "client", "unsupported");
+ if (support == "unsupported") {
+ continue;
+ } else if (support == "optional") {
+ // TODO: Make a review dialog for choosing which ones the user wants!
+ if (!had_optional) {
+ had_optional = true;
+ auto info = CustomMessageBox::selectable(
+ m_parent, tr("Optional mod detected!"),
+ tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information);
+ info->exec();
+ }
+
+ if (file.path.endsWith(".jar"))
+ file.path += ".disabled";
+ }
+
+ QJsonObject hashes = Json::requireObject(obj, "hashes");
+ QString hash;
+ QCryptographicHash::Algorithm hashAlgorithm;
+ hash = Json::ensureString(hashes, "sha1");
+ hashAlgorithm = QCryptographicHash::Sha1;
+ if (hash.isEmpty()) {
+ hash = Json::ensureString(hashes, "sha512");
+ hashAlgorithm = QCryptographicHash::Sha512;
+ if (hash.isEmpty()) {
+ hash = Json::ensureString(hashes, "sha256");
+ hashAlgorithm = QCryptographicHash::Sha256;
+ if (hash.isEmpty()) {
+ throw JSONValidationError("No hash found for: " + file.path);
+ }
+ }
+ }
+ file.hash = QByteArray::fromHex(hash.toLatin1());
+ file.hashAlgorithm = hashAlgorithm;
+ // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
+ // (as Modrinth seems to incorrectly handle spaces)
+ file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path);
+ if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) {
+ throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL");
+ }
+ files.push_back(file);
+ }
+
+ auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
+ for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
+ QString name = it.key();
+ if (name == "minecraft") {
+ minecraftVersion = Json::requireString(*it, "Minecraft version");
+ }
+ else if (name == "fabric-loader") {
+ fabricVersion = Json::requireString(*it, "Fabric Loader version");
+ }
+ else if (name == "quilt-loader") {
+ quiltVersion = Json::requireString(*it, "Quilt Loader version");
+ }
+ else if (name == "forge") {
+ forgeVersion = Json::requireString(*it, "Forge version");
+ }
+ else {
+ throw JSONValidationError("Unknown dependency type: " + name);
+ }
+ }
+ } else {
+ throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion));
+ }
+ QFile::remove(indexPath);
+ } catch (const JSONValidationError& e) {
+ emitFailed(tr("Could not understand pack index:\n") + e.cause());
+ return;
+ }
+
+ QString overridePath = FS::PathCombine(m_stagingPath, "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") + "overrides");
+ return;
+ }
+ }
+
+ QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
+ MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = instance.getPackProfile();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", minecraftVersion, true);
+ if (!fabricVersion.isEmpty())
+ components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true);
+ if (!quiltVersion.isEmpty())
+ components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true);
+ if (!forgeVersion.isEmpty())
+ components->setComponentVersion("net.minecraftforge", forgeVersion, true);
+ if (m_instIcon != "default")
+ {
+ instance.setIconKey(m_instIcon);
+ }
+ else
+ {
+ instance.setIconKey("modrinth");
+ }
+ instance.setName(m_instName);
+ instance.saveNow();
+
+ m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network());
+ for (auto &file : files)
+ {
+ auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path);
+ qDebug() << "Will download" << file.download << "to" << path;
+ auto dl = Net::Download::makeFile(file.download, path);
+ dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
+ m_filesNetJob->addNetAction(dl);
+ }
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
+ {
+ m_filesNetJob.reset();
+ emitSucceeded();
+ }
+ );
+ connect(m_filesNetJob.get(), &NetJob::failed, [&](const 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();
+}
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 365c3dc4..5e4d3235 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
- * 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
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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
@@ -35,7 +55,7 @@ class InstanceImportTask : public InstanceTask
{
Q_OBJECT
public:
- explicit InstanceImportTask(const QUrl sourceUrl);
+ explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr);
bool canAbort() const override { return true; }
bool abort() override;
@@ -47,8 +67,9 @@ protected:
private:
void processZipPack();
void processMultiMC();
- void processFlame();
void processTechnic();
+ void processFlame();
+ void processModrinth();
private slots:
void downloadSucceeded();
@@ -69,7 +90,11 @@ private: /* data */
enum class ModpackType{
Unknown,
MultiMC,
+ Technic,
Flame,
- Technic
+ Modrinth,
} m_modpackType = ModpackType::Unknown;
+
+ //FIXME: nuke
+ QWidget* m_parent;
};
diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp
index b92f1781..8591fcc0 100644
--- a/launcher/MMCZip.cpp
+++ b/launcher/MMCZip.cpp
@@ -297,20 +297,40 @@ nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString &
{
continue;
}
+
name.remove(0, subdir.size());
- QString absFilePath = directory.absoluteFilePath(name);
+ auto original_name = name;
+
+ // Fix weird "folders with a single file get squashed" thing
+ QString path;
+ if(name.contains('/') && !name.endsWith('/')){
+ path = name.section('/', 0, -2) + "/";
+ FS::ensureFolderPathExists(path);
+
+ name = name.split('/').last();
+ }
+
+ QString absFilePath;
if(name.isEmpty())
{
- absFilePath += "/";
+ absFilePath = directory.absoluteFilePath(name) + "/";
}
+ else
+ {
+ absFilePath = directory.absoluteFilePath(path + name);
+ }
+
if (!JlCompress::extractFile(zip, "", absFilePath))
{
- qWarning() << "Failed to extract file" << name << "to" << absFilePath;
+ qWarning() << "Failed to extract file" << original_name << "to" << absFilePath;
JlCompress::removeFile(extracted);
return nonstd::nullopt;
}
+
extracted.append(absFilePath);
- qDebug() << "Extracted file" << name;
+ QFile::setPermissions(absFilePath, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser);
+
+ qDebug() << "Extracted file" << name << "to" << absFilePath;
} while (zip->goToNextFile());
return extracted;
}
diff --git a/launcher/UpdateController.cpp b/launcher/UpdateController.cpp
index c02cd1e7..646f8e57 100644
--- a/launcher/UpdateController.cpp
+++ b/launcher/UpdateController.cpp
@@ -138,20 +138,6 @@ void UpdateController::installUpdates()
}
#endif
QFileInfo destination (FS::PathCombine(m_root, op.destination));
-#ifdef Q_OS_WIN32
- if(QSysInfo::windowsVersion() < QSysInfo::WV_VISTA)
- {
- if(destination.fileName() == windowsExeName)
- {
- QDir rootDir(m_root);
- exeOrigin = rootDir.relativeFilePath(op.source);
- exePath = rootDir.relativeFilePath(op.destination);
- exeBackup = rootDir.relativeFilePath(FS::PathCombine(backupPath, destination.fileName()));
- useXPHack = true;
- continue;
- }
- }
-#endif
if(destination.exists())
{
QString backupName = op.destination;
diff --git a/launcher/main.cpp b/launcher/main.cpp
index 275fff32..85c5fdee 100644
--- a/launcher/main.cpp
+++ b/launcher/main.cpp
@@ -24,10 +24,8 @@ int main(int argc, char *argv[])
return 42;
#endif
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
-#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp
index 7290aeb4..15062c2b 100644
--- a/launcher/minecraft/AssetsUtils.cpp
+++ b/launcher/minecraft/AssetsUtils.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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
+ * 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
+ * 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.
+ * 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 <QFileInfo>
@@ -297,7 +317,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
- objectDL->m_total_progress = size;
+ objectDL->setProgress(objectDL->getProgress(), size);
return objectDL;
}
return nullptr;
diff --git a/launcher/minecraft/GradleSpecifier.h b/launcher/minecraft/GradleSpecifier.h
index 60e0a726..d9bb0207 100644
--- a/launcher/minecraft/GradleSpecifier.h
+++ b/launcher/minecraft/GradleSpecifier.h
@@ -124,7 +124,7 @@ struct GradleSpecifier
}
bool matchName(const GradleSpecifier & other) const
{
- return other.artifactId() == artifactId() && other.groupId() == groupId();
+ return other.artifactId() == artifactId() && other.groupId() == groupId() && other.classifier() == classifier();
}
bool operator==(const GradleSpecifier & other) const
{
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index e20dc24c..61326fac 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -487,9 +488,8 @@ QStringList MinecraftInstance::processMinecraftArgs(
}
}
- // blatant self-promotion.
- token_mapping["profile_name"] = token_mapping["version_name"] = BuildConfig.LAUNCHER_NAME;
-
+ token_mapping["profile_name"] = name();
+ token_mapping["version_name"] = profile->getMinecraftVersion();
token_mapping["version_type"] = profile->getMinecraftVersionType();
QString absRootDir = QDir(gameRoot()).absolutePath();
diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp
index 9db30ba2..f242fbe7 100644
--- a/launcher/minecraft/VersionFile.cpp
+++ b/launcher/minecraft/VersionFile.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -55,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile)
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
{
- profile->applyMinecraftVersion(minecraftVersion);
+ profile->applyMinecraftVersion(version);
profile->applyMinecraftVersionType(type);
// HACK: ignore assets from other version files than Minecraft
// workaround for stupid assets issue caused by amazon:
diff --git a/launcher/minecraft/mod/LocalModParseTask.cpp b/launcher/minecraft/mod/LocalModParseTask.cpp
index f01da8ae..a7bec5ae 100644
--- a/launcher/minecraft/mod/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/LocalModParseTask.cpp
@@ -8,6 +8,7 @@
#include <quazip/quazipfile.h>
#include <toml.h>
+#include "Json.h"
#include "settings/INIFile.h"
#include "FileSystem.h"
@@ -262,6 +263,44 @@ std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
return details;
}
+// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md
+std::shared_ptr<ModDetails> ReadQuiltModInfo(QByteArray contents)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = Json::requireObject(jsonDoc, "quilt.mod.json");
+ auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version");
+
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+
+ // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md
+ if (schemaVersion == 1)
+ {
+ auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info");
+
+ details->mod_id = Json::requireString(modInfo.value("id"), "Mod ID");
+ details->version = Json::requireString(modInfo.value("version"), "Mod version");
+
+ auto modMetadata = Json::ensureObject(modInfo.value("metadata"));
+
+ details->name = Json::ensureString(modMetadata.value("name"), details->mod_id);
+ details->description = Json::ensureString(modMetadata.value("description"));
+
+ auto modContributors = Json::ensureObject(modMetadata.value("contributors"));
+
+ // We don't really care about the role of a contributor here
+ details->authors += modContributors.keys();
+
+ auto modContact = Json::ensureObject(modMetadata.value("contact"));
+
+ if (modContact.contains("homepage"))
+ {
+ details->homeurl = Json::requireString(modContact.value("homepage"));
+ }
+ }
+ return details;
+}
+
std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
{
std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
@@ -391,7 +430,20 @@ void LocalModParseTask::processAsZip()
zip.close();
return;
}
- else if (zip.setCurrentFile("fabric.mod.json")) // TODO: Support quilt.mod.json
+ else if (zip.setCurrentFile("quilt.mod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadQuiltModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("fabric.mod.json"))
{
if (!file.open(QIODevice::ReadOnly))
{
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 95924a68..0deb99c4 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -31,7 +31,21 @@ void Flame::FileResolvingTask::netJobFinished()
for (auto& bytes : results) {
auto& out = m_toProcess.files[index];
try {
- failed &= (!out.parseFromBytes(bytes));
+ bool fail = (!out.parseFromBytes(bytes));
+ if(fail){
+ //failed :( probably disabled mod, try to add to the list
+ auto doc = Json::requireDocument(bytes);
+ if (!doc.isObject()) {
+ throw JSONValidationError(QString("data is not an object? that's not supposed to happen"));
+ }
+ auto obj = Json::ensureObject(doc.object(), "data");
+ //FIXME : HACK, MAY NOT WORK FOR LONG
+ out.url = QUrl(QString("https://media.forgecdn.net/files/%1/%2/%3")
+ .arg(QString::number(QString::number(out.fileId).leftRef(4).toInt())
+ ,QString::number(QString::number(out.fileId).rightRef(3).toInt())
+ ,QUrl::toPercentEncoding(out.fileName)), QUrl::TolerantMode);
+ }
+ failed &= fail;
} catch (const JSONValidationError& e) {
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index ba0824cf..9846b156 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -56,8 +56,15 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
file.fileId = Json::requireInteger(obj, "id");
file.date = Json::requireString(obj, "fileDate");
file.version = Json::requireString(obj, "displayName");
- file.downloadUrl = Json::requireString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
+ file.downloadUrl = Json::ensureString(obj, "downloadUrl", "");
+ if(file.downloadUrl.isEmpty()){
+ //FIXME : HACK, MAY NOT WORK FOR LONG
+ file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3")
+ .arg(QString::number(QString::number(file.fileId.toInt()).leftRef(4).toInt())
+ ,QString::number(QString::number(file.fileId.toInt()).rightRef(3).toInt())
+ ,QUrl::toPercentEncoding(file.fileName));
+ }
unsortedVersions.append(file);
}
diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp
index ac24c647..6d48a3bf 100644
--- a/launcher/modplatform/flame/FlamePackIndex.cpp
+++ b/launcher/modplatform/flame/FlamePackIndex.cpp
@@ -65,7 +65,15 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName");
- file.downloadUrl = Json::requireString(version, "downloadUrl");
+ file.fileName = Json::requireString(version, "fileName");
+ file.downloadUrl = Json::ensureString(version, "downloadUrl");
+ if(file.downloadUrl.isEmpty()){
+ //FIXME : HACK, MAY NOT WORK FOR LONG
+ file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3")
+ .arg(QString::number(QString::number(file.fileId).leftRef(4).toInt())
+ ,QString::number(QString::number(file.fileId).rightRef(3).toInt())
+ ,QUrl::toPercentEncoding(file.fileName));
+ }
unsortedVersions.append(file);
}
diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h
index 7ffa29c3..a8bb15be 100644
--- a/launcher/modplatform/flame/FlamePackIndex.h
+++ b/launcher/modplatform/flame/FlamePackIndex.h
@@ -18,6 +18,7 @@ struct IndexedVersion {
QString version;
QString mcVersion;
QString downloadUrl;
+ QString fileName;
};
struct IndexedPack
diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp
index e4f90c1a..3217a756 100644
--- a/launcher/modplatform/flame/PackManifest.cpp
+++ b/launcher/modplatform/flame/PackManifest.cpp
@@ -71,11 +71,6 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
fileName = Json::requireString(obj, "fileName");
- 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
type = File::Type::SingleFile;
@@ -87,7 +82,17 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
// this is probably a mod, dunno what else could modpacks download
targetFolder = "mods";
}
+ QString rawUrl = Json::ensureString(obj, "downloadUrl");
+ if(rawUrl.isEmpty()){
+ //either there somehow is an emtpy string as a link, or it's null either way it's invalid
+ //soft failing
+ return false;
+ }
+ url = QUrl(rawUrl, QUrl::TolerantMode);
+ if (!url.isValid()) {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
resolved = true;
return true;
}
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 86852c94..6d642b5e 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -1,5 +1,24 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
#pragma once
+#include "BuildConfig.h"
#include "modplatform/ModAPI.h"
#include "modplatform/helpers/NetworkModAPI.h"
@@ -47,13 +66,13 @@ class ModrinthAPI : public NetworkModAPI {
return "";
}
- return QString(
- "https://api.modrinth.com/v2/search?"
- "offset=%1&"
- "limit=25&"
- "query=%2&"
- "index=%3&"
- "facets=[[%4],%5[\"project_type:mod\"]]")
+ return QString(BuildConfig.MODRINTH_PROD_URL +
+ "/search?"
+ "offset=%1&"
+ "limit=25&"
+ "query=%2&"
+ "index=%3&"
+ "facets=[[%4],%5[\"project_type:mod\"]]")
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
@@ -63,9 +82,10 @@ class ModrinthAPI : public NetworkModAPI {
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
{
- return QString("https://api.modrinth.com/v2/project/%1/version?"
- "game_versions=[%2]"
- "loaders=[\"%3\"]")
+ return QString(BuildConfig.MODRINTH_PROD_URL +
+ "/project/%1/version?"
+ "game_versions=[%2]"
+ "loaders=[\"%3\"]")
.arg(args.addonId)
.arg(getGameVersionsString(args.mcVersions))
.arg(getModLoaderStrings(args.loader).join("\",\""));
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index a3c2f166..f7fa9864 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -1,3 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
#include "ModrinthPackIndex.h"
#include "ModrinthAPI.h"
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index fd17847a..7f306f25 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -1,3 +1,20 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
#pragma once
#include "modplatform/ModIndex.h"
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
new file mode 100644
index 00000000..f1ad39ce
--- /dev/null
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2022 kb1000
+ *
+ * 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 "ModrinthPackManifest.h"
+#include "Json.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+static ModrinthAPI api;
+
+namespace Modrinth {
+
+void loadIndexedPack(Modpack& pack, QJsonObject& obj)
+{
+ pack.id = Json::ensureString(obj, "project_id");
+
+ pack.name = Json::ensureString(obj, "title");
+ pack.description = Json::ensureString(obj, "description");
+ auto temp_author_name = Json::ensureString(obj, "author");
+ pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name));
+ pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug"));
+ pack.iconUrl = Json::ensureString(obj, "icon_url");
+}
+
+void loadIndexedInfo(Modpack& pack, QJsonObject& obj)
+{
+ pack.extra.body = Json::ensureString(obj, "body");
+ pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug"));
+ pack.extra.sourceUrl = Json::ensureString(obj, "source_url");
+ pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url");
+
+ pack.extraInfoLoaded = true;
+}
+
+void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
+{
+ QVector<ModpackVersion> unsortedVersions;
+
+ auto arr = Json::requireArray(doc);
+
+ for (auto versionIter : arr) {
+ auto obj = Json::requireObject(versionIter);
+ auto file = loadIndexedVersion(obj);
+
+ if(!file.id.isEmpty()) // Heuristic to check if the returned value is valid
+ unsortedVersions.append(file);
+ }
+ auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
+ // dates are in RFC 3339 format
+ return a.date > b.date;
+ };
+
+ std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
+
+ pack.versions.swap(unsortedVersions);
+
+ pack.versionsLoaded = true;
+}
+
+auto validateDownloadUrl(QUrl url) -> bool
+{
+ auto domain = url.host();
+ if(domain == "cdn.modrinth.com")
+ return true;
+ if(domain == "edge.forgecdn.net")
+ return true;
+ if(domain == "media.forgecdn.net")
+ return true;
+ if(domain == "github.com")
+ return true;
+ if(domain == "raw.githubusercontent.com")
+ return true;
+
+ return false;
+}
+
+auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion
+{
+ ModpackVersion file;
+
+ file.name = Json::requireString(obj, "name");
+ file.version = Json::requireString(obj, "version_number");
+
+ file.id = Json::requireString(obj, "id");
+ file.project_id = Json::requireString(obj, "project_id");
+
+ file.date = Json::requireString(obj, "date_published");
+
+ auto files = Json::requireArray(obj, "files");
+
+
+ for (auto file_iter : files) {
+ File indexed_file;
+ auto parent = Json::requireObject(file_iter);
+ auto is_primary = Json::ensureBoolean(parent, "primary", false);
+ if (!is_primary) {
+ auto filename = Json::ensureString(parent, "filename");
+ // Checking suffix here is fine because it's the response from Modrinth,
+ // so one would assume it will always be in English.
+ if(!filename.endsWith("mrpack") && !filename.endsWith("zip"))
+ continue;
+ }
+
+ auto url = Json::requireString(parent, "url");
+
+ if(!validateDownloadUrl(url))
+ continue;
+
+ file.download_url = url;
+ if(is_primary)
+ break;
+ }
+
+ if(file.download_url.isEmpty())
+ return {};
+
+ return file;
+}
+
+} // namespace Modrinth
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h
new file mode 100644
index 00000000..e5fc9a70
--- /dev/null
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2022 kb1000
+ *
+ * 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 <QMetaType>
+
+#include <QByteArray>
+#include <QCryptographicHash>
+#include <QString>
+#include <QUrl>
+#include <QVector>
+
+class MinecraftInstance;
+
+namespace Modrinth {
+
+struct File
+{
+ QString path;
+
+ QCryptographicHash::Algorithm hashAlgorithm;
+ QByteArray hash;
+ // TODO: should this support multiple download URLs, like the JSON does?
+ QUrl download;
+};
+
+struct ModpackExtra {
+ QString body;
+
+ QString projectUrl;
+ QString sourceUrl;
+ QString wikiUrl;
+};
+
+struct ModpackVersion {
+ QString name;
+ QString version;
+
+ QString id;
+ QString project_id;
+
+ QString date;
+
+ QString download_url;
+};
+
+struct Modpack {
+ QString id;
+
+ QString name;
+ QString description;
+ std::tuple<QString, QUrl> author;
+ QString iconName;
+ QUrl iconUrl;
+
+ bool versionsLoaded = false;
+ bool extraInfoLoaded = false;
+
+ ModpackExtra extra;
+ QVector<ModpackVersion> versions;
+};
+
+void loadIndexedPack(Modpack&, QJsonObject&);
+void loadIndexedInfo(Modpack&, QJsonObject&);
+void loadIndexedVersions(Modpack&, QJsonDocument&);
+auto loadIndexedVersion(QJsonObject&) -> ModpackVersion;
+
+auto validateDownloadUrl(QUrl) -> bool;
+
+}
+
+Q_DECLARE_METATYPE(Modrinth::Modpack)
+Q_DECLARE_METATYPE(Modrinth::ModpackVersion)
diff --git a/launcher/modplatform/technic/SolderPackManifest.cpp b/launcher/modplatform/technic/SolderPackManifest.cpp
index 16fe0b0e..e52a7ec0 100644
--- a/launcher/modplatform/technic/SolderPackManifest.cpp
+++ b/launcher/modplatform/technic/SolderPackManifest.cpp
@@ -37,7 +37,7 @@ void loadPack(Pack& v, QJsonObject& obj)
static void loadPackBuildMod(PackBuildMod& b, QJsonObject& obj)
{
b.name = Json::requireString(obj, "name");
- b.version = Json::requireString(obj, "version");
+ b.version = Json::ensureString(obj, "version", "");
b.md5 = Json::requireString(obj, "md5");
b.url = Json::requireString(obj, "url");
}
diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h
index 20e6764c..501318a1 100644
--- a/launcher/net/ByteArraySink.h
+++ b/launcher/net/ByteArraySink.h
@@ -1,62 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "Sink.h"
namespace Net {
+
/*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
+ * FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
+ * causing a segmentation fault.
*/
-class ByteArraySink : public Sink
-{
-public:
- ByteArraySink(QByteArray *output)
- :m_output(output)
- {
- // nil
- };
+class ByteArraySink : public Sink {
+ public:
+ ByteArraySink(QByteArray* output) : m_output(output){};
- virtual ~ByteArraySink()
- {
- // nil
- }
+ virtual ~ByteArraySink() = default;
-public:
- JobStatus init(QNetworkRequest & request) override
+ public:
+ auto init(QNetworkRequest& request) -> Task::State override
{
m_output->clear();
- if(initAllValidators(request))
- return Job_InProgress;
- return Job_Failed;
+ if (initAllValidators(request))
+ return Task::State::Running;
+ return Task::State::Failed;
};
- JobStatus write(QByteArray & data) override
+ auto write(QByteArray& data) -> Task::State override
{
m_output->append(data);
- if(writeAllValidators(data))
- return Job_InProgress;
- return Job_Failed;
+ if (writeAllValidators(data))
+ return Task::State::Running;
+ return Task::State::Failed;
}
- JobStatus abort() override
+ auto abort() -> Task::State override
{
m_output->clear();
failAllValidators();
- return Job_Failed;
+ return Task::State::Failed;
}
- JobStatus finalize(QNetworkReply &reply) override
+ auto finalize(QNetworkReply& reply) -> Task::State override
{
- if(finalizeAllValidators(reply))
- return Job_Finished;
- return Job_Failed;
+ if (finalizeAllValidators(reply))
+ return Task::State::Succeeded;
+ return Task::State::Failed;
}
- bool hasLocalData() override
- {
- return false;
- }
+ auto hasLocalData() -> bool override { return false; }
-private:
- QByteArray * m_output;
+ private:
+ QByteArray* m_output;
};
-}
+} // namespace Net
diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h
index 0d6b19c2..a2ca2c7a 100644
--- a/launcher/net/ChecksumValidator.h
+++ b/launcher/net/ChecksumValidator.h
@@ -1,55 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "Validator.h"
+
#include <QCryptographicHash>
-#include <memory>
#include <QFile>
namespace Net {
-class ChecksumValidator: public Validator
-{
-public: /* con/des */
+class ChecksumValidator : public Validator {
+ public:
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
- :m_checksum(algorithm), m_expected(expected)
- {
- };
- virtual ~ChecksumValidator() {};
+ : m_checksum(algorithm), m_expected(expected){};
+ virtual ~ChecksumValidator() = default;
-public: /* methods */
- bool init(QNetworkRequest &) override
+ public:
+ auto init(QNetworkRequest&) -> bool override
{
m_checksum.reset();
return true;
}
- bool write(QByteArray & data) override
+
+ auto write(QByteArray& data) -> bool override
{
m_checksum.addData(data);
return true;
}
- bool abort() override
- {
- return true;
- }
- bool validate(QNetworkReply &) override
+
+ auto abort() -> bool override { return true; }
+
+ auto validate(QNetworkReply&) -> bool override
{
- if(m_expected.size() && m_expected != hash())
- {
+ 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 */
+ auto hash() -> QByteArray { return m_checksum.result(); }
+
+ void setExpected(QByteArray expected) { m_expected = expected; }
+
+ private:
QCryptographicHash m_checksum;
QByteArray m_expected;
};
-} \ No newline at end of file
+} // namespace Net
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index 65cc8f67..966d4126 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -1,22 +1,41 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "Download.h"
#include <QDateTime>
-#include <QDebug>
#include <QFileInfo>
#include "ByteArraySink.h"
@@ -25,38 +44,38 @@
#include "MetaCacheSink.h"
#include "BuildConfig.h"
+#include "Application.h"
namespace Net {
Download::Download() : NetAction()
{
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options)
+auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* 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 dl;
}
-Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options)
+auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* dl = new Download();
dl->m_url = url;
dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output));
return dl;
}
-Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
+auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
{
- Download* dl = new Download();
+ auto* dl = new Download();
dl->m_url = url;
dl->m_options = options;
dl->m_sink.reset(new FileSink(path));
@@ -68,42 +87,45 @@ void Download::addValidator(Validator* v)
m_sink->addValidator(v);
}
-void Download::startImpl()
+void Download::executeTask()
{
- if (m_status == Job_Aborted) {
+ setStatus(tr("Downloading %1").arg(m_url.toString()));
+
+ if (getState() == Task::State::AbortedByUser) {
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
- emit aborted(m_index_within_job);
+ emitAborted();
return;
}
+
QNetworkRequest request(m_url);
- m_status = m_sink->init(request);
- switch (m_status) {
- case Job_Finished:
- emit succeeded(m_index_within_job);
+ m_state = m_sink->init(request);
+ switch (m_state) {
+ case State::Succeeded:
+ emit succeeded();
qDebug() << "Download cache hit " << m_url.toString();
return;
- case Job_InProgress:
+ case State::Running:
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);
+ case State::Inactive:
+ case State::Failed:
+ emitFailed();
return;
- case Job_Aborted:
+ case State::AbortedByUser:
+ emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
if (request.url().host().contains("api.curseforge.com")) {
- request.setRawHeader("x-api-key", BuildConfig.CURSEFORGE_API_KEY.toUtf8());
+ request.setRawHeader("x-api-key", APPLICATION->getCurseKey().toUtf8());
};
QNetworkReply* rep = m_network->get(request);
m_reply.reset(rep);
- connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
- connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
+ connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
+ connect(rep, &QNetworkReply::finished, this, &Download::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);
@@ -111,26 +133,24 @@ void Download::startImpl()
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
}
void Download::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCritical() << "Aborted " << m_url.toString();
- m_status = Job_Aborted;
+ m_state = State::AbortedByUser;
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
- m_status = Job_Failed_Proceed;
+ m_state = State::Succeeded;
return;
}
}
// error happened during download.
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
- m_status = Job_Failed;
+ m_state = State::Failed;
}
}
@@ -145,7 +165,7 @@ void Download::sslErrors(const QList<QSslError>& errors)
}
}
-bool Download::handleRedirect()
+auto Download::handleRedirect() -> bool
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
@@ -194,7 +214,8 @@ bool Download::handleRedirect()
m_url = QUrl(redirect.toString());
qDebug() << "Following redirect to " << m_url.toString();
- start(m_network);
+ startAction(m_network);
+
return true;
}
@@ -207,74 +228,71 @@ void Download::downloadFinished()
}
// if the download failed before this point ...
- if (m_status == Job_Failed_Proceed) {
+ if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
+ {
qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit succeeded(m_index_within_job);
+ emit succeeded();
return;
- } else if (m_status == Job_Failed) {
+ } else if (m_state == State::Failed) {
qDebug() << "Download failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit failed(m_index_within_job);
+ emit failed("");
return;
- } else if (m_status == Job_Aborted) {
+ } else if (m_state == State::AbortedByUser) {
qDebug() << "Download aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit aborted(m_index_within_job);
+ emit aborted();
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);
+ qDebug() << "Writing extra" << data.size() << "bytes";
+ m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
- m_status = m_sink->finalize(*m_reply.get());
- if (m_status != Job_Finished) {
+ m_state = m_sink->finalize(*m_reply.get());
+ if (m_state != State::Succeeded) {
qDebug() << "Download failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
- emit failed(m_index_within_job);
+ emit failed("");
return;
}
+
m_reply.reset();
qDebug() << "Download succeeded:" << m_url.toString();
- emit succeeded(m_index_within_job);
+ emit succeeded();
}
void Download::downloadReadyRead()
{
- if (m_status == Job_InProgress) {
+ if (m_state == State::Running) {
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;
+ m_state = m_sink->write(data);
+ if (m_state == State::Failed) {
+ qCritical() << "Failed to process response chunk";
}
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
} else {
- qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status;
+ qCritical() << "Cannot write download data! illegal status " << m_status;
}
}
} // namespace Net
-bool Net::Download::abort()
+auto Net::Download::abort() -> bool
{
if (m_reply) {
m_reply->abort();
} else {
- m_status = Job_Aborted;
+ m_state = State::AbortedByUser;
}
return true;
}
-
-bool Net::Download::canAbort()
-{
- return true;
-}
diff --git a/launcher/net/Download.h b/launcher/net/Download.h
index 0f9bfe7f..20932944 100644
--- a/launcher/net/Download.h
+++ b/launcher/net/Download.h
@@ -1,77 +1,88 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "NetAction.h"
#include "HttpMetaCache.h"
-#include "Validator.h"
+#include "NetAction.h"
#include "Sink.h"
+#include "Validator.h"
#include "QObjectPtr.h"
namespace Net {
-class Download : public NetAction
-{
+class Download : public NetAction {
Q_OBJECT
-public: /* types */
- typedef shared_qobject_ptr<class Download> Ptr;
- enum class Option
- {
- NoOptions = 0,
- AcceptLocalFiles = 1
- };
+ public:
+ using Ptr = shared_qobject_ptr<class Download>;
+ enum class Option { NoOptions = 0, AcceptLocalFiles = 1 };
Q_DECLARE_FLAGS(Options, Option)
-protected: /* con/des */
+ protected:
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);
-public: /* methods */
- QString getTargetFilepath()
- {
- return m_target_path;
- }
- void addValidator(Validator * v);
- bool abort() override;
- bool canAbort() override;
+ public:
+ ~Download() override = default;
+
+ static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr;
+ static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
+
+ public:
+ void addValidator(Validator* v);
+ auto abort() -> bool override;
+ auto canAbort() const -> bool override { return true; };
-private: /* methods */
- bool handleRedirect();
+ private:
+ auto handleRedirect() -> bool;
-protected slots:
+ protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
- void sslErrors(const QList<QSslError> & errors);
+ void sslErrors(const QList<QSslError>& errors);
void downloadFinished() override;
void downloadReadyRead() override;
-public slots:
- void startImpl() override;
+ public slots:
+ void executeTask() override;
-private: /* data */
- // FIXME: remove this, it has no business being here.
- QString m_target_path;
+ private:
std::unique_ptr<Sink> m_sink;
Options m_options;
};
-}
+} // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp
index 7e9b8929..ba0caf6c 100644
--- a/launcher/net/FileSink.cpp
+++ b/launcher/net/FileSink.cpp
@@ -1,109 +1,131 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "FileSink.h"
-#include <QFile>
-#include <QFileInfo>
+
#include "FileSystem.h"
namespace Net {
-FileSink::FileSink(QString filename)
- :m_filename(filename)
-{
- // nil
-}
-
-FileSink::~FileSink()
-{
- // nil
-}
-
-JobStatus FileSink::init(QNetworkRequest& request)
+Task::State FileSink::init(QNetworkRequest& request)
{
auto result = initCache(request);
- if(result != Job_InProgress)
- {
+ if (result != Task::State::Running) {
return result;
}
+
// create a new save file and open it for writing
- if (!FS::ensureFilePathExists(m_filename))
- {
+ if (!FS::ensureFilePathExists(m_filename)) {
qCritical() << "Could not create folder for " + m_filename;
- return Job_Failed;
+ return Task::State::Failed;
}
+
wroteAnyData = false;
m_output_file.reset(new QSaveFile(m_filename));
- if (!m_output_file->open(QIODevice::WriteOnly))
- {
+ if (!m_output_file->open(QIODevice::WriteOnly)) {
qCritical() << "Could not open " + m_filename + " for writing";
- return Job_Failed;
+ return Task::State::Failed;
}
- if(initAllValidators(request))
- return Job_InProgress;
- return Job_Failed;
+ if (initAllValidators(request))
+ return Task::State::Running;
+ return Task::State::Failed;
}
-JobStatus FileSink::initCache(QNetworkRequest &)
+Task::State FileSink::write(QByteArray& data)
{
- return Job_InProgress;
-}
-
-JobStatus FileSink::write(QByteArray& data)
-{
- if (!writeAllValidators(data) || m_output_file->write(data) != data.size())
- {
+ 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;
+ return Task::State::Failed;
}
+
wroteAnyData = true;
- return Job_InProgress;
+ return Task::State::Running;
}
-JobStatus FileSink::abort()
+Task::State FileSink::abort()
{
m_output_file->cancelWriting();
failAllValidators();
- return Job_Failed;
+ return Task::State::Failed;
}
-JobStatus FileSink::finalize(QNetworkReply& reply)
+Task::State FileSink::finalize(QNetworkReply& reply)
{
bool gotFile = false;
QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
bool validStatus = false;
int statusCode = statusCodeV.toInt(&validStatus);
- if(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)
- {
+ 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;
+ if (!finalizeAllValidators(reply))
+ return Task::State::Failed;
+
// nothing went wrong...
- if (!m_output_file->commit())
- {
+ if (!m_output_file->commit()) {
qCritical() << "Failed to commit changes to " << m_filename;
m_output_file->cancelWriting();
- return Job_Failed;
+ return Task::State::Failed;
}
}
+
// then get rid of the save file
m_output_file.reset();
return finalizeCache(reply);
}
-JobStatus FileSink::finalizeCache(QNetworkReply &)
+Task::State FileSink::initCache(QNetworkRequest&)
{
- return Job_Finished;
+ return Task::State::Running;
+}
+
+Task::State FileSink::finalizeCache(QNetworkReply&)
+{
+ return Task::State::Succeeded;
}
bool FileSink::hasLocalData()
@@ -111,4 +133,4 @@ bool FileSink::hasLocalData()
QFileInfo info(m_filename);
return info.exists() && info.size() != 0;
}
-}
+} // namespace Net
diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h
index 875fe511..dffbdca6 100644
--- a/launcher/net/FileSink.h
+++ b/launcher/net/FileSink.h
@@ -1,28 +1,65 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "Sink.h"
+
#include <QSaveFile>
+#include "Sink.h"
+
namespace Net {
-class FileSink : public Sink
-{
-public: /* con/des */
- 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;
-
-protected: /* methods */
- virtual JobStatus initCache(QNetworkRequest &);
- virtual JobStatus finalizeCache(QNetworkReply &reply);
-
-protected: /* data */
+class FileSink : public Sink {
+ public:
+ FileSink(QString filename) : m_filename(filename){};
+ virtual ~FileSink() = default;
+
+ public:
+ auto init(QNetworkRequest& request) -> Task::State override;
+ auto write(QByteArray& data) -> Task::State override;
+ auto abort() -> Task::State override;
+ auto finalize(QNetworkReply& reply) -> Task::State override;
+
+ auto hasLocalData() -> bool override;
+
+ protected:
+ virtual auto initCache(QNetworkRequest&) -> Task::State;
+ virtual auto finalizeCache(QNetworkReply& reply) -> Task::State;
+
+ protected:
QString m_filename;
bool wroteAnyData = false;
std::unique_ptr<QSaveFile> m_output_file;
};
-}
+} // namespace Net
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 8734e0bf..4d86c0b8 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -1,43 +1,60 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "HttpMetaCache.h"
#include "FileSystem.h"
+#include "Json.h"
-#include <QFileInfo>
-#include <QFile>
-#include <QDateTime>
#include <QCryptographicHash>
+#include <QDateTime>
+#include <QFile>
+#include <QFileInfo>
#include <QDebug>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QJsonObject>
-
-QString MetaEntry::getFullPath()
+auto MetaEntry::getFullPath() -> QString
{
// FIXME: make local?
return FS::PathCombine(basePath, relativePath);
}
-HttpMetaCache::HttpMetaCache(QString path) : QObject()
+HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
{
- m_index_file = path;
saveBatchingTimer.setSingleShot(true);
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
+
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
}
@@ -47,45 +64,42 @@ HttpMetaCache::~HttpMetaCache()
SaveNow();
}
-MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path)
+auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPtr
{
// no base. no base path. can't store
- if (!m_entries.contains(base))
- {
+ if (!m_entries.contains(base)) {
// TODO: log problem
- return MetaEntryPtr();
+ return {};
}
- EntryMap &map = m_entries[base];
- if (map.entry_list.contains(resource_path))
- {
+
+ EntryMap& map = m_entries[base];
+ if (map.entry_list.contains(resource_path)) {
return map.entry_list[resource_path];
}
- return MetaEntryPtr();
+
+ return {};
}
-MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag)
+auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
{
auto entry = getEntry(base, resource_path);
// it's not present? generate a default stale entry
- if (!entry)
- {
+ if (!entry) {
return staleEntry(base, resource_path);
}
- auto &selected_base = m_entries[base];
+ 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 (!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 (!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);
@@ -93,18 +107,15 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
// if the file changed, check md5sum
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
- if (file_last_changed != entry->local_changed_timestamp)
- {
+ 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)
- {
+ 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();
@@ -115,42 +126,42 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
return entry;
}
-bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry)
+auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{
- if (!m_entries.contains(stale_entry->baseId))
- {
- qCritical() << "Cannot add entry with unknown base: "
- << stale_entry->baseId.toLocal8Bit();
+ if (!m_entries.contains(stale_entry->baseId)) {
+ qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
return false;
}
- if (stale_entry->stale)
- {
+
+ 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)
+auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
{
- if(entry)
- {
- entry->stale = true;
- SaveEventually();
- return true;
- }
- return false;
+ if (!entry)
+ return false;
+
+ entry->stale = true;
+ SaveEventually();
+ return true;
}
-MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path)
+auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{
auto foo = new MetaEntry();
foo->baseId = base;
foo->basePath = getBasePath(base);
foo->relativePath = resource_path;
foo->stale = true;
+
return MetaEntryPtr(foo);
}
@@ -159,24 +170,25 @@ void HttpMetaCache::addBase(QString base, QString base_root)
// TODO: report error
if (m_entries.contains(base))
return;
+
// TODO: check if the base path is valid
EntryMap foo;
foo.base_path = base_root;
m_entries[base] = foo;
}
-QString HttpMetaCache::getBasePath(QString base)
+auto HttpMetaCache::getBasePath(QString base) -> QString
{
- if (m_entries.contains(base))
- {
+ if (m_entries.contains(base)) {
return m_entries[base].base_path;
}
- return QString();
+
+ return {};
}
void HttpMetaCache::Load()
{
- if(m_index_file.isNull())
+ if (m_index_file.isNull())
return;
QFile index(m_index_file);
@@ -184,41 +196,35 @@ void HttpMetaCache::Load()
return;
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
- if (!json.isObject())
- return;
- auto root = json.object();
+
+ auto root = Json::requireObject(json, "HttpMetaCache root");
+
// check file version first
- auto version_val = root.value("version");
- if (!version_val.isString())
- return;
- if (version_val.toString() != "1")
+ auto version_val = Json::ensureString(root, "version");
+ if (version_val != "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();
+ auto array = Json::ensureArray(root, "entries");
+ for (auto element : array) {
+ auto element_obj = Json::ensureObject(element);
+ auto base = Json::ensureString(element_obj, "base");
if (!m_entries.contains(base))
continue;
- auto &entrymap = m_entries[base];
+
+ 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();
+ foo->relativePath = Json::ensureString(element_obj, "path");
+ foo->md5sum = Json::ensureString(element_obj, "md5sum");
+ foo->etag = Json::ensureString(element_obj, "etag");
+ foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
+ foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
// presumed innocent until closer examination
foo->stale = false;
- entrymap.entry_list[path] = MetaEntryPtr(foo);
+
+ entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
}
}
@@ -231,42 +237,36 @@ void HttpMetaCache::SaveEventually()
void HttpMetaCache::SaveNow()
{
- if(m_index_file.isNull())
+ if (m_index_file.isNull())
return;
+
QJsonObject toplevel;
- toplevel.insert("version", QJsonValue(QString("1")));
+ Json::writeString(toplevel, "version", "1");
+
QJsonArray entriesArr;
- for (auto group : m_entries)
- {
- for (auto entry : group.entry_list)
- {
+ for (auto group : m_entries) {
+ for (auto entry : group.entry_list) {
// do not save stale entries. they are dead.
- if(entry->stale)
- {
+ 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)));
+ Json::writeString(entryObj, "base", entry->baseId);
+ Json::writeString(entryObj, "path", entry->relativePath);
+ Json::writeString(entryObj, "md5sum", entry->md5sum);
+ Json::writeString(entryObj, "etag", 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));
+ 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 (const Exception &e)
- {
+ try {
+ Json::write(toplevel, m_index_file);
+ } catch (const Exception& e) {
qWarning() << e.what();
}
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index 1c10e8c7..e944b3d5 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -1,122 +1,122 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 <QString>
+
#include <QMap>
-#include <qtimer.h>
+#include <QString>
+#include <QTimer>
#include <memory>
class HttpMetaCache;
-class MetaEntry
-{
-friend class HttpMetaCache;
-protected:
- 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;
- }
-protected:
+class MetaEntry {
+ friend class HttpMetaCache;
+
+ protected:
+ MetaEntry() = default;
+
+ public:
+ auto isStale() -> bool { return stale; }
+ void setStale(bool stale) { this->stale = stale; }
+
+ auto getFullPath() -> QString;
+
+ auto getRemoteChangedTimestamp() -> QString { 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; }
+
+ auto getETag() -> QString { return etag; }
+ void setETag(QString etag) { this->etag = etag; }
+
+ auto getMD5Sum() -> QString { 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
+ QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
bool stale = true;
};
-typedef std::shared_ptr<MetaEntry> MetaEntryPtr;
+using MetaEntryPtr = std::shared_ptr<MetaEntry>;
-class HttpMetaCache : public QObject
-{
+class HttpMetaCache : public QObject {
Q_OBJECT
-public:
+ public:
// supply path to the cache index file
HttpMetaCache(QString path = QString());
- ~HttpMetaCache();
+ ~HttpMetaCache() override;
// 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);
+ auto getEntry(QString base, QString resource_path) -> MetaEntryPtr;
// 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());
+ auto resolveEntry(QString base, QString resource_path, QString expected_etag = QString()) -> MetaEntryPtr;
// add a previously resolved stale entry
- bool updateEntry(MetaEntryPtr stale_entry);
+ auto updateEntry(MetaEntryPtr stale_entry) -> bool;
// evict selected entry from cache
- bool evictEntry(MetaEntryPtr entry);
+ auto evictEntry(MetaEntryPtr entry) -> bool;
void addBase(QString base, QString base_root);
// (re)start a timer that calls SaveNow later.
void SaveEventually();
void Load();
- QString getBasePath(QString base);
-public
-slots:
+
+ auto getBasePath(QString base) -> QString;
+
+ public slots:
void SaveNow();
-private:
+ private:
// create a new stale entry, given the parameters
- MetaEntryPtr staleEntry(QString base, QString resource_path);
- struct EntryMap
- {
+ auto staleEntry(QString base, QString resource_path) -> MetaEntryPtr;
+
+ struct EntryMap {
QString base_path;
QMap<QString, MetaEntryPtr> entry_list;
};
+
QMap<QString, EntryMap> m_entries;
QString m_index_file;
QTimer saveBatchingTimer;
diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp
index 5cdf0460..f86dd870 100644
--- a/launcher/net/MetaCacheSink.cpp
+++ b/launcher/net/MetaCacheSink.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "MetaCacheSink.h"
#include <QFile>
#include <QFileInfo>
@@ -12,17 +47,13 @@ MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
addValidator(md5sum);
}
-MetaCacheSink::~MetaCacheSink()
-{
- // nil
-}
-
-JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
+Task::State MetaCacheSink::initCache(QNetworkRequest& request)
{
if (!m_entry->isStale())
{
- return Job_Finished;
+ return Task::State::Succeeded;
}
+
// check if file exists, if it does, use its information for the request
QFile current(m_filename);
if(current.exists() && current.size() != 0)
@@ -36,25 +67,31 @@ JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
}
}
- return Job_InProgress;
+
+ return Task::State::Running;
}
-JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply)
+Task::State 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);
APPLICATION->metacache()->updateEntry(m_entry);
- return Job_Finished;
+
+ return Task::State::Succeeded;
}
bool MetaCacheSink::hasLocalData()
diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h
index edcf7ad1..c9f7edfe 100644
--- a/launcher/net/MetaCacheSink.h
+++ b/launcher/net/MetaCacheSink.h
@@ -1,22 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "FileSink.h"
+
#include "ChecksumValidator.h"
+#include "FileSink.h"
#include "net/HttpMetaCache.h"
namespace Net {
-class MetaCacheSink : public FileSink
-{
-public: /* con/des */
- MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum);
- virtual ~MetaCacheSink();
- bool hasLocalData() override;
+class MetaCacheSink : public FileSink {
+ public:
+ MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum);
+ virtual ~MetaCacheSink() = default;
+
+ auto hasLocalData() -> bool override;
-protected: /* methods */
- JobStatus initCache(QNetworkRequest & request) override;
- JobStatus finalizeCache(QNetworkReply & reply) override;
+ protected:
+ auto initCache(QNetworkRequest& request) -> Task::State override;
+ auto finalizeCache(QNetworkReply& reply) -> Task::State override;
-private: /* data */
+ private:
MetaEntryPtr m_entry;
- ChecksumValidator * m_md5Node;
+ ChecksumValidator* m_md5Node;
};
-}
+} // namespace Net
diff --git a/launcher/net/Mode.h b/launcher/net/Mode.h
index 9a95f5ad..3d75981f 100644
--- a/launcher/net/Mode.h
+++ b/launcher/net/Mode.h
@@ -1,10 +1,5 @@
#pragma once
-namespace Net
-{
-enum class Mode
-{
- Offline,
- Online
-};
+namespace Net {
+enum class Mode { Offline, Online };
}
diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h
index efb20953..729d4132 100644
--- a/launcher/net/NetAction.h
+++ b/launcher/net/NetAction.h
@@ -1,108 +1,76 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
-#include <QObject>
-#include <QUrl>
-#include <memory>
#include <QNetworkReply>
-#include <QObjectPtr.h>
+#include <QUrl>
-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
-};
+#include "QObjectPtr.h"
+#include "tasks/Task.h"
-class NetAction : public QObject
-{
+class NetAction : public Task {
Q_OBJECT
-protected:
- explicit NetAction() : QObject(nullptr) {};
+ protected:
+ explicit NetAction() : Task() {};
-public:
+ public:
using Ptr = shared_qobject_ptr<NetAction>;
- virtual ~NetAction() {};
+ virtual ~NetAction() = default;
- 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;
- }
+ QUrl url() { return m_url; }
+ auto index() -> int { return m_index_within_job; }
- 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);
-
-protected slots:
+ protected slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
-public slots:
- void start(shared_qobject_ptr<QNetworkAccessManager> network) {
+ public slots:
+ void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
+ {
m_network = network;
- startImpl();
+ executeTask();
}
-protected:
- virtual void startImpl() = 0;
+ protected:
+ void executeTask() override {};
-public:
+ public:
shared_qobject_ptr<QNetworkAccessManager> m_network;
/// index within the parent job, FIXME: nuke
@@ -113,10 +81,4 @@ public:
/// source URL
QUrl m_url;
-
- qint64 m_progress = 0;
- qint64 m_total_progress = 1;
-
-protected:
- JobStatus m_status = Job_NotStarted;
};
diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp
index 9bad89ed..df899178 100644
--- a/launcher/net/NetJob.cpp
+++ b/launcher/net/NetJob.cpp
@@ -1,79 +1,173 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "NetJob.h"
#include "Download.h"
-#include <QDebug>
+auto NetJob::addNetAction(NetAction::Ptr action) -> bool
+{
+ action->m_index_within_job = m_downloads.size();
+ m_downloads.append(action);
+ part_info pi;
+ m_parts_progress.append(pi);
+
+ partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress());
+
+ if (action->isRunning()) {
+ connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); });
+ connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); });
+ connect(action.get(), &NetAction::aborted, [this, action](){ partAborted(action->index()); });
+ connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); });
+ connect(action.get(), &NetAction::status, this, &NetJob::status);
+ } else {
+ m_todo.append(m_parts_progress.size() - 1);
+ }
+
+ return true;
+}
+
+auto NetJob::canAbort() const -> bool
+{
+ bool canFullyAbort = true;
+
+ // can abort the downloads on the queue?
+ for (auto index : m_todo) {
+ auto part = m_downloads[index];
+ canFullyAbort &= part->canAbort();
+ }
+ // can abort the active downloads?
+ for (auto index : m_doing) {
+ auto part = m_downloads[index];
+ canFullyAbort &= part->canAbort();
+ }
+
+ return canFullyAbort;
+}
+
+void NetJob::executeTask()
+{
+ // hack that delays early failures so they can be caught easier
+ QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
+}
+
+auto NetJob::getFailedFiles() -> QStringList
+{
+ QStringList failed;
+ for (auto index : m_failed) {
+ failed.push_back(m_downloads[index]->url().toString());
+ }
+ failed.sort();
+ return failed;
+}
+
+auto NetJob::abort() -> bool
+{
+ bool fullyAborted = true;
+
+ // fail all downloads on the queue
+ m_failed.unite(m_todo.toSet());
+ m_todo.clear();
+
+ // abort active downloads
+ auto toKill = m_doing.toList();
+ for (auto index : toKill) {
+ auto part = m_downloads[index];
+ fullyAborted &= part->abort();
+ }
+
+ return fullyAborted;
+}
void NetJob::partSucceeded(int index)
{
// do progress. all slots are 1 in size at least
- auto &slot = parts_progress[index];
+ auto& slot = m_parts_progress[index];
partProgress(index, slot.total_progress, slot.total_progress);
m_doing.remove(index);
m_done.insert(index);
- downloads[index].get()->disconnect(this);
+ m_downloads[index].get()->disconnect(this);
+
startMoreParts();
}
void NetJob::partFailed(int index)
{
m_doing.remove(index);
- auto &slot = parts_progress[index];
- if (slot.failures == 3)
- {
+
+ auto& slot = m_parts_progress[index];
+ // Can try 3 times before failing by definitive
+ if (slot.failures == 3) {
m_failed.insert(index);
- }
- else
- {
+ } else {
slot.failures++;
m_todo.enqueue(index);
}
- downloads[index].get()->disconnect(this);
+
+ m_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);
+ m_downloads[index].get()->disconnect(this);
+
startMoreParts();
}
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
{
- auto &slot = parts_progress[index];
+ auto& slot = m_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();
+ int all = m_parts_progress.size();
qint64 bytesAll = 0;
qint64 bytesTotalAll = 0;
- for(auto & partIdx: m_doing)
- {
- auto part = parts_progress[partIdx];
+ for (auto& partIdx : m_doing) {
+ auto part = m_parts_progress[partIdx];
// do not count parts with unknown/nonsensical total size
- if(part.total_progress <= 0)
- {
+ if (part.total_progress <= 0) {
continue;
}
bytesAll += part.current_progress;
@@ -85,134 +179,54 @@ void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
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) {
+ if (m_current_progress == 1000) {
m_current_progress = inprogress;
}
- if(m_current_progress > current)
- {
+ 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);
-}
-
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.
+ if (!isRunning()) {
+ // this actually makes sense. You can put running m_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())
- {
+ if (!m_todo.size()) {
+ if (!m_doing.size()) {
+ if (!m_failed.size()) {
emitSucceeded();
- }
- else if(m_aborted)
- {
+ } else if (m_aborted) {
emitAborted();
- }
- else
- {
+ } 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())
+
+ // There's work to do, try to start more parts, to a maximum of 6 concurrent ones.
+ while (m_doing.size() < 6) {
+ if (m_todo.size() == 0)
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(m_network);
- }
-}
-
-QStringList NetJob::getFailedFiles()
-{
- QStringList failed;
- for (auto index: m_failed)
- {
- failed.push_back(downloads[index]->url().toString());
- }
- failed.sort();
- return failed;
-}
+ auto part = m_downloads[doThis];
-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 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;
-}
+ // connect signals :D
+ connect(part.get(), &NetAction::succeeded, this, [this, part]{ partSucceeded(part->index()); });
+ connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); });
+ connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); });
+ connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); });
+ connect(part.get(), &NetAction::status, this, &NetJob::status);
-bool NetJob::addNetAction(NetAction::Ptr 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)));
+ part->startAction(m_network);
}
- else
- {
- m_todo.append(parts_progress.size() - 1);
- }
- return true;
}
-
-NetJob::~NetJob() = default;
diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h
index fdea710f..63c1cf51 100644
--- a/launcher/net/NetJob.h
+++ b/launcher/net/NetJob.h
@@ -1,88 +1,98 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 <QtNetwork>
+
+#include <QObject>
#include "NetAction.h"
-#include "Download.h"
-#include "HttpMetaCache.h"
#include "tasks/Task.h"
-#include "QObjectPtr.h"
-class NetJob;
+// Those are included so that they are also included by anyone using NetJob
+#include "net/Download.h"
+#include "net/HttpMetaCache.h"
-class NetJob : public Task
-{
+class NetJob : public Task {
Q_OBJECT
-public:
+
+ public:
using Ptr = shared_qobject_ptr<NetJob>;
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : Task(), m_network(network)
{
setObjectName(job_name);
}
- virtual ~NetJob();
+ virtual ~NetJob() = default;
- bool addNetAction(NetAction::Ptr action);
+ void executeTask() override;
- NetAction::Ptr operator[](int index)
- {
- return downloads[index];
- }
- const NetAction::Ptr at(const int index)
- {
- return downloads.at(index);
- }
- NetAction::Ptr first()
- {
- if (downloads.size())
- return downloads[0];
- return NetAction::Ptr();
- }
- int size() const
- {
- return downloads.size();
- }
- QStringList getFailedFiles();
+ auto canAbort() const -> bool override;
- bool canAbort() const override;
+ auto addNetAction(NetAction::Ptr action) -> bool;
-private slots:
- void startMoreParts();
+ auto operator[](int index) -> NetAction::Ptr { return m_downloads[index]; }
+ auto at(int index) -> const NetAction::Ptr { return m_downloads.at(index); }
+ auto size() const -> int { return m_downloads.size(); }
+ auto first() -> NetAction::Ptr { return m_downloads.size() != 0 ? m_downloads[0] : NetAction::Ptr{}; }
-public slots:
- virtual void executeTask() override;
- virtual bool abort() override;
+ auto getFailedFiles() -> QStringList;
+
+ public slots:
+ // Qt can't handle auto at the start for some reason?
+ bool abort() override;
+
+ private slots:
+ void startMoreParts();
-private slots:
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
void partSucceeded(int index);
void partFailed(int index);
void partAborted(int index);
-private:
+ private:
shared_qobject_ptr<QNetworkAccessManager> m_network;
- struct part_info
- {
+ struct part_info {
qint64 current_progress = 0;
qint64 total_progress = 1;
int failures = 0;
};
- QList<NetAction::Ptr> downloads;
- QList<part_info> parts_progress;
+
+ QList<NetAction::Ptr> m_downloads;
+ QList<part_info> m_parts_progress;
QQueue<int> m_todo;
QSet<int> m_doing;
QSet<int> m_done;
diff --git a/launcher/net/PasteUpload.cpp b/launcher/net/PasteUpload.cpp
index 52b82a0e..3d106c92 100644
--- a/launcher/net/PasteUpload.cpp
+++ b/launcher/net/PasteUpload.cpp
@@ -1,3 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "PasteUpload.h"
#include "BuildConfig.h"
#include "Application.h"
diff --git a/launcher/net/PasteUpload.h b/launcher/net/PasteUpload.h
index 62b2dc36..ea3a06d3 100644
--- a/launcher/net/PasteUpload.h
+++ b/launcher/net/PasteUpload.h
@@ -1,4 +1,39 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "tasks/Task.h"
#include <QNetworkReply>
#include <QBuffer>
diff --git a/launcher/net/Sink.h b/launcher/net/Sink.h
index d367fb15..3870f29b 100644
--- a/launcher/net/Sink.h
+++ b/launcher/net/Sink.h
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "net/NetAction.h"
@@ -5,33 +40,39 @@
#include "Validator.h"
namespace Net {
-class Sink
-{
-public: /* con/des */
- Sink() {};
- virtual ~Sink() {};
+class Sink {
+ public:
+ Sink() = default;
+ virtual ~Sink() = default;
+
+ public:
+ virtual auto init(QNetworkRequest& request) -> Task::State = 0;
+ virtual auto write(QByteArray& data) -> Task::State = 0;
+ virtual auto abort() -> Task::State = 0;
+ virtual auto finalize(QNetworkReply& reply) -> Task::State = 0;
-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 auto hasLocalData() -> bool = 0;
- void addValidator(Validator * validator)
+ void addValidator(Validator* validator)
{
- if(validator)
- {
+ if (validator) {
validators.push_back(std::shared_ptr<Validator>(validator));
}
}
-protected: /* methods */
- bool finalizeAllValidators(QNetworkReply & reply)
+ protected:
+ bool initAllValidators(QNetworkRequest& request)
+ {
+ for (auto& validator : validators) {
+ if (!validator->init(request))
+ return false;
+ }
+ return true;
+ }
+ bool finalizeAllValidators(QNetworkReply& reply)
{
- for(auto & validator: validators)
- {
- if(!validator->validate(reply))
+ for (auto& validator : validators) {
+ if (!validator->validate(reply))
return false;
}
return true;
@@ -39,32 +80,21 @@ protected: /* methods */
bool failAllValidators()
{
bool success = true;
- for(auto & validator: validators)
- {
+ 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)
+ bool writeAllValidators(QByteArray& data)
{
- for(auto & validator: validators)
- {
- if(!validator->write(data))
+ for (auto& validator : validators) {
+ if (!validator->write(data))
return false;
}
return true;
}
-protected: /* data */
+ protected:
std::vector<std::shared_ptr<Validator>> validators;
};
-}
+} // namespace Net
diff --git a/launcher/net/Validator.h b/launcher/net/Validator.h
index 59b72a0b..6b3d4635 100644
--- a/launcher/net/Validator.h
+++ b/launcher/net/Validator.h
@@ -1,3 +1,37 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "net/NetAction.h"
diff --git a/launcher/resources/multimc/128x128/instances/flame.png b/launcher/resources/multimc/128x128/instances/flame.png
index 8a50a0b4..6482975c 100644
--- a/launcher/resources/multimc/128x128/instances/flame.png
+++ b/launcher/resources/multimc/128x128/instances/flame.png
Binary files differ
diff --git a/launcher/resources/multimc/128x128/instances/modrinth.png b/launcher/resources/multimc/128x128/instances/modrinth.png
deleted file mode 100644
index 740bc8f0..00000000
--- a/launcher/resources/multimc/128x128/instances/modrinth.png
+++ /dev/null
Binary files differ
diff --git a/launcher/resources/multimc/32x32/instances/flame.png b/launcher/resources/multimc/32x32/instances/flame.png
deleted file mode 100644
index d8987338..00000000
--- a/launcher/resources/multimc/32x32/instances/flame.png
+++ /dev/null
Binary files differ
diff --git a/launcher/resources/multimc/32x32/instances/modrinth.png b/launcher/resources/multimc/32x32/instances/modrinth.png
deleted file mode 100644
index 025ed065..00000000
--- a/launcher/resources/multimc/32x32/instances/modrinth.png
+++ /dev/null
Binary files differ
diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc
index 0fe673ff..2337acd6 100644
--- a/launcher/resources/multimc/multimc.qrc
+++ b/launcher/resources/multimc/multimc.qrc
@@ -6,8 +6,7 @@
<!-- REDDIT logo icon, needs reddit license! -->
<file>scalable/reddit-alien.svg</file>
- <!-- Icon for CurseForge. Unknown license? -->
- <file alias="32x32/flame.png">32x32/instances/flame.png</file>
+ <!-- Icon for CurseForge. CC0 -->
<file alias="128x128/flame.png">128x128/instances/flame.png</file>
<!-- launcher settings page -->
@@ -20,6 +19,9 @@
<file>scalable/atlauncher.svg</file>
<file>scalable/atlauncher-placeholder.png</file>
+ <!-- Modrinth logo icon -->
+ <file>scalable/instances/modrinth.svg</file>
+
<!-- A proxy icon. Our own. SSSsss -->
<file>scalable/proxy.svg</file>
@@ -269,12 +271,8 @@
<file>32x32/instances/ftb_logo.png</file>
<file>128x128/instances/ftb_logo.png</file>
- <file>32x32/instances/flame.png</file>
<file>128x128/instances/flame.png</file>
- <file>32x32/instances/modrinth.png</file>
- <file>128x128/instances/modrinth.png</file>
-
<file>32x32/instances/gear.png</file>
<file>128x128/instances/gear.png</file>
diff --git a/launcher/resources/multimc/scalable/instances/modrinth.svg b/launcher/resources/multimc/scalable/instances/modrinth.svg
new file mode 100644
index 00000000..a40f0e72
--- /dev/null
+++ b/launcher/resources/multimc/scalable/instances/modrinth.svg
@@ -0,0 +1,4 @@
+<svg width="512" height="514" viewBox="0 0 512 514" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M503.16 323.56C514.55 281.47 515.32 235.91 503.2 190.76C466.57 54.2299 326.04 -26.8001 189.33 9.77991C83.8101 38.0199 11.3899 128.07 0.689941 230.47H43.99C54.29 147.33 113.74 74.7298 199.75 51.7098C306.05 23.2598 415.13 80.6699 453.17 181.38L411.03 192.65C391.64 145.8 352.57 111.45 306.3 96.8198L298.56 140.66C335.09 154.13 364.72 184.5 375.56 224.91C391.36 283.8 361.94 344.14 308.56 369.17L320.09 412.16C390.25 383.21 432.4 310.3 422.43 235.14L464.41 223.91C468.91 252.62 467.35 281.16 460.55 308.07L503.16 323.56Z" fill="#30b27b"/>
+ <path d="M321.99 504.22C185.27 540.8 44.7501 459.77 8.11011 323.24C3.84011 307.31 1.17 291.33 0 275.46H43.27C44.36 287.37 46.4699 299.35 49.6799 311.29C53.0399 323.8 57.45 335.75 62.79 347.07L101.38 323.92C98.1299 316.42 95.39 308.6 93.21 300.47C69.17 210.87 122.41 118.77 212.13 94.7601C229.13 90.2101 246.23 88.4401 262.93 89.1501L255.19 133C244.73 133.05 234.11 134.42 223.53 137.25C157.31 154.98 118.01 222.95 135.75 289.09C136.85 293.16 138.13 297.13 139.59 300.99L188.94 271.38L174.07 231.95L220.67 184.08L279.57 171.39L296.62 192.38L269.47 219.88L245.79 227.33L228.87 244.72L237.16 267.79C237.16 267.79 253.95 285.63 253.98 285.64L277.7 279.33L294.58 260.79L331.44 249.12L342.42 273.82L304.39 320.45L240.66 340.63L212.08 308.81L162.26 338.7C187.8 367.78 226.2 383.93 266.01 380.56L277.54 423.55C218.13 431.41 160.1 406.82 124.05 361.64L85.6399 384.68C136.25 451.17 223.84 484.11 309.61 461.16C371.35 444.64 419.4 402.56 445.42 349.38L488.06 364.88C457.17 431.16 398.22 483.82 321.99 504.22Z" fill="#30b27b"/>
+</svg>
diff --git a/launcher/screenshots/ImgurAlbumCreation.cpp b/launcher/screenshots/ImgurAlbumCreation.cpp
index d5de302a..7afdc5cc 100644
--- a/launcher/screenshots/ImgurAlbumCreation.cpp
+++ b/launcher/screenshots/ImgurAlbumCreation.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "ImgurAlbumCreation.h"
#include <QNetworkRequest>
@@ -13,12 +48,12 @@
ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenShot::Ptr> screenshots) : NetAction(), m_screenshots(screenshots)
{
m_url = BuildConfig.IMGUR_BASE_URL + "album.json";
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-void ImgurAlbumCreation::startImpl()
+void ImgurAlbumCreation::executeTask()
{
- m_status = Job_InProgress;
+ m_state = State::Running;
QNetworkRequest request(m_url);
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
@@ -43,11 +78,11 @@ void ImgurAlbumCreation::startImpl()
void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
{
qDebug() << m_reply->errorString();
- m_status = Job_Failed;
+ m_state = State::Failed;
}
void ImgurAlbumCreation::downloadFinished()
{
- if (m_status != Job_Failed)
+ if (m_state != State::Failed)
{
QByteArray data = m_reply->readAll();
m_reply.reset();
@@ -56,33 +91,32 @@ void ImgurAlbumCreation::downloadFinished()
if (jsonError.error != QJsonParseError::NoError)
{
qDebug() << jsonError.errorString();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
auto object = doc.object();
if (!object.value("success").toBool())
{
qDebug() << doc.toJson();
- emit failed(m_index_within_job);
+ emitFailed();
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);
+ m_state = State::Succeeded;
+ emit succeeded();
return;
}
else
{
qDebug() << m_reply->readAll();
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
}
void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
+ emit progress(bytesReceived, bytesTotal);
}
diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h
index cb048a23..0228b6e4 100644
--- a/launcher/screenshots/ImgurAlbumCreation.h
+++ b/launcher/screenshots/ImgurAlbumCreation.h
@@ -1,7 +1,42 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "net/NetAction.h"
#include "Screenshot.h"
-#include "QObjectPtr.h"
typedef shared_qobject_ptr<class ImgurAlbumCreation> ImgurAlbumCreationPtr;
class ImgurAlbumCreation : public NetAction
@@ -24,16 +59,14 @@ public:
protected
slots:
- virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
- virtual void downloadError(QNetworkReply::NetworkError error);
- virtual void downloadFinished();
- virtual void downloadReadyRead()
- {
- }
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
+ void downloadError(QNetworkReply::NetworkError error) override;
+ void downloadFinished() override;
+ void downloadReadyRead() override {}
public
slots:
- virtual void startImpl();
+ void executeTask() override;
private:
QList<ScreenShot::Ptr> m_screenshots;
diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp
index 76a84947..fbcfb95f 100644
--- a/launcher/screenshots/ImgurUpload.cpp
+++ b/launcher/screenshots/ImgurUpload.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "ImgurUpload.h"
#include "BuildConfig.h"
@@ -13,13 +48,13 @@
ImgurUpload::ImgurUpload(ScreenShot::Ptr shot) : NetAction(), m_shot(shot)
{
m_url = BuildConfig.IMGUR_BASE_URL + "upload.json";
- m_status = Job_NotStarted;
+ m_state = State::Inactive;
}
-void ImgurUpload::startImpl()
+void ImgurUpload::executeTask()
{
finished = false;
- m_status = Job_InProgress;
+ m_state = Task::State::Running;
QNetworkRequest request(m_url);
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
@@ -28,7 +63,7 @@ void ImgurUpload::startImpl()
QFile f(m_shot->m_file.absoluteFilePath());
if (!f.open(QFile::ReadOnly))
{
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
@@ -63,10 +98,10 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
qCritical() << "Double finished ImgurUpload!";
return;
}
- m_status = Job_Failed;
+ m_state = Task::State::Failed;
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
}
void ImgurUpload::downloadFinished()
{
@@ -84,7 +119,7 @@ void ImgurUpload::downloadFinished()
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
return;
}
auto object = doc.object();
@@ -93,20 +128,19 @@ void ImgurUpload::downloadFinished()
qDebug() << "Screenshot upload not successful:" << doc.toJson();
finished = true;
m_reply.reset();
- emit failed(m_index_within_job);
+ emitFailed();
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;
+ m_state = Task::State::Succeeded;
finished = true;
- emit succeeded(m_index_within_job);
+ emit succeeded();
return;
}
void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
+ setProgress(bytesReceived, bytesTotal);
+ emit progress(bytesReceived, bytesTotal);
}
diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h
index cf54f58d..404dc876 100644
--- a/launcher/screenshots/ImgurUpload.h
+++ b/launcher/screenshots/ImgurUpload.h
@@ -1,5 +1,40 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "QObjectPtr.h"
+
#include "net/NetAction.h"
#include "Screenshot.h"
@@ -21,7 +56,7 @@ slots:
public
slots:
- void startImpl() override;
+ void executeTask() override;
private:
ScreenShot::Ptr m_shot;
diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp
index 57307b43..bb71b98c 100644
--- a/launcher/tasks/Task.cpp
+++ b/launcher/tasks/Task.cpp
@@ -1,16 +1,36 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "Task.h"
@@ -99,7 +119,7 @@ void Task::emitAborted()
m_state = State::AbortedByUser;
m_failReason = "Aborted.";
qDebug() << "Task" << describe() << "aborted.";
- emit failed(m_failReason);
+ emit aborted();
emit finished();
}
diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h
index 344a024e..f7765c3d 100644
--- a/launcher/tasks/Task.h
+++ b/launcher/tasks/Task.h
@@ -1,24 +1,40 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
#pragma once
-#include <QObject>
-#include <QString>
-#include <QStringList>
-
#include "QObjectPtr.h"
class Task : public QObject {
@@ -52,6 +68,8 @@ class Task : public QObject {
virtual bool canAbort() const { return false; }
+ auto getState() const -> State { return m_state; }
+
QString getStatus() { return m_status; }
virtual auto getStepStatus() const -> QString { return m_status; }
@@ -68,15 +86,16 @@ class Task : public QObject {
signals:
void started();
- virtual void progress(qint64 current, qint64 total);
+ void progress(qint64 current, qint64 total);
void finished();
void succeeded();
+ void aborted();
void failed(QString reason);
void status(QString status);
public slots:
virtual void start();
- virtual bool abort() { return false; };
+ virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); };
protected:
virtual void executeTask() = 0;
@@ -84,13 +103,13 @@ class Task : public QObject {
protected slots:
virtual void emitSucceeded();
virtual void emitAborted();
- virtual void emitFailed(QString reason);
+ virtual void emitFailed(QString reason = "");
public slots:
void setStatus(const QString& status);
void setProgress(qint64 current, qint64 total);
- private:
+ protected:
State m_state = State::Inactive;
QStringList m_Warnings;
QString m_failReason = "";
diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 250854d3..53722d69 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -1,3 +1,38 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "TranslationsModel.h"
#include <QCoreApplication>
@@ -667,7 +702,7 @@ void TranslationsModel::downloadTranslation(QString key)
auto dl = Net::Download::makeCached(QUrl(BuildConfig.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;
+ dl->setProgress(dl->getProgress(), lang->file_size);
d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network());
d->m_dl_job->addNetAction(dl);
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index ca345b1f..f016dc76 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -749,6 +749,9 @@ public:
// disabled until we have an instance selected
instanceToolBar->setEnabled(false);
instanceToolBar->setMovable(true);
+ // Qt doesn't like vertical moving toolbars, so we have to force them...
+ // See https://github.com/PolyMC/PolyMC/issues/493
+ connect(instanceToolBar, &QToolBar::orientationChanged, [=](Qt::Orientation){ instanceToolBar->setOrientation(Qt::Vertical); });
instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
instanceToolBar->setFloatable(false);
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index 5fac1015..8631edf6 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -117,7 +117,7 @@ public:
flags |= Qt::ItemIsUserCheckable;
if (sourceIndex.model()->hasChildren(sourceIndex))
{
- flags |= Qt::ItemIsTristate;
+ flags |= Qt::ItemIsAutoTristate;
}
}
@@ -210,7 +210,7 @@ public:
QStack<QModelIndex> todo;
while (1)
{
- auto node = doing.child(row, 0);
+ auto node = fsm->index(row, 0, doing);
if (!node.isValid())
{
if (!todo.size())
@@ -259,7 +259,7 @@ public:
QStack<QModelIndex> todo;
while (1)
{
- auto node = doing.child(row, 0);
+ auto node = this->index(row, 0, doing);
if (!node.isValid())
{
if (!todo.size())
@@ -460,7 +460,7 @@ void ExportInstanceDialog::rowsInserted(QModelIndex parent, int top, int bottom)
//WARNING: possible off-by-one?
for(int i = top; i < bottom; i++)
{
- auto node = parent.child(i, 0);
+ auto node = proxyModel->index(i, 0, parent);
if(proxyModel->shouldExpand(node))
{
auto expNode = node.parent();
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index d02ea476..305e85c0 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -13,7 +13,7 @@
#include <QDialogButtonBox>
#include "ui/widgets/PageContainer.h"
-#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthModPage.h"
#include "ModDownloadTask.h"
@@ -98,7 +98,7 @@ void ModDownloadDialog::accept()
QList<BasePage *> ModDownloadDialog::getPages()
{
- modrinthPage = new ModrinthPage(this, m_instance);
+ modrinthPage = new ModrinthModPage(this, m_instance);
flameModPage = new FlameModPage(this, m_instance);
return
{
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index 309d89d0..782dc361 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -16,7 +16,7 @@ class ModDownloadDialog;
class PageContainer;
class QDialogButtonBox;
-class ModrinthPage;
+class ModrinthModPage;
class ModDownloadDialog : public QDialog, public BasePageProvider
{
@@ -50,7 +50,7 @@ private:
QVBoxLayout *m_verticalLayout = nullptr;
- ModrinthPage *modrinthPage = nullptr;
+ ModrinthModPage *modrinthPage = nullptr;
FlameModPage *flameModPage = nullptr;
QHash<QString, ModDownloadTask*> modTask;
BaseInstance *m_instance;
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index b402839c..05ea091d 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -39,6 +39,7 @@
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
@@ -134,6 +135,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
flamePage,
new FtbPage(this),
new LegacyFTB::Page(this),
+ new ModrinthPage(this),
technicPage
};
}
diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp
index 287eb74f..8b806bcf 100644
--- a/launcher/ui/pages/global/APIPage.cpp
+++ b/launcher/ui/pages/global/APIPage.cpp
@@ -70,6 +70,8 @@ void APIPage::loadSettings()
ui->urlChoices->setCurrentText(pastebinURL);
QString msaClientID = s->get("MSAClientIDOverride").toString();
ui->msaClientID->setText(msaClientID);
+ QString curseKey = s->get("CFKeyOverride").toString();
+ ui->curseKey->setText(curseKey);
}
void APIPage::applySettings()
@@ -79,6 +81,8 @@ void APIPage::applySettings()
s->set("PastebinURL", pastebinURL);
QString msaClientID = ui->msaClientID->text();
s->set("MSAClientIDOverride", msaClientID);
+ QString curseKey = ui->curseKey->text();
+ s->set("CFKeyOverride", curseKey);
}
bool APIPage::apply()
diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui
index acde9aef..eaa44c88 100644
--- a/launcher/ui/pages/global/APIPage.ui
+++ b/launcher/ui/pages/global/APIPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>491</width>
- <height>474</height>
+ <width>603</width>
+ <height>530</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -148,17 +148,56 @@
</widget>
</item>
<item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QGroupBox" name="groupBox_curse">
+ <property name="enabled">
+ <bool>true</bool>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
+ <property name="title">
+ <string>&amp;CurseForge Core API</string>
</property>
- </spacer>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Note: you probably don't need to set this if CurseForge already works.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="curseKey">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="placeholderText">
+ <string>(Default)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Enter a custom API Key for CurseForge here. </string>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::RichText</enum>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
@@ -166,9 +205,6 @@
</item>
</layout>
</widget>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/launcher/ui/pages/global/ExternalToolsPage.cpp b/launcher/ui/pages/global/ExternalToolsPage.cpp
index 693ca5c1..5ba0ebc2 100644
--- a/launcher/ui/pages/global/ExternalToolsPage.cpp
+++ b/launcher/ui/pages/global/ExternalToolsPage.cpp
@@ -54,9 +54,7 @@ ExternalToolsPage::ExternalToolsPage(QWidget *parent) :
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
- #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
ui->jsonEditorTextBox->setClearButtonEnabled(true);
- #endif
ui->mceditLink->setOpenExternalLinks(true);
ui->jvisualvmLink->setOpenExternalLinks(true);
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp
index e694ebe3..2cf17b32 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.cpp
+++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp
@@ -251,7 +251,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
m_model.reset(new QFileSystemModel());
m_filterModel.reset(new FilterModel());
m_filterModel->setSourceModel(m_model.get());
- m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable);
+ m_model->setFilter(QDir::Files);
m_model->setReadOnly(false);
m_model->setNameFilters({"*.png"});
m_model->setNameFilterDisables(false);
@@ -343,6 +343,29 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
DesktopServices::openFile(info.absoluteFilePath());
}
+void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection &selected)
+{
+ bool allReadable = !selected.isEmpty();
+ bool allWritable = !selected.isEmpty();
+
+ for (auto index : selected.indexes())
+ {
+ if (!index.isValid())
+ break;
+ auto info = m_model->fileInfo(index);
+ if (!info.isReadable())
+ allReadable = false;
+ if (!info.isWritable())
+ allWritable = false;
+ }
+
+ ui->actionUpload->setEnabled(allReadable);
+ ui->actionCopy_Image->setEnabled(allReadable);
+ ui->actionCopy_File_s->setEnabled(allReadable);
+ ui->actionDelete->setEnabled(allWritable);
+ ui->actionRename->setEnabled(allWritable);
+}
+
void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
@@ -503,6 +526,8 @@ void ScreenshotsPage::openedImpl()
if(idx.isValid())
{
ui->listView->setModel(m_filterModel.get());
+ connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ScreenshotsPage::onCurrentSelectionChanged);
+ onCurrentSelectionChanged(ui->listView->selectionModel()->selection()); // set initial button enable states
ui->listView->setRootIndex(m_filterModel->mapFromSource(idx));
}
else
diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h
index 50cf1a17..c22706af 100644
--- a/launcher/ui/pages/instance/ScreenshotsPage.h
+++ b/launcher/ui/pages/instance/ScreenshotsPage.h
@@ -100,6 +100,7 @@ private slots:
void on_actionRename_triggered();
void on_actionView_Folder_triggered();
void onItemActivated(QModelIndex);
+ void onCurrentSelectionChanged(const QItemSelection &selected);
void ShowContextMenu(const QPoint &pos);
private:
diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index 1b53dd40..c7bc13d8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -2,6 +2,7 @@
/*
* PolyMC - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -109,7 +110,11 @@ void ImportPage::updateState()
{
// FIXME: actually do some validation of what's inside here... this is fake AF
QFileInfo fi(input);
- if(fi.exists() && fi.suffix() == "zip")
+ // mrpack is a modrinth pack
+
+ // Allow non-latin people to use ZIP files!
+ auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip");
+ if(fi.exists() && (zip || fi.suffix() == "mrpack"))
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
@@ -144,6 +149,7 @@ void ImportPage::setUrl(const QString& url)
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
+ filter += ";;" + tr("Modrinth pack (*.mrpack)");
const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter);
if (url.isValid())
{
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index eb63cbe9..77bc5da5 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -11,28 +11,75 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="1">
- <widget class="QPushButton" name="modpackBtn">
- <property name="text">
- <string>Browse</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
+ <item row="2" column="0">
<widget class="QLineEdit" name="modpackEdit">
<property name="placeholderText">
<string notr="true">http://</string>
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="modpackLabel">
+ <item row="2" column="1">
+ <widget class="QPushButton" name="modpackBtn">
<property name="text">
- <string>Local file or link to a direct download:</string>
+ <string>Browse</string>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" rowspan="2" colspan="2">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>The following file types are implemented (both for local files and URLs):</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>- Curseforge modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>- Modrinth modpacks (ZIP and mrpack)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>- PolyMC / MultiMC exported instances (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>- Technic modpacks (ZIP)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="5" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -45,6 +92,13 @@
</property>
</spacer>
</item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="modpackLabel">
+ <property name="text">
+ <string>Local file or link to a direct download:</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 6d8d8e10..9fab9773 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -1,90 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>FlamePage</class>
- <widget class="QWidget" name="FlamePage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>837</width>
- <height>685</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QListView" name="packView">
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QTextBrowser" name="packDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- <property name="openLinks">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>Search</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit">
- <property name="placeholderText">
- <string>Search and filter...</string>
- </property>
- </widget>
- </item>
+ <class>FlamePage</class>
+ <widget class="QWidget" name="FlamePage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note: CurseForge's API is very unreliable. CurseForge and some mod authors have disallowed downloading mods in third-party applications like PolyMC. As such, you may need to manually download some mods to be able to install a modpack.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
</layout>
- </widget>
- <tabstops>
- <tabstop>searchEdit</tabstop>
- <tabstop>searchButton</tabstop>
- <tabstop>packView</tabstop>
- <tabstop>packDescription</tabstop>
- <tabstop>sortByBox</tabstop>
- <tabstop>versionSelectionBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
</ui>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
new file mode 100644
index 00000000..1d9f4d60
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "ModrinthModModel.h"
+
+#include "modplatform/modrinth/ModrinthPackIndex.h"
+
+namespace Modrinth {
+
+// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
+
+void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ Modrinth::loadIndexedPack(m, obj);
+}
+
+void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+}
+
+auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return obj.object().value("hits").toArray();
+}
+
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
new file mode 100644
index 00000000..ae7b0bdd
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "ModrinthModPage.h"
+
+namespace Modrinth {
+
+class ListModel : public ModPlatform::ListModel {
+ Q_OBJECT
+
+ public:
+ ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){};
+ ~ListModel() override = default;
+
+ private:
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
+
+ // NOLINTNEXTLINE(modernize-avoid-c-arrays)
+ static const char* sorts[5];
+ inline auto getSorts() const -> const char** override { return sorts; };
+};
+
+} // namespace Modrinth
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
new file mode 100644
index 00000000..d3a1f859
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "ModrinthModPage.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "ui_ModPage.h"
+
+#include "ModrinthModModel.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+
+ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance)
+ : ModPage(dialog, instance, new ModrinthAPI())
+{
+ listModel = new Modrinth::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ // index is used to set the sorting with the modrinth api
+ ui->sortByBox->addItem(tr("Sort by Relevance"));
+ ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Follows"));
+ ui->sortByBox->addItem(tr("Sort by Last Updated"));
+ ui->sortByBox->addItem(tr("Sort by Newest"));
+
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // so it's best not to connect them in the parent's constructor...
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged);
+ connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
+}
+
+auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+{
+ auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+
+ auto loaderCompatible = false;
+ for (auto remoteLoader : ver.loaders)
+ {
+ if (loaderStrings.contains(remoteLoader)) {
+ loaderCompatible = true;
+ break;
+ }
+ }
+ return ver.mcVersion.contains(mineVer) && loaderCompatible;
+}
+
+// I don't know why, but doing this on the parent class makes it so that
+// other mod providers start loading before being selected, at least with
+// my Qt, so we need to implement this in every derived class...
+auto ModrinthModPage::shouldDisplay() const -> bool { return true; }
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
new file mode 100644
index 00000000..b1e72bfe
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "modplatform/ModAPI.h"
+#include "ui/pages/modplatform/ModPage.h"
+
+#include "modplatform/modrinth/ModrinthAPI.h"
+
+class ModrinthModPage : public ModPage {
+ Q_OBJECT
+
+ public:
+ explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance);
+ ~ModrinthModPage() override = default;
+
+ inline auto displayName() const -> QString override { return "Modrinth"; }
+ inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
+ inline auto id() const -> QString override { return "modrinth"; }
+ inline auto helpPage() const -> QString override { return "Mod-platform"; }
+
+ inline auto debugName() const -> QString override { return "Modrinth"; }
+ inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
+
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+
+ auto shouldDisplay() const -> bool override;
+};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index b788860a..7cacf37a 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,30 +14,301 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "ModrinthModel.h"
-#include "modplatform/modrinth/ModrinthPackIndex.h"
+#include "BuildConfig.h"
+#include "Json.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "ui/dialogs/ModDownloadDialog.h"
+
+#include <QMessageBox>
namespace Modrinth {
-// NOLINTNEXTLINE(modernize-avoid-c-arrays)
-const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" };
+ModpackListModel::ModpackListModel(ModrinthPage* parent) : QAbstractListModel(parent), m_parent(parent) {}
+
+auto ModpackListModel::debugName() const -> QString
+{
+ return m_parent->debugName();
+}
+
+/******** Make data requests ********/
+
+void ModpackListModel::fetchMore(const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return;
+ if (nextSearchOffset == 0) {
+ qWarning() << "fetchMore with 0 offset is wrong...";
+ return;
+ }
+ performPaginatedSearch();
+}
+
+auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVariant
+{
+ int pos = index.row();
+ if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ Modrinth::Modpack pack = modpacks.at(pos);
+ if (role == Qt::DisplayRole) {
+ return pack.name;
+ } else if (role == Qt::ToolTipRole) {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ } else if (role == Qt::DecorationRole) {
+ if (m_logoMap.contains(pack.iconName)) {
+ auto icon = m_logoMap.value(pack.iconName);
+ auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48));
-void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+ return icon_scaled;
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString());
+ return icon;
+ } else if (role == Qt::UserRole) {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return {};
+}
+
+void ModpackListModel::performPaginatedSearch()
+{
+ // TODO: Move to standalone API
+ NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network());
+ auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
+ "/search?"
+ "offset=%1&"
+ "limit=%2&"
+ "query=%3&"
+ "index=%4&"
+ "facets=[[\"project_type:modpack\"]]")
+ .arg(nextSearchOffset)
+ .arg(m_modpacks_per_page)
+ .arg(currentSearchTerm)
+ .arg(currentSort);
+
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this] {
+ QJsonParseError parse_error_all{};
+
+ QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all);
+ if (parse_error_all.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset
+ << " reason: " << parse_error_all.errorString();
+ qWarning() << m_all_response;
+ return;
+ }
+
+ searchRequestFinished(doc_all);
+ });
+ QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
+
+ jobPtr = netJob;
+ jobPtr->start();
+}
+
+void ModpackListModel::refresh()
{
- Modrinth::loadIndexedPack(m, obj);
+ if (jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ } else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ nextSearchOffset = 0;
+ performPaginatedSearch();
}
-void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+static auto sortFromIndex(int index) -> QString
{
- Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance);
+ switch(index){
+ default:
+ case 1:
+ return "relevance";
+ case 2:
+ return "downloads";
+ case 3:
+ return "follows";
+ case 4:
+ return "newest";
+ case 5:
+ return "updated";
+ }
+
+ return {};
}
-auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+void ModpackListModel::searchWithTerm(const QString& term, const int sort)
{
- return obj.object().value("hits").toArray();
+ if(sort > 5 || sort < 0)
+ return;
+
+ auto sort_str = sortFromIndex(sort);
+
+ if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) {
+ return;
+ }
+
+ currentSearchTerm = term;
+ currentSort = sort_str;
+
+ refresh();
+}
+
+void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
+{
+ if (m_logoMap.contains(logo)) {
+ callback(APPLICATION->metacache()
+ ->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))
+ ->getFullPath());
+ } else {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+void ModpackListModel::requestLogo(QString logo, QString url)
+{
+ if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) {
+ return;
+ }
+
+ MetaEntryPtr entry =
+ APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
+ job->deleteLater();
+ emit logoLoaded(logo, QIcon(fullPath));
+ if (waitingCallbacks.contains(logo)) {
+ waitingCallbacks.value(logo)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo, job] {
+ job->deleteLater();
+ emit logoFailed(logo);
+ });
+
+ job->start();
+ m_loadingLogos.append(logo);
+}
+
+/******** Request callbacks ********/
+
+void ModpackListModel::logoLoaded(QString logo, QIcon out)
+{
+ m_loadingLogos.removeAll(logo);
+ m_logoMap.insert(logo, out);
+ for (int i = 0; i < modpacks.size(); i++) {
+ if (modpacks[i].iconName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole });
+ }
+ }
+}
+
+void ModpackListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
+{
+ jobPtr.reset();
+
+ QList<Modrinth::Modpack> newList;
+
+ auto packs_all = doc_all.object().value("hits").toArray();
+ for (auto packRaw : packs_all) {
+ auto packObj = packRaw.toObject();
+
+ Modrinth::Modpack pack;
+ try {
+ Modrinth::loadIndexedPack(pack, packObj);
+ newList.append(pack);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
+ continue;
+ }
+ }
+
+ if (packs_all.size() < m_modpacks_per_page) {
+ searchState = Finished;
+ } else {
+ nextSearchOffset += m_modpacks_per_page;
+ searchState = CanPossiblyFetchMore;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void ModpackListModel::searchRequestFailed(QString reason)
+{
+ if (!jobPtr->first()->m_reply) {
+ // Network error
+ QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
+ } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) {
+ // 409 Gone, notify user to update
+ QMessageBox::critical(nullptr, tr("Error"),
+ //: %1 refers to the launcher itself
+ QString("%1 %2")
+ .arg(m_parent->displayName())
+ .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME)));
+ }
+ jobPtr.reset();
+
+ if (searchState == ResetRequested) {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+ } else {
+ searchState = Finished;
+ }
}
} // namespace Modrinth
+
+/******** Helpers ********/
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 45a6090a..14aa6747 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -1,25 +1,116 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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 "ModrinthPage.h"
+#include <QAbstractListModel>
+
+#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
+
+class ModPage;
+class Version;
namespace Modrinth {
-class ListModel : public ModPlatform::ListModel {
+using LogoMap = QMap<QString, QIcon>;
+using LogoCallback = std::function<void (QString)>;
+
+class ModpackListModel : public QAbstractListModel {
Q_OBJECT
public:
- ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent){};
- ~ListModel() override = default;
-
- private:
- void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
- void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
-
- auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
-
- // NOLINTNEXTLINE(modernize-avoid-c-arrays)
- static const char* sorts[5];
- inline auto getSorts() const -> const char** override { return sorts; };
-};
+ ModpackListModel(ModrinthPage* parent);
+ ~ModpackListModel() override = default;
+
+ inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
+ inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
+ inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
+
+ auto debugName() const -> QString;
+
+ /* Retrieve information from the model at a given index with the given role */
+ auto data(const QModelIndex& index, int role) const -> QVariant override;
+
+ inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; }
+
+ /* Ask the API for more information */
+ void fetchMore(const QModelIndex& parent) override;
+ void refresh();
+ void searchWithTerm(const QString& term, const int sort);
+
+ void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
+
+ inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
+
+ public slots:
+ void searchRequestFinished(QJsonDocument& doc_all);
+ void searchRequestFailed(QString reason);
-} // namespace Modrinth
+ protected slots:
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+ void performPaginatedSearch();
+
+ protected:
+ void requestLogo(QString file, QString url);
+
+ inline auto getMineVersions() const -> std::list<Version>;
+
+ protected:
+ ModrinthPage* m_parent;
+
+ QList<Modrinth::Modpack> modpacks;
+
+ LogoMap m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+
+ QString currentSearchTerm;
+ QString currentSort;
+ int nextSearchOffset = 0;
+ enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
+
+ NetJob::Ptr jobPtr;
+
+ QByteArray m_all_response;
+ QByteArray m_specific_response;
+
+ int m_modpacks_per_page = 20;
+};
+} // namespace ModPlatform
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 98bde0ae..9bd24b57 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2021-2022 kb1000
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,49 +35,237 @@
*/
#include "ModrinthPage.h"
-#include "modplatform/modrinth/ModrinthAPI.h"
-#include "ui_ModPage.h"
+#include "ui_ModrinthPage.h"
#include "ModrinthModel.h"
-#include "ui/dialogs/ModDownloadDialog.h"
-ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance)
- : ModPage(dialog, instance, new ModrinthAPI())
+#include "BuildConfig.h"
+#include "InstanceImportTask.h"
+#include "Json.h"
+
+#include <HoeDown.h>
+
+#include <QComboBox>
+#include <QKeyEvent>
+#include <QPushButton>
+
+ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
{
- listModel = new Modrinth::ListModel(this);
- ui->packView->setModel(listModel);
+ ui->setupUi(this);
+
+ connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ m_model = new Modrinth::ModpackListModel(this);
+ ui->packView->setModel(m_model);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
- // index is used to set the sorting with the modrinth api
ui->sortByBox->addItem(tr("Sort by Relevance"));
- ui->sortByBox->addItem(tr("Sort by Downloads"));
+ ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
- ui->sortByBox->addItem(tr("Sort by Last Updated"));
ui->sortByBox->addItem(tr("Sort by Newest"));
+ ui->sortByBox->addItem(tr("Sort by Last Updated"));
- // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
- // so it's best not to connect them in the parent's constructor...
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged);
- connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthPage::onModSelected);
}
-auto ModrinthPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
+ModrinthPage::~ModrinthPage()
+{
+ delete ui;
+}
+
+void ModrinthPage::retranslate()
+{
+ ui->retranslateUi(this);
+}
+
+void ModrinthPage::openedImpl()
+{
+ BasePage::openedImpl();
+ triggerSearch();
+}
+
+bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ auto* keyEvent = reinterpret_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ this->triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QObject::eventFilter(watched, event);
+}
+
+void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if (!first.isValid()) {
+ if (isOpened) {
+ dialog->setSuggestedPack();
+ }
+ return;
+ }
+
+ current = m_model->data(first, Qt::UserRole).value<Modrinth::Modpack>();
+ auto name = current.name;
+
+ if (!current.extraInfoLoaded) {
+ qDebug() << "Loading modrinth modpack information";
+
+ auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network());
+ auto response = new QByteArray();
+
+ QString id = current.id;
+
+ netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
+ if (id != current.id) {
+ return; // wrong request?
+ }
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ auto obj = Json::requireObject(doc);
+
+ try {
+ Modrinth::loadIndexedInfo(current, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << *response;
+ qWarning() << "Error while reading modrinth modpack version: " << e.cause();
+ }
+
+ updateUI();
+ suggestCurrent();
+ });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
+ netJob->deleteLater();
+ delete response;
+ });
+ netJob->start();
+ } else
+ updateUI();
+
+ if (!current.versionsLoaded) {
+ qDebug() << "Loading modrinth modpack versions";
+
+ auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
+ auto response = new QByteArray();
+
+ QString id = current.id;
+
+ netJob->addNetAction(
+ Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] {
+ if (id != current.id) {
+ return; // wrong request?
+ }
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+
+ try {
+ Modrinth::loadIndexedVersions(current, doc);
+ } catch (const JSONValidationError& e) {
+ qDebug() << *response;
+ qWarning() << "Error while reading modrinth modpack version: " << e.cause();
+ }
+
+ for (auto version : current.versions) {
+ ui->versionSelectionBox->addItem(version.version, QVariant(version.id));
+ }
+
+ suggestCurrent();
+ });
+ QObject::connect(netJob, &NetJob::finished, this, [response, netJob] {
+ netJob->deleteLater();
+ delete response;
+ });
+ netJob->start();
+
+ } else {
+ for (auto version : current.versions) {
+ ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id));
+ }
+
+ suggestCurrent();
+ }
+}
+
+void ModrinthPage::updateUI()
+{
+ QString text = "";
+
+ if (current.extra.projectUrl.isEmpty())
+ text = current.name;
+ else
+ text = "<a href=\"" + current.extra.projectUrl + "\">" + current.name + "</a>";
+
+ // TODO: Implement multiple authors with links
+ text += "<br>" + tr(" by ") + QString("<a href=%1>%2</a>").arg(std::get<1>(current.author).toString(), std::get<0>(current.author));
+
+ text += "<br>";
+
+ HoeDown h;
+ text += h.process(current.extra.body.toUtf8());
+
+ ui->packDescription->setHtml(text + current.description);
+}
+
+void ModrinthPage::suggestCurrent()
{
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
+ if (!isOpened) {
+ return;
+ }
+
+ if (selectedVersion.isEmpty()) {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ for (auto& ver : current.versions) {
+ if (ver.id == selectedVersion) {
+ dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this));
+ auto iconName = current.iconName;
+ m_model->getLogo(iconName, current.iconUrl.toString(),
+ [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); });
- auto loaderCompatible = false;
- for (auto remoteLoader : ver.loaders)
- {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
break;
}
}
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
}
-// I don't know why, but doing this on the parent class makes it so that
-// other mod providers start loading before being selected, at least with
-// my Qt, so we need to implement this in every derived class...
-auto ModrinthPage::shouldDisplay() const -> bool { return true; }
+void ModrinthPage::triggerSearch()
+{
+ m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+}
+
+void ModrinthPage::onVersionSelectionChanged(QString data)
+{
+ if (data.isNull() || data.isEmpty()) {
+ selectedVersion = "";
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toString();
+ suggestCurrent();
+}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index e3a0e1f0..db5e1a3d 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* PolyMC - Minecraft Launcher
- * Copyright (c) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
+ * Copyright 2021-2022 kb1000
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,27 +36,56 @@
#pragma once
-#include "modplatform/ModAPI.h"
-#include "ui/pages/modplatform/ModPage.h"
+#include "Application.h"
+#include "ui/dialogs/NewInstanceDialog.h"
+#include "ui/pages/BasePage.h"
-#include "modplatform/modrinth/ModrinthAPI.h"
+#include "modplatform/modrinth/ModrinthPackManifest.h"
-class ModrinthPage : public ModPage {
+#include <QWidget>
+
+namespace Ui {
+class ModrinthPage;
+}
+
+namespace Modrinth {
+class ModpackListModel;
+}
+
+class ModrinthPage : public QWidget, public BasePage {
Q_OBJECT
public:
- explicit ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance);
- ~ModrinthPage() override = default;
+ explicit ModrinthPage(NewInstanceDialog* dialog, QWidget* parent = nullptr);
+ ~ModrinthPage() override;
+
+ QString displayName() const override { return tr("Modrinth"); }
+ QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); }
+ QString id() const override { return "modrinth"; }
+ QString helpPage() const override { return "Modrinth-platform"; }
+
+ inline auto debugName() const -> QString { return "Modrinth"; }
+ inline auto metaEntryBase() const -> QString { return "ModrinthModpacks"; };
+
+ auto getCurrent() -> Modrinth::Modpack& { return current; }
+ void suggestCurrent();
+
+ void updateUI();
- inline auto displayName() const -> QString override { return "Modrinth"; }
- inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); }
- inline auto id() const -> QString override { return "modrinth"; }
- inline auto helpPage() const -> QString override { return "Mod-platform"; }
+ void retranslate() override;
+ void openedImpl() override;
+ bool eventFilter(QObject* watched, QEvent* event) override;
- inline auto debugName() const -> QString override { return "Modrinth"; }
- inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
+ private slots:
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+ void triggerSearch();
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
+ private:
+ Ui::ModrinthPage* ui;
+ NewInstanceDialog* dialog;
+ Modrinth::ModpackListModel* m_model;
- auto shouldDisplay() const -> bool override;
+ Modrinth::Modpack current;
+ QString selectedVersion;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
new file mode 100644
index 00000000..ae9556ed
--- /dev/null
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ModrinthPage</class>
+ <widget class="QWidget" name="ModrinthPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="uniformItemSizes">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt
index 0eccae8b..2c859499 100644
--- a/libraries/launcher/CMakeLists.txt
+++ b/libraries/launcher/CMakeLists.txt
@@ -4,17 +4,18 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development)
include(UseJava)
set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
-set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked)
+set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
set(SRC
org/multimc/EntryPoint.java
org/multimc/Launcher.java
- org/multimc/LegacyFrame.java
- org/multimc/NotFoundException.java
- org/multimc/ParamBucket.java
- org/multimc/ParseException.java
- org/multimc/Utils.java
- org/multimc/onesix/OneSixLauncher.java
+ org/multimc/LauncherFactory.java
+ org/multimc/impl/OneSixLauncher.java
+ org/multimc/applet/LegacyFrame.java
+ org/multimc/exception/ParameterNotFoundException.java
+ org/multimc/exception/ParseException.java
+ org/multimc/utils/Parameters.java
+ org/multimc/utils/Utils.java
net/minecraft/Launcher.java
)
add_jar(NewLaunch ${SRC})
diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java
index b6b0a574..265fa66a 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -16,31 +16,28 @@
package net.minecraft;
-import java.util.TreeMap;
-import java.util.Map;
-import java.net.URL;
-import java.awt.Dimension;
-import java.awt.BorderLayout;
-import java.awt.Graphics;
import java.applet.Applet;
import java.applet.AppletStub;
+import java.awt.*;
import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+import java.util.TreeMap;
-public class Launcher extends Applet implements AppletStub
-{
- private Applet wrappedApplet;
- private URL documentBase;
- private boolean active = false;
- private final Map<String, String> params;
+public final class Launcher extends Applet implements AppletStub {
- public Launcher(Applet applet, URL documentBase)
- {
- params = new TreeMap<String, String>();
+ private final Map<String, String> params = new TreeMap<>();
+
+ private final Applet wrappedApplet;
+
+ private boolean active = false;
+ public Launcher(Applet applet) {
this.setLayout(new BorderLayout());
+
this.add(applet, "Center");
+
this.wrappedApplet = applet;
- this.documentBase = documentBase;
}
public void setParameter(String name, String value)
@@ -48,84 +45,61 @@ public class Launcher extends Applet implements AppletStub
params.put(name, value);
}
- public void replace(Applet applet)
- {
- this.wrappedApplet = applet;
-
- applet.setStub(this);
- applet.setSize(getWidth(), getHeight());
-
- this.setLayout(new BorderLayout());
- this.add(applet, "Center");
-
- applet.init();
- active = true;
- applet.start();
- validate();
- }
-
@Override
- public String getParameter(String name)
- {
+ public String getParameter(String name) {
String param = params.get(name);
+
if (param != null)
return param;
- try
- {
+
+ try {
return super.getParameter(name);
- } catch (Exception ignore){}
+ } catch (Exception ignore) {}
+
return null;
}
@Override
- public boolean isActive()
- {
+ public boolean isActive() {
return active;
}
@Override
- public void appletResize(int width, int height)
- {
+ public void appletResize(int width, int height) {
wrappedApplet.resize(width, height);
}
@Override
- public void resize(int width, int height)
- {
+ public void resize(int width, int height) {
wrappedApplet.resize(width, height);
}
@Override
- public void resize(Dimension d)
- {
+ public void resize(Dimension d) {
wrappedApplet.resize(d);
}
@Override
- public void init()
- {
+ public void init() {
if (wrappedApplet != null)
- {
wrappedApplet.init();
- }
}
@Override
- public void start()
- {
+ public void start() {
wrappedApplet.start();
+
active = true;
}
@Override
- public void stop()
- {
+ public void stop() {
wrappedApplet.stop();
+
active = false;
}
- public void destroy()
- {
+ public void destroy() {
wrappedApplet.destroy();
}
@@ -136,34 +110,34 @@ public class Launcher extends Applet implements AppletStub
} catch (MalformedURLException e) {
e.printStackTrace();
}
+
return null;
}
@Override
- public URL getDocumentBase()
- {
+ public URL getDocumentBase() {
try {
// Special case only for Classic versions
- if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) {
- return new URL("http", "www.minecraft.net", 80, "/game/", null);
- }
+ if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
+ return new URL("http", "www.minecraft.net", 80, "/game/");
+
return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
+
return null;
}
@Override
- public void setVisible(boolean b)
- {
+ public void setVisible(boolean b) {
super.setVisible(b);
+
wrappedApplet.setVisible(b);
}
- public void update(Graphics paramGraphics)
- {
- }
- public void paint(Graphics paramGraphics)
- {
- }
-} \ No newline at end of file
+
+ public void update(Graphics paramGraphics) {}
+
+ public void paint(Graphics paramGraphics) {}
+
+}
diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java
index b626d095..c0500bbe 100644
--- a/libraries/launcher/org/multimc/EntryPoint.java
+++ b/libraries/launcher/org/multimc/EntryPoint.java
@@ -1,20 +1,42 @@
-package org.multimc;/*
- * Copyright 2012-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 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.
*/
-import org.multimc.onesix.OneSixLauncher;
+package org.multimc;
+
+import org.multimc.exception.ParseException;
+import org.multimc.utils.Parameters;
import java.io.BufferedReader;
import java.io.IOException;
@@ -23,31 +45,25 @@ import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
-public class EntryPoint
-{
+public final class EntryPoint {
private static final Logger LOGGER = Logger.getLogger("EntryPoint");
- private final ParamBucket params = new ParamBucket();
+ private final Parameters params = new Parameters();
- private org.multimc.Launcher launcher;
-
- public static void main(String[] args)
- {
+ public static void main(String[] args) {
EntryPoint listener = new EntryPoint();
int retCode = listener.listen();
- if (retCode != 0)
- {
+ if (retCode != 0) {
LOGGER.info("Exiting with " + retCode);
System.exit(retCode);
}
}
- private Action parseLine(String inData) throws ParseException
- {
+ private Action parseLine(String inData) throws ParseException {
String[] tokens = inData.split("\\s+", 2);
if (tokens.length == 0)
@@ -62,21 +78,6 @@ public class EntryPoint
return Action.Abort;
}
- case "launcher": {
- if (tokens.length != 2)
- throw new ParseException("Expected 2 tokens, got " + tokens.length);
-
- if (tokens[1].equals("onesix")) {
- launcher = new OneSixLauncher();
-
- LOGGER.info("Using onesix launcher.");
-
- return Action.Proceed;
- } else {
- throw new ParseException("Invalid launcher type: " + tokens[1]);
- }
- }
-
default: {
if (tokens.length != 2)
throw new ParseException("Error while parsing:" + inData);
@@ -88,8 +89,7 @@ public class EntryPoint
}
}
- public int listen()
- {
+ public int listen() {
Action action = Action.Proceed;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
@@ -112,21 +112,30 @@ public class EntryPoint
}
// Main loop
- if (action == Action.Abort)
- {
+ if (action == Action.Abort) {
LOGGER.info("Launch aborted by the launcher.");
return 1;
}
- if (launcher != null)
- {
- return launcher.launch(params);
- }
+ try {
+ Launcher launcher =
+ LauncherFactory
+ .getInstance()
+ .createLauncher(params);
- LOGGER.log(Level.SEVERE, "No valid launcher implementation specified.");
+ launcher.launch();
- return 1;
+ return 0;
+ } catch (IllegalArgumentException e) {
+ LOGGER.log(Level.SEVERE, "Wrong argument.", e);
+
+ return 1;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e);
+
+ return 1;
+ }
}
private enum Action {
diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java
index c5e8fbc1..bc0b525e 100644
--- a/libraries/launcher/org/multimc/Launcher.java
+++ b/libraries/launcher/org/multimc/Launcher.java
@@ -16,7 +16,8 @@
package org.multimc;
-public interface Launcher
-{
- int launch(ParamBucket params);
+public interface Launcher {
+
+ void launch() throws Exception;
+
}
diff --git a/libraries/launcher/org/multimc/LauncherFactory.java b/libraries/launcher/org/multimc/LauncherFactory.java
new file mode 100644
index 00000000..a2af8581
--- /dev/null
+++ b/libraries/launcher/org/multimc/LauncherFactory.java
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * PolyMC - Minecraft Launcher
+ * Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package org.multimc;
+
+import org.multimc.impl.OneSixLauncher;
+import org.multimc.utils.Parameters;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public final class LauncherFactory {
+
+ private static final LauncherFactory INSTANCE = new LauncherFactory();
+
+ private final Map<String, LauncherProvider> launcherRegistry = new HashMap<>();
+
+ private LauncherFactory() {
+ launcherRegistry.put("onesix", new LauncherProvider() {
+ @Override
+ public Launcher provide(Parameters parameters) {
+ return new OneSixLauncher(parameters);
+ }
+ });
+ }
+
+ public Launcher createLauncher(Parameters parameters) {
+ String name = parameters.first("launcher");
+
+ LauncherProvider launcherProvider = launcherRegistry.get(name);
+
+ if (launcherProvider == null)
+ throw new IllegalArgumentException("Invalid launcher type: " + name);
+
+ return launcherProvider.provide(parameters);
+ }
+
+ public static LauncherFactory getInstance() {
+ return INSTANCE;
+ }
+
+ public interface LauncherProvider {
+
+ Launcher provide(Parameters parameters);
+
+ }
+
+}
diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java
deleted file mode 100644
index 985a10e6..00000000
--- a/libraries/launcher/org/multimc/LegacyFrame.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package org.multimc;/*
- * Copyright 2012-2021 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.
- */
-
-import net.minecraft.Launcher;
-
-import javax.imageio.ImageIO;
-import java.applet.Applet;
-import java.awt.*;
-import java.awt.event.WindowEvent;
-import java.awt.event.WindowListener;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Scanner;
-
-public class LegacyFrame extends Frame implements WindowListener
-{
- private Launcher appletWrap = null;
- public LegacyFrame(String title)
- {
- super ( title );
- BufferedImage image;
- try {
- image = ImageIO.read ( new File ( "icon.png" ) );
- setIconImage ( image );
- } catch ( IOException e ) {
- e.printStackTrace();
- }
- this.addWindowListener ( this );
- }
-
- public void start (
- Applet mcApplet,
- String user,
- String session,
- int winSizeW,
- int winSizeH,
- boolean maximize,
- String serverAddress,
- String serverPort
- )
- {
- try {
- appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) );
- } catch ( MalformedURLException ignored ) {}
-
- // Implements support for launching in to multiplayer on classic servers using a mpticket
- // file generated by an external program and stored in the instance's root folder.
- File mpticketFile = null;
- Scanner fileReader = null;
- try {
- mpticketFile = new File(System.getProperty("user.dir") + "/../mpticket").getCanonicalFile();
- fileReader = new Scanner(new FileInputStream(mpticketFile), "ascii");
- String[] mpticketParams = new String[3];
-
- for(int i=0;i<3;i++) {
- if(fileReader.hasNextLine()) {
- mpticketParams[i] = fileReader.nextLine();
- } else {
- throw new IllegalArgumentException();
- }
- }
-
- // Assumes parameters are valid and in the correct order
- appletWrap.setParameter("server", mpticketParams[0]);
- appletWrap.setParameter("port", mpticketParams[1]);
- appletWrap.setParameter("mppass", mpticketParams[2]);
-
- fileReader.close();
- mpticketFile.delete();
- }
- catch (FileNotFoundException e) {}
- catch (IllegalArgumentException e) {
-
- fileReader.close();
- File mpticketFileCorrupt = new File(System.getProperty("user.dir") + "/../mpticket.corrupt");
- if(mpticketFileCorrupt.exists()) {
- mpticketFileCorrupt.delete();
- }
- mpticketFile.renameTo(mpticketFileCorrupt);
-
- System.err.println("Malformed mpticket file, missing argument.");
- e.printStackTrace(System.err);
- System.exit(-1);
- }
- catch (Exception e) {
- e.printStackTrace(System.err);
- System.exit(-1);
- }
-
- if (serverAddress != null)
- {
- appletWrap.setParameter("server", serverAddress);
- appletWrap.setParameter("port", serverPort);
- }
-
- appletWrap.setParameter ( "username", user );
- appletWrap.setParameter ( "sessionid", session );
- appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button.
- appletWrap.setParameter ( "haspaid", "true" ); // Some old versions need this for world saves to work.
- appletWrap.setParameter ( "demo", "false" );
- appletWrap.setParameter ( "fullscreen", "false" );
- mcApplet.setStub(appletWrap);
- this.add ( appletWrap );
- appletWrap.setPreferredSize ( new Dimension (winSizeW, winSizeH) );
- this.pack();
- this.setLocationRelativeTo ( null );
- this.setResizable ( true );
- if ( maximize ) {
- this.setExtendedState ( MAXIMIZED_BOTH );
- }
- validate();
- appletWrap.init();
- appletWrap.start();
- setVisible ( true );
- }
-
- @Override
- public void windowActivated ( WindowEvent e ) {}
-
- @Override
- public void windowClosed ( WindowEvent e ) {}
-
- @Override
- public void windowClosing ( WindowEvent e )
- {
- new Thread() {
- public void run() {
- try {
- Thread.sleep ( 30000L );
- } catch ( InterruptedException localInterruptedException ) {
- localInterruptedException.printStackTrace();
- }
- System.out.println ( "FORCING EXIT!" );
- System.exit ( 0 );
- }
- }
- .start();
-
- if ( appletWrap != null ) {
- appletWrap.stop();
- appletWrap.destroy();
- }
- // old minecraft versions can hang without this >_<
- System.exit ( 0 );
- }
-
- @Override
- public void windowDeactivated ( WindowEvent e ) {}
-
- @Override
- public void windowDeiconified ( WindowEvent e ) {}
-
- @Override
- public void windowIconified ( WindowEvent e ) {}
-
- @Override
- public void windowOpened ( WindowEvent e ) {}
-}
diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java
deleted file mode 100644
index e48029c2..00000000
--- a/libraries/launcher/org/multimc/Utils.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2012-2021 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.
- */
-
-package org.multimc;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.List;
-
-public class Utils
-{
- /**
- * Combine two parts of a path.
- *
- * @param path1
- * @param path2
- * @return the paths, combined
- */
- public static String combine(String path1, String path2)
- {
- File file1 = new File(path1);
- File file2 = new File(file1, path2);
- return file2.getPath();
- }
-
- /**
- * Join a list of strings into a string using a separator!
- *
- * @param strings the string list to join
- * @param separator the glue
- * @return the result.
- */
- public static String join(List<String> strings, String separator)
- {
- StringBuilder sb = new StringBuilder();
- String sep = "";
- for (String s : strings)
- {
- sb.append(sep).append(s);
- sep = separator;
- }
- return sb.toString();
- }
-
- /**
- * Finds a field that looks like a Minecraft base folder in a supplied class
- *
- * @param mc the class to scan
- */
- public static Field getMCPathField(Class<?> mc)
- {
- Field[] fields = mc.getDeclaredFields();
-
- for (Field f : fields)
- {
- if (f.getType() != File.class)
- {
- // Has to be File
- continue;
- }
- if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC))
- {
- // And Private Static.
- continue;
- }
- return f;
- }
- return null;
- }
-
-}
-
diff --git a/libraries/launcher/org/multimc/applet/LegacyFrame.java b/libraries/launcher/org/multimc/applet/LegacyFrame.java
new file mode 100644
index 00000000..caec079c
--- /dev/null
+++ b/libraries/launcher/org/multimc/applet/LegacyFrame.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2012-2021 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.
+ */
+
+package org.multimc.applet;
+
+import net.minecraft.Launcher;
+
+import javax.imageio.ImageIO;
+import java.applet.Applet;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class LegacyFrame extends Frame {
+
+ private static final Logger LOGGER = Logger.getLogger("LegacyFrame");
+
+ private final Launcher appletWrap;
+
+ public LegacyFrame(String title, Applet mcApplet) {
+ super(title);
+
+ appletWrap = new Launcher(mcApplet);
+
+ mcApplet.setStub(appletWrap);
+
+ try {
+ setIconImage(ImageIO.read(new File("icon.png")));
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e);
+ }
+
+ addWindowListener(new ForceExitHandler());
+ }
+
+ public void start (
+ String user,
+ String session,
+ int winSizeW,
+ int winSizeH,
+ boolean maximize,
+ String serverAddress,
+ String serverPort
+ ) {
+ // Implements support for launching in to multiplayer on classic servers using a mpticket
+ // file generated by an external program and stored in the instance's root folder.
+
+ Path mpticketFile =
+ Paths.get(System.getProperty("user.dir"), "..", "mpticket");
+
+ Path mpticketFileCorrupt =
+ Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt");
+
+ if (Files.exists(mpticketFile)) {
+ try {
+ List<String> lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8);
+
+ if (lines.size() < 3) {
+ Files.move(
+ mpticketFile,
+ mpticketFileCorrupt,
+ StandardCopyOption.REPLACE_EXISTING
+ );
+
+ LOGGER.warning("Mpticket file is corrupted!");
+ } else {
+ // Assumes parameters are valid and in the correct order
+ appletWrap.setParameter("server", lines.get(0));
+ appletWrap.setParameter("port", lines.get(1));
+ appletWrap.setParameter("mppass", lines.get(2));
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e);
+ }
+ }
+
+ if (serverAddress != null) {
+ appletWrap.setParameter("server", serverAddress);
+ appletWrap.setParameter("port", serverPort);
+ }
+
+ appletWrap.setParameter("username", user);
+ appletWrap.setParameter("sessionid", session);
+ appletWrap.setParameter("stand-alone", "true"); // Show the quit button.
+ appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work.
+ appletWrap.setParameter("demo", "false");
+ appletWrap.setParameter("fullscreen", "false");
+
+ add(appletWrap);
+
+ appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH));
+
+ pack();
+
+ setLocationRelativeTo(null);
+ setResizable(true);
+
+ if (maximize)
+ this.setExtendedState(MAXIMIZED_BOTH);
+
+ validate();
+
+ appletWrap.init();
+ appletWrap.start();
+
+ setVisible(true);
+ }
+
+ private final class ForceExitHandler extends WindowAdapter {
+
+ @Override
+ public void windowClosing(WindowEvent e) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(30000L);
+ } catch (InterruptedException localInterruptedException) {
+ localInterruptedException.printStackTrace();
+ }
+
+ LOGGER.info("Forcing exit!");
+
+ System.exit(0);
+ }
+ }).start();
+
+ if (appletWrap != null) {
+ appletWrap.stop();
+ appletWrap.destroy();
+ }
+
+ // old minecraft versions can hang without this >_<
+ System.exit(0);
+ }
+
+ }
+
+}
diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java
index ba12951d..9edbb826 100644
--- a/libraries/launcher/org/multimc/NotFoundException.java
+++ b/libraries/launcher/org/multimc/exception/ParameterNotFoundException.java
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-package org.multimc;
+package org.multimc.exception;
+
+public final class ParameterNotFoundException extends IllegalArgumentException {
+
+ public ParameterNotFoundException(String key) {
+ super("Unknown parameter name: " + key);
+ }
-public class NotFoundException extends Exception
-{
}
diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/exception/ParseException.java
index 7ea44c1f..848b395d 100644
--- a/libraries/launcher/org/multimc/ParseException.java
+++ b/libraries/launcher/org/multimc/exception/ParseException.java
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package org.multimc;
+package org.multimc.exception;
+
+public final class ParseException extends IllegalArgumentException {
-public class ParseException extends java.lang.Exception
-{
- public ParseException() { super(); }
public ParseException(String message) {
super(message);
}
+
}
diff --git a/libraries/launcher/org/multimc/impl/OneSixLauncher.java b/libraries/launcher/org/multimc/impl/OneSixLauncher.java
new file mode 100644
index 00000000..b981e4ff
--- /dev/null
+++ b/libraries/launcher/org/multimc/impl/OneSixLauncher.java
@@ -0,0 +1,189 @@
+/* Copyright 2012-2021 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.
+ */
+
+package org.multimc.impl;
+
+import org.multimc.Launcher;
+import org.multimc.applet.LegacyFrame;
+import org.multimc.utils.Parameters;
+import org.multimc.utils.Utils;
+
+import java.applet.Applet;
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public final class OneSixLauncher implements Launcher {
+
+ private static final int DEFAULT_WINDOW_WIDTH = 854;
+ private static final int DEFAULT_WINDOW_HEIGHT = 480;
+
+ private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
+
+ // parameters, separated from ParamBucket
+ private final List<String> mcParams;
+ private final List<String> traits;
+ private final String appletClass;
+ private final String mainClass;
+ private final String userName, sessionId;
+ private final String windowTitle;
+
+ // secondary parameters
+ private final int winSizeW;
+ private final int winSizeH;
+ private final boolean maximize;
+ private final String cwd;
+
+ private final String serverAddress;
+ private final String serverPort;
+
+ private final ClassLoader classLoader;
+
+ public OneSixLauncher(Parameters params) {
+ classLoader = ClassLoader.getSystemClassLoader();
+
+ mcParams = params.allSafe("param", Collections.<String>emptyList());
+ mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
+ appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
+ traits = params.allSafe("traits", Collections.<String>emptyList());
+
+ userName = params.first("userName");
+ sessionId = params.first("sessionId");
+ windowTitle = params.firstSafe("windowTitle", "Minecraft");
+
+ serverAddress = params.firstSafe("serverAddress", null);
+ serverPort = params.firstSafe("serverPort", null);
+
+ cwd = System.getProperty("user.dir");
+
+ String windowParams = params.firstSafe("windowParams", null);
+
+ if (windowParams != null) {
+ String[] dimStrings = windowParams.split("x");
+
+ if (windowParams.equalsIgnoreCase("max")) {
+ maximize = true;
+
+ winSizeW = DEFAULT_WINDOW_WIDTH;
+ winSizeH = DEFAULT_WINDOW_HEIGHT;
+ } else if (dimStrings.length == 2) {
+ maximize = false;
+
+ winSizeW = Integer.parseInt(dimStrings[0]);
+ winSizeH = Integer.parseInt(dimStrings[1]);
+ } else {
+ throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams);
+ }
+ } else {
+ maximize = false;
+
+ winSizeW = DEFAULT_WINDOW_WIDTH;
+ winSizeH = DEFAULT_WINDOW_HEIGHT;
+ }
+ }
+
+ private void invokeMain(Class<?> mainClass) throws Exception {
+ Method method = mainClass.getMethod("main", String[].class);
+
+ method.invoke(null, (Object) mcParams.toArray(new String[0]));
+ }
+
+ private void legacyLaunch() throws Exception {
+ // Get the Minecraft Class and set the base folder
+ Class<?> minecraftClass = classLoader.loadClass(mainClass);
+
+ Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass);
+
+ if (baseDirField == null) {
+ LOGGER.warning("Could not find Minecraft path field.");
+ } else {
+ baseDirField.setAccessible(true);
+
+ baseDirField.set(null, new File(cwd));
+ }
+
+ System.setProperty("minecraft.applet.TargetDirectory", cwd);
+
+ if (!traits.contains("noapplet")) {
+ LOGGER.info("Launching with applet wrapper...");
+
+ try {
+ Class<?> mcAppletClass = classLoader.loadClass(appletClass);
+
+ Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance();
+
+ LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet);
+
+ mcWindow.start(
+ userName,
+ sessionId,
+ winSizeW,
+ winSizeH,
+ maximize,
+ serverAddress,
+ serverPort
+ );
+
+ return;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e);
+
+ LOGGER.warning("Falling back to using main class.");
+ }
+ }
+
+ invokeMain(minecraftClass);
+ }
+
+ private void launchWithMainClass() throws Exception {
+ // window size, title and state, onesix
+
+ // FIXME: there is no good way to maximize the minecraft window in onesix.
+ // the following often breaks linux screen setups
+ // mcparams.add("--fullscreen");
+
+ if (!maximize) {
+ mcParams.add("--width");
+ mcParams.add(Integer.toString(winSizeW));
+ mcParams.add("--height");
+ mcParams.add(Integer.toString(winSizeH));
+ }
+
+ if (serverAddress != null) {
+ mcParams.add("--server");
+ mcParams.add(serverAddress);
+ mcParams.add("--port");
+ mcParams.add(serverPort);
+ }
+
+ invokeMain(classLoader.loadClass(mainClass));
+ }
+
+ @Override
+ public void launch() throws Exception {
+ if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) {
+ // legacy launch uses the applet wrapper
+ legacyLaunch();
+ } else {
+ // normal launch just calls main()
+ launchWithMainClass();
+ }
+ }
+
+}
diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
deleted file mode 100644
index 0058bd43..00000000
--- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/* Copyright 2012-2021 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.
- */
-
-package org.multimc.onesix;
-
-import org.multimc.*;
-
-import java.applet.Applet;
-import java.io.File;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-public class OneSixLauncher implements Launcher
-{
-
- private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
-
- // parameters, separated from ParamBucket
- private List<String> libraries;
- private List<String> mcparams;
- private List<String> mods;
- private List<String> jarmods;
- private List<String> coremods;
- private List<String> traits;
- private String appletClass;
- private String mainClass;
- private String nativePath;
- private String userName, sessionId;
- private String windowTitle;
- private String windowParams;
-
- // secondary parameters
- private int winSizeW;
- private int winSizeH;
- private boolean maximize;
- private String cwd;
-
- private String serverAddress;
- private String serverPort;
-
- // the much abused system classloader, for convenience (for further abuse)
- private ClassLoader cl;
-
- private void processParams(ParamBucket params) throws NotFoundException
- {
- libraries = params.all("cp");
- mcparams = params.allSafe("param", new ArrayList<String>() );
- mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
- appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
- traits = params.allSafe("traits", new ArrayList<String>());
- nativePath = params.first("natives");
-
- userName = params.first("userName");
- sessionId = params.first("sessionId");
- windowTitle = params.firstSafe("windowTitle", "Minecraft");
- windowParams = params.firstSafe("windowParams", "854x480");
-
- serverAddress = params.firstSafe("serverAddress", null);
- serverPort = params.firstSafe("serverPort", null);
-
- cwd = System.getProperty("user.dir");
-
- winSizeW = 854;
- winSizeH = 480;
- maximize = false;
-
- String[] dimStrings = windowParams.split("x");
-
- if (windowParams.equalsIgnoreCase("max"))
- {
- maximize = true;
- }
- else if (dimStrings.length == 2)
- {
- try
- {
- winSizeW = Integer.parseInt(dimStrings[0]);
- winSizeH = Integer.parseInt(dimStrings[1]);
- } catch (NumberFormatException ignored) {}
- }
- }
-
- int legacyLaunch()
- {
- // Get the Minecraft Class and set the base folder
- Class<?> mc;
- try
- {
- mc = cl.loadClass(mainClass);
-
- Field f = Utils.getMCPathField(mc);
-
- if (f == null)
- {
- LOGGER.warning("Could not find Minecraft path field.");
- }
- else
- {
- f.setAccessible(true);
- f.set(null, new File(cwd));
- }
- } catch (Exception e)
- {
- LOGGER.log(
- Level.SEVERE,
- "Could not set base folder. Failed to find/access Minecraft main class:",
- e
- );
-
- return -1;
- }
-
- System.setProperty("minecraft.applet.TargetDirectory", cwd);
-
- if(!traits.contains("noapplet"))
- {
- LOGGER.info("Launching with applet wrapper...");
- try
- {
- Class<?> MCAppletClass = cl.loadClass(appletClass);
- Applet mcappl = (Applet) MCAppletClass.newInstance();
- LegacyFrame mcWindow = new LegacyFrame(windowTitle);
- mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize, serverAddress, serverPort);
- return 0;
- } catch (Exception e)
- {
- LOGGER.log(Level.SEVERE, "Applet wrapper failed:", e);
-
- LOGGER.warning("Falling back to using main class.");
- }
- }
-
- // init params for the main method to chomp on.
- String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
- try
- {
- mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray);
- return 0;
- } catch (Exception e)
- {
- LOGGER.log(Level.SEVERE, "Failed to invoke the Minecraft main class:", e);
-
- return -1;
- }
- }
-
- int launchWithMainClass()
- {
- // window size, title and state, onesix
- if (maximize)
- {
- // FIXME: there is no good way to maximize the minecraft window in onesix.
- // the following often breaks linux screen setups
- // mcparams.add("--fullscreen");
- }
- else
- {
- mcparams.add("--width");
- mcparams.add(Integer.toString(winSizeW));
- mcparams.add("--height");
- mcparams.add(Integer.toString(winSizeH));
- }
-
- if (serverAddress != null)
- {
- mcparams.add("--server");
- mcparams.add(serverAddress);
- mcparams.add("--port");
- mcparams.add(serverPort);
- }
-
- // Get the Minecraft Class.
- Class<?> mc;
- try
- {
- mc = cl.loadClass(mainClass);
- } catch (ClassNotFoundException e)
- {
- LOGGER.log(Level.SEVERE, "Failed to find Minecraft main class:", e);
-
- return -1;
- }
-
- // get the main method.
- Method meth;
- try
- {
- meth = mc.getMethod("main", String[].class);
- } catch (NoSuchMethodException e)
- {
- LOGGER.log(Level.SEVERE, "Failed to acquire the main method:", e);
-
- return -1;
- }
-
- // init params for the main method to chomp on.
- String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
- try
- {
- // static method doesn't have an instance
- meth.invoke(null, (Object) paramsArray);
- } catch (Exception e)
- {
- LOGGER.log(Level.SEVERE, "Failed to start Minecraft:", e);
-
- return -1;
- }
- return 0;
- }
-
- @Override
- public int launch(ParamBucket params)
- {
- // get and process the launch script params
- try
- {
- processParams(params);
- } catch (NotFoundException e)
- {
- LOGGER.log(Level.SEVERE, "Not enough arguments!");
-
- return -1;
- }
-
- // grab the system classloader and ...
- cl = ClassLoader.getSystemClassLoader();
-
- if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") )
- {
- // legacy launch uses the applet wrapper
- return legacyLaunch();
- }
- else
- {
- // normal launch just calls main()
- return launchWithMainClass();
- }
- }
-
-}
diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/utils/Parameters.java
index 8ff03ddc..7be790c2 100644
--- a/libraries/launcher/org/multimc/ParamBucket.java
+++ b/libraries/launcher/org/multimc/utils/Parameters.java
@@ -14,36 +14,41 @@
* limitations under the License.
*/
-package org.multimc;
+package org.multimc.utils;
+
+import org.multimc.exception.ParameterNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class ParamBucket
-{
+public final class Parameters {
private final Map<String, List<String>> paramsMap = new HashMap<>();
- public void add(String key, String value)
- {
- paramsMap.computeIfAbsent(key, k -> new ArrayList<>())
- .add(value);
+ public void add(String key, String value) {
+ List<String> params = paramsMap.get(key);
+
+ if (params == null) {
+ params = new ArrayList<>();
+
+ paramsMap.put(key, params);
+ }
+
+ params.add(value);
}
- public List<String> all(String key) throws NotFoundException
- {
+ public List<String> all(String key) throws ParameterNotFoundException {
List<String> params = paramsMap.get(key);
if (params == null)
- throw new NotFoundException();
+ throw new ParameterNotFoundException(key);
return params;
}
- public List<String> allSafe(String key, List<String> def)
- {
+ public List<String> allSafe(String key, List<String> def) {
List<String> params = paramsMap.get(key);
if (params == null || params.isEmpty())
@@ -52,23 +57,16 @@ public class ParamBucket
return params;
}
- public List<String> allSafe(String key)
- {
- return allSafe(key, new ArrayList<>());
- }
-
- public String first(String key) throws NotFoundException
- {
+ public String first(String key) throws ParameterNotFoundException {
List<String> list = all(key);
if (list.isEmpty())
- throw new NotFoundException();
+ throw new ParameterNotFoundException(key);
return list.get(0);
}
- public String firstSafe(String key, String def)
- {
+ public String firstSafe(String key, String def) {
List<String> params = paramsMap.get(key);
if (params == null || params.isEmpty())
@@ -77,9 +75,4 @@ public class ParamBucket
return params.get(0);
}
- public String firstSafe(String key)
- {
- return firstSafe(key, "");
- }
-
}
diff --git a/libraries/launcher/org/multimc/utils/Utils.java b/libraries/launcher/org/multimc/utils/Utils.java
new file mode 100644
index 00000000..416eff26
--- /dev/null
+++ b/libraries/launcher/org/multimc/utils/Utils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2021 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.
+ */
+
+package org.multimc.utils;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+public final class Utils {
+
+ private Utils() {}
+
+ /**
+ * Finds a field that looks like a Minecraft base folder in a supplied class
+ *
+ * @param clazz the class to scan
+ */
+ public static Field getMinecraftBaseDirField(Class<?> clazz) {
+ for (Field f : clazz.getDeclaredFields()) {
+ // Has to be File
+ if (f.getType() != File.class)
+ continue;
+
+ // And Private Static.
+ if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers()))
+ continue;
+
+ return f;
+ }
+
+ return null;
+ }
+
+}
+