This commit is contained in:
Trial97 2023-08-05 19:00:12 +03:00
commit 377fc0352a
No known key found for this signature in database
GPG Key ID: 55EF5DA53DB36318
86 changed files with 1765 additions and 1098 deletions

View File

@ -68,7 +68,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.5.1' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -80,7 +80,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.5.1' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -90,7 +90,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: '' qt_arch: ''
qt_version: '6.5.0' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''

View File

@ -318,6 +318,8 @@ add_subdirectory(program_info)
####################################### Install layout ####################################### ####################################### Install layout #######################################
set(Launcher_ENABLE_UPDATER NO)
if(NOT (UNIX AND APPLE)) if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable" # Install "portable.txt" if selected component is "portable"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL) install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
@ -342,9 +344,9 @@ if(UNIX AND APPLE)
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}") set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml") set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive") set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive") set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
@ -353,8 +355,12 @@ if(UNIX AND APPLE)
# directories to look for dependencies # directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR}) set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "")
set(Launcher_ENABLE_UPDATER YES)
endif()
# install as bundle # install as bundle
set(INSTALL_BUNDLE "full") set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
# Add the icon # Add the icon
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns) install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
@ -367,7 +373,7 @@ elseif(UNIX)
set(JARS_DEST_DIR "share/${Launcher_Name}") set(JARS_DEST_DIR "share/${Launcher_Name}")
# install as bundle with no dependencies included # install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps") set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies")
# Set RPATH # Set RPATH
SET(Launcher_BINARY_RPATH "$ORIGIN/") SET(Launcher_BINARY_RPATH "$ORIGIN/")
@ -401,7 +407,7 @@ elseif(WIN32)
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle # install as bundle
set(INSTALL_BUNDLE "full") set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
else() else()
message(FATAL_ERROR "Platform not supported") message(FATAL_ERROR "Platform not supported")
endif() endif()

View File

@ -19,7 +19,7 @@ In an effort to ensure that the code you contribute is actually compatible with
This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message: This can be done by appending `-s` to your `git commit` call, or by manually appending the following text to your commit message:
``` ```text
<commit message> <commit message>
Signed-off-by: Author name <Author email> Signed-off-by: Author name <Author email>
@ -27,7 +27,7 @@ Signed-off-by: Author name <Author email>
By signing off your work, you agree to the terms below: By signing off your work, you agree to the terms below:
``` ```text
Developer's Certificate of Origin 1.1 Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that: By making a contribution to this project, I certify that:
@ -62,7 +62,6 @@ As a bonus, you can also [cryptographically sign your commits][gh-signing-commit
[gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits [gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
[gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits [gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits
## Backporting to Release Branches ## Backporting to Release Branches
We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches. We use [automated backports](https://github.com/PrismLauncher/PrismLauncher/blob/develop/.github/workflows/backport.yml) to merge specific contributions from develop into `release` branches.

View File

@ -91,11 +91,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1690026219, "lastModified": 1690630721,
"narHash": "sha256-oOduRk/kzQxOBknZXTLSEYd7tk+GoKvr8wV6Ab+t4AU=", "narHash": "sha256-Y04onHyBQT4Erfr2fc82dbJTfXGYrf4V0ysLUYnPOP8=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f465da166263bc0d4b39dfd4ca28b777c92d4b73", "rev": "d2b52322f35597c62abf56de91b0236746b2a03d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -138,11 +138,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1689668210, "lastModified": 1690628027,
"narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=", "narHash": "sha256-OTSbA2hM6VmxyZ/4siYPANffMBzIsKu04GLjXcv8ST0=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "eb433bff05b285258be76513add6f6c57b441775", "rev": "1e2443dd3f669eb65433b2fc26a3065e05a7dc9c",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -18,6 +18,8 @@ finish-args:
- --filesystem=xdg-run/app/com.discordapp.Discord:create - --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop # Mod drag&drop
- --filesystem=xdg-download:ro - --filesystem=xdg-download:ro
# FTBApp import
- --filesystem=~/.ftba:ro
cleanup: cleanup:
- /lib/libGLU* - /lib/libGLU*

View File

@ -1,5 +1,6 @@
builds: builds:
exclude: [] exclude: []
include: include:
- "devShells.*-linux.*" - "checks.x86_64-linux.*"
- "packages.*-linux.*" - "devShells.*.*"
- "packages.*.*"

View File

@ -131,7 +131,7 @@
#include "MangoHud.h" #include "MangoHud.h"
#endif #endif
#ifdef Q_OS_MAC #if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h" #include "updater/MacSparkleUpdater.h"
#endif #endif
@ -281,7 +281,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
} }
else else
{ {
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); QDir foo;
if (DesktopServices::isSnap())
{
foo = QDir(getenv("SNAP_USER_COMMON"));
}
else
{
foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
}
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; adjustedBy = "Persistent data path";
@ -628,9 +637,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGlobalGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true); m_settings->registerSetting("RecordGameTime", true);
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
// Minecraft mods // Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModMetadataDisabled", false);
@ -704,7 +710,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QUrl metaUrl(m_settings->get("MetaURLOverride").toString()); QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
// get rid of invalid meta urls // get rid of invalid meta urls
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https") if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https"))
m_settings->reset("MetaURLOverride"); m_settings->reset("MetaURLOverride");
} }
@ -776,7 +782,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
qDebug() << "Initializing updater"; qDebug() << "Initializing updater";
#ifdef Q_OS_MAC #if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater()); m_updater.reset(new MacSparkleUpdater());
#endif #endif
qDebug() << "<> Updater started."; qDebug() << "<> Updater started.";

View File

@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h net/Validator.h
net/Upload.cpp net/Upload.cpp
net/Upload.h net/Upload.h
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
net/ApiUpload.h
net/NetRequest.cpp
net/NetRequest.h
) )
# Game launch logic # Game launch logic
@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/CreateGameFolders.h minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp minecraft/launch/LauncherPartLaunch.cpp
@ -501,6 +508,11 @@ set(FTB_SOURCES
modplatform/legacy_ftb/PrivatePackManager.cpp modplatform/legacy_ftb/PrivatePackManager.cpp
modplatform/legacy_ftb/PackHelpers.h modplatform/legacy_ftb/PackHelpers.h
modplatform/import_ftb/PackInstallTask.h
modplatform/import_ftb/PackInstallTask.cpp
modplatform/import_ftb/PackHelpers.h
modplatform/import_ftb/PackHelpers.cpp
) )
set(FLAME_SOURCES set(FLAME_SOURCES
@ -668,7 +680,7 @@ set(LOGIC_SOURCES
${ATLAUNCHER_SOURCES} ${ATLAUNCHER_SOURCES}
) )
if(APPLE) if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES}) set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
endif() endif()
@ -872,6 +884,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/legacy_ftb/ListModel.h ui/pages/modplatform/legacy_ftb/ListModel.h
ui/pages/modplatform/legacy_ftb/ListModel.cpp ui/pages/modplatform/legacy_ftb/ListModel.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.h
ui/pages/modplatform/import_ftb/ListModel.h
ui/pages/modplatform/import_ftb/ListModel.cpp
ui/pages/modplatform/flame/FlameModel.cpp ui/pages/modplatform/flame/FlameModel.cpp
ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlameModel.h
ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.cpp
@ -1046,6 +1063,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui ui/pages/modplatform/technic/TechnicPage.ui
@ -1102,6 +1120,7 @@ endif()
# Add executable # Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
systeminfo systeminfo
@ -1141,17 +1160,23 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH 1) set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/") set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
if(Launcher_ENABLE_UPDATER)
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
add_compile_definitions(SPARKLE_ENABLED)
endif()
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
"-framework AppKit" "-framework AppKit"
"-framework Carbon" "-framework Carbon"
"-framework Foundation" "-framework Foundation"
"-framework ApplicationServices" "-framework ApplicationServices"
) )
if(Launcher_ENABLE_UPDATER)
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif()
endif() endif()
target_link_libraries(Launcher_logic) target_link_libraries(Launcher_logic)
@ -1213,7 +1238,7 @@ if(WIN32)
) )
endif() endif()
if (UNIX AND APPLE) if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
# Add Sparkle updater # Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of # It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
# the framework aren't installed # the framework aren't installed

View File

@ -118,7 +118,7 @@ bool openDirectory(const QString &path, bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak()) if(!isSandbox())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -139,7 +139,7 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak()) if(!isSandbox())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -157,7 +157,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application; qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
if(!isFlatpak()) if(!isSandbox())
{ {
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
@ -177,7 +177,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{ {
qDebug() << "Running" << application << "with args" << args.join(' '); qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak()) if(!isSandbox())
{ {
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() return IndirectOpen([&]()
@ -202,7 +202,7 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url); return QDesktopServices::openUrl(url);
}; };
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
if(!isFlatpak()) if(!isSandbox())
{ {
return IndirectOpen(f); return IndirectOpen(f);
} }
@ -224,4 +224,18 @@ bool isFlatpak()
#endif #endif
} }
bool isSnap()
{
#ifdef Q_OS_LINUX
return getenv("SNAP");
#else
return false;
#endif
}
bool isSandbox()
{
return isSnap() || isFlatpak();
}
} }

View File

@ -34,5 +34,18 @@ namespace DesktopServices
*/ */
bool openUrl(const QUrl &url); bool openUrl(const QUrl &url);
/**
* Determine whether the launcher is running in a Flatpak environment
*/
bool isFlatpak(); bool isFlatpak();
/**
* Determine whether the launcher is running in a Snap environment
*/
bool isSnap();
/**
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
*/
bool isSandbox();
} }

View File

@ -51,6 +51,8 @@
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "net/ApiDownload.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
#include <algorithm> #include <algorithm>
@ -95,7 +97,7 @@ void InstanceImportTask::executeTask()
m_archivePath = entry->getFullPath(); m_archivePath = entry->getFullPath();
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);

View File

@ -24,6 +24,8 @@
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version, ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs, const std::shared_ptr<ResourceFolderModel> packs,
@ -51,7 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
} }
} }
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename()))); m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress); connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);

View File

@ -15,7 +15,7 @@
#include "BaseEntity.h" #include "BaseEntity.h"
#include "net/Download.h" #include "net/ApiDownload.h"
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "Json.h" #include "Json.h"
@ -130,7 +130,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
auto url = this->url(); auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename()); auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
entry->setStale(true); entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
/* /*
* The validator parses the file and loads it into the object. * The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage. * If that fails, the file is not written to storage.

View File

@ -45,7 +45,7 @@
#include "AssetsUtils.h" #include "AssetsUtils.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "net/Download.h" #include "net/ApiDownload.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "BuildConfig.h" #include "BuildConfig.h"
@ -311,7 +311,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
QFileInfo objectFile(getLocalPath()); QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size)) if ((!objectFile.isFile()) || (objectFile.size() != size))
{ {
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath()); auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
if(hash.size()) if(hash.size())
{ {
auto rawHash = QByteArray::fromHex(hash.toLatin1()); auto rawHash = QByteArray::fromHex(hash.toLatin1());

View File

@ -36,7 +36,7 @@
#include "Library.h" #include "Library.h"
#include "MinecraftInstance.h" #include "MinecraftInstance.h"
#include <net/Download.h> #include <net/ApiDownload.h>
#include <net/ChecksumValidator.h> #include <net/ChecksumValidator.h>
#include <FileSystem.h> #include <FileSystem.h>
#include <BuildConfig.h> #include <BuildConfig.h>
@ -129,14 +129,14 @@ QList<NetAction::Ptr> Library::getDownloads(
if(sha1.size()) if(sha1.size())
{ {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options); auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl); out.append(dl);
} }
else else
{ {
out.append(Net::Download::makeCached(url, entry, options)); out.append(Net::ApiDownload::makeCached(url, entry, options));
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
} }
return true; return true;

View File

@ -61,7 +61,6 @@
#include "launch/steps/QuitAfterGameStop.h" #include "launch/steps/QuitAfterGameStop.h"
#include "minecraft/launch/LauncherPartLaunch.h" #include "minecraft/launch/LauncherPartLaunch.h"
#include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h" #include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.h" #include "minecraft/launch/ClaimAccount.h"
#include "minecraft/launch/ReconstructAssets.h" #include "minecraft/launch/ReconstructAssets.h"
@ -167,10 +166,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting); m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting); m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
// Native library workarounds // Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
@ -990,15 +985,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(makeShared<CheckJava>(pptr)); process->appendStep(makeShared<CheckJava>(pptr));
} }
// check launch method
QStringList validMethods = {"LauncherPart", "DirectJava"};
QString method = launchMethod();
if(!validMethods.contains(method))
{
process->appendStep(makeShared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
return process;
}
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732) // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
{ {
process->appendStep(makeShared<CreateGameFolders>(pptr)); process->appendStep(makeShared<CreateGameFolders>(pptr));
@ -1072,24 +1058,12 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
{ {
// actually launch the game // actually launch the game
auto method = launchMethod();
if(method == "LauncherPart")
{
auto step = makeShared<LauncherPartLaunch>(pptr); auto step = makeShared<LauncherPartLaunch>(pptr);
step->setWorkingDirectory(gameRoot()); step->setWorkingDirectory(gameRoot());
step->setAuthSession(session); step->setAuthSession(session);
step->setServerToJoin(serverToJoin); step->setServerToJoin(serverToJoin);
process->appendStep(step); process->appendStep(step);
} }
else if (method == "DirectJava")
{
auto step = makeShared<DirectJavaLaunch>(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
}
// run post-exit command if that's needed // run post-exit command if that's needed
if(getPostExitCommand().size()) if(getPostExitCommand().size())
@ -1111,11 +1085,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
return m_launchProcess; return m_launchProcess;
} }
QString MinecraftInstance::launchMethod()
{
return settings()->get("MCLaunchMethod").toString();
}
JavaVersion MinecraftInstance::getJavaVersion() JavaVersion MinecraftInstance::getJavaVersion()
{ {
return JavaVersion(settings()->get("JavaVersion").toString()); return JavaVersion(settings()->get("JavaVersion").toString());

View File

@ -165,8 +165,6 @@ public:
protected: protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session); QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();
QString launchMethod();
protected: // data protected: // data
std::shared_ptr<PackProfile> m_components; std::shared_ptr<PackProfile> m_components;

View File

@ -1,166 +0,0 @@
/* 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 "DirectJavaLaunch.h"
#include <QStandardPaths>
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <FileSystem.h>
#include <Commandline.h>
#include "Application.h"
#ifdef Q_OS_LINUX
#include "gamemode_client.h"
#endif
DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)
{
connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state);
}
void DirectJavaLaunch::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
QStringList args = minecraftInstance->javaArguments();
args.append("-Djava.library.path=" + minecraftInstance->getNativePath());
auto classPathEntries = minecraftInstance->getClassPath();
args.append("-cp");
QString classpath;
#ifdef Q_OS_WIN32
classpath = classPathEntries.join(';');
#else
classpath = classPathEntries.join(':');
#endif
args.append(classpath);
args.append(minecraftInstance->getMainClass());
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
m_process.setProcessEnvironment(instance->createLaunchEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin);
args.append(mcArgs);
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
emitFailed(tr(reason).arg(wrapperCommand));
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::Launcher);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
#ifdef Q_OS_LINUX
if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
{
auto pid = m_process.processId();
if (pid)
{
gamemode_request_start_for(pid);
}
}
#endif
}
void DirectJavaLaunch::on_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instance can't start
const char *reason = QT_TR_NOOP("Could not launch Minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(tr(reason));
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
{
m_parent->setPid(-1);
emitFailed(tr("Game crashed."));
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed(tr("Game crashed."));
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
break;
default:
break;
}
}
void DirectJavaLaunch::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
void DirectJavaLaunch::proceed()
{
// nil
}
bool DirectJavaLaunch::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@ -1,58 +0,0 @@
/* 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 <launch/LaunchStep.h>
#include <LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
#include "MinecraftServerTarget.h"
class DirectJavaLaunch: public LaunchStep
{
Q_OBJECT
public:
explicit DirectJavaLaunch(LaunchTask *parent);
virtual ~DirectJavaLaunch() {};
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
{
m_serverToJoin = std::move(serverToJoin);
}
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
MinecraftServerTargetPtr m_serverToJoin;
};

View File

@ -1,9 +1,9 @@
#include "LocalModParseTask.h" #include "LocalModParseTask.h"
#include <qdcss.h>
#include <quazip/quazip.h> #include <quazip/quazip.h>
#include <quazip/quazipfile.h> #include <quazip/quazipfile.h>
#include <toml++/toml.h> #include <toml++/toml.h>
#include <qdcss.h>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@ -369,12 +369,11 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
details.icon_file = icon.toString(); details.icon_file = icon.toString();
} }
} }
} }
return details; return details;
} }
ModDetails ReadForgeInfo(QString fileName) ModDetails ReadForgeInfo(QByteArray contents)
{ {
ModDetails details; ModDetails details;
// Read the data // Read the data
@ -382,7 +381,7 @@ ModDetails ReadForgeInfo(QString fileName)
details.mod_id = "Forge"; details.mod_id = "Forge";
details.homeurl = "http://www.minecraftforge.net/forum/"; details.homeurl = "http://www.minecraftforge.net/forum/";
INIFile ini; INIFile ini;
if (!ini.loadFile(fileName)) if (!ini.loadFile(contents))
return details; return details;
QString major = ini.get("forge.major.number", "0").toString(); QString major = ini.get("forge.major.number", "0").toString();
@ -554,7 +553,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
return false; return false;
} }
details = ReadForgeInfo(file.getFileName()); details = ReadForgeInfo(file.readAll());
file.close(); file.close();
zip.close(); zip.close();

View File

@ -7,6 +7,8 @@
#include "Application.h" #include "Application.h"
#include "net/ApiDownload.h"
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst) AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
{ {
m_inst = inst; m_inst = inst;
@ -34,7 +36,7 @@ void AssetUpdateTask::executeTask()
entry->setStale(true); entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1(); auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1; qDebug() << "Asset index SHA1:" << hexSha1;
auto dl = Net::Download::makeCached(indexUrl, entry); auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1()); auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl); job->addNetAction(dl);

View File

@ -8,6 +8,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "net/ApiDownload.h"
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst) FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{ {
m_inst = inst; m_inst = inst;
@ -68,7 +70,7 @@ void FMLLibrariesTask::executeTask()
{ {
auto entry = metacache->resolveEntry("fmllibs", lib.filename); auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options)); dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options));
} }
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);

View File

@ -53,6 +53,8 @@
#include "meta/Version.h" #include "meta/Version.h"
#include "meta/VersionList.h" #include "meta/VersionList.h"
#include "net/ApiDownload.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h" #include "Application.h"
@ -84,7 +86,7 @@ void PackInstallTask::executeTask()
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) }; NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl = auto searchUrl =
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name); QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true); entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) { if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1()); auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true); entry->setStale(true);
modsToExtract.insert(entry->getFullPath(), mod); modsToExtract.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true); entry->setStale(true);
modsToDecomp.insert(entry->getFullPath(), mod); modsToDecomp.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true); entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));

View File

@ -1,7 +1,9 @@
#include "FileResolvingTask.h" #include "FileResolvingTask.h"
#include "Json.h" #include "Json.h"
#include "net/ApiUpload.h"
#include "net/Upload.h" #include "net/Upload.h"
#include "net/ApiDownload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h" #include "modplatform/modrinth/ModrinthPackIndex.h"
@ -38,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()
return l; return l;
})); }));
QByteArray data = Json::toText(object); QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data); auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
m_dljob->addNetAction(dl); m_dljob->addNetAction(dl);
auto step_progress = std::make_shared<TaskStepProgress>(); auto step_progress = std::make_shared<TaskStepProgress>();
@ -99,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()
if (!hash.isEmpty()) { if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash); auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto dl = Net::Download::makeByteArray(QUrl(url), output); auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; }); QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl); m_checkJob->addNetAction(dl);
@ -171,7 +173,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto projectId = mod->projectId; auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, output); auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName; qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() { QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done

View File

@ -8,7 +8,9 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/ApiUpload.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/ApiDownload.h"
#include "net/Upload.h" #include "net/Upload.h"
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response) Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
@ -26,7 +28,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw)); netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
return netJob; return netJob;
} }
@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray( netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog") QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))), .arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response)); response));
@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction( netJob->addNetAction(
Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response)); Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] { QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver; ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] { QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw)); netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; }); QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });

View File

@ -13,6 +13,8 @@
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
static FlameAPI api; static FlameAPI api;
bool FlameCheckUpdate::abort() bool FlameCheckUpdate::abort()
@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString()); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
auto dl = Net::Download::makeByteArray(url, response); auto dl = Net::ApiDownload::makeByteArray(url, response);
get_project_job->addNetAction(dl); get_project_job->addNetAction(dl);
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() { QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
@ -77,7 +79,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId)); auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
auto dl = Net::Download::makeByteArray(url, response); auto dl = Net::ApiDownload::makeByteArray(url, response);
get_file_info_job->addNetAction(dl); get_file_info_job->addNetAction(dl);
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() { QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {

View File

@ -61,6 +61,7 @@
#include "meta/VersionList.h" #include "meta/VersionList.h"
#include "minecraft/World.h" #include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h" #include "minecraft/mod/tasks/LocalResourceParse.h"
#include "net/ApiDownload.h"
static const FlameAPI api; static const FlameAPI api;
@ -523,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
case Flame::File::Type::Mod: { case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) { if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path; qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path); auto dl = Net::ApiDownload::makeFile(result.url, path);
m_files_job->addNetAction(dl); m_files_job->addNetAction(dl);
} }
break; break;

View File

@ -26,6 +26,7 @@
#include <QMessageBox> #include <QMessageBox>
#include <QtConcurrentRun> #include <QtConcurrentRun>
#include <algorithm> #include <algorithm>
#include <iterator>
#include <memory> #include <memory>
#include "Json.h" #include "Json.h"
#include "MMCZip.h" #include "MMCZip.h"
@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()
bool FlamePackExportTask::abort() bool FlamePackExportTask::abort()
{ {
if (task != nullptr) { if (task) {
task->abort(); task->abort();
task = nullptr;
emitAborted(); emitAborted();
return true; return true;
} }
if (buildZipFuture.isRunning()) {
buildZipFuture.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
// immediately.
return true;
}
return false; return false;
} }
@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()
setStatus(tr("Adding files...")); setStatus(tr("Adding files..."));
setProgress(4, 5); setProgress(4, 5);
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
QuaZip zip(output); zipTask->addExtraFile("manifest.json", generateIndex());
if (!zip.open(QuaZip::mdCreate)) { zipTask->addExtraFile("modlist.html", generateHTML());
QFile::remove(output);
return BuildZipResult(tr("Could not create file"));
}
if (buildZipFuture.isCanceled()) QStringList exclude;
return BuildZipResult(); std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
[this](QString file) { return gameRoot.relativeFilePath(file); });
QuaZipFile indexFile(&zip); zipTask->setExcludeFiles(exclude);
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) {
QFile::remove(output);
return BuildZipResult(tr("Could not create index"));
}
indexFile.write(generateIndex());
QuaZipFile modlist(&zip);
if (!modlist.open(QIODevice::WriteOnly, QuaZipNewInfo("modlist.html"))) {
QFile::remove(output);
return BuildZipResult(tr("Could not create index"));
}
QString content = "";
for (auto mod : resolvedFiles) {
if (mod.isMod) {
content += QString(TEMPLATE)
.replace("{name}", mod.name.toHtmlEscaped())
.replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
.replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
}
}
content = "<ul>" + content + "</ul>";
modlist.write(content.toUtf8());
auto progressStep = std::make_shared<TaskStepProgress>(); auto progressStep = std::make_shared<TaskStepProgress>();
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
size_t progress = 0;
for (const QFileInfo& file : files) {
if (buildZipFuture.isCanceled()) {
QFile::remove(output);
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
return BuildZipResult();
}
progressStep->update(progress, files.length());
stepProgress(*progressStep);
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
if (!resolvedFiles.contains(file.absoluteFilePath()) &&
!JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
QFile::remove(output);
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
}
progress++;
}
zip.close();
if (zip.getZipError() != 0) {
QFile::remove(output);
progressStep->state = TaskStepState::Failed;
stepProgress(*progressStep);
return BuildZipResult(tr("A zip error occurred"));
}
progressStep->state = TaskStepState::Succeeded; progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep); stepProgress(*progressStep);
return BuildZipResult();
}); });
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
buildZipWatcher.setFuture(buildZipFuture);
}
void FlamePackExportTask::finish() connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded);
{ connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
if (buildZipFuture.isCanceled()) connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
emitAborted(); progressStep->state = TaskStepState::Failed;
else { stepProgress(*progressStep);
const BuildZipResult result = buildZipFuture.result(); emitFailed(reason);
if (result.has_value()) });
emitFailed(result.value()); connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
else
emitSucceeded(); connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
} progressStep->update(current, total);
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
stepProgress(*progressStep);
});
task.reset(zipTask);
zipTask->start();
} }
QByteArray FlamePackExportTask::generateIndex() QByteArray FlamePackExportTask::generateIndex()
@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()
return QJsonDocument(obj).toJson(QJsonDocument::Compact); return QJsonDocument(obj).toJson(QJsonDocument::Compact);
} }
QByteArray FlamePackExportTask::generateHTML()
{
QString content = "";
for (auto mod : resolvedFiles) {
if (mod.isMod) {
content += QString(TEMPLATE)
.replace("{name}", mod.name.toHtmlEscaped())
.replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
.replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
}
}
content = "<ul>" + content + "</ul>";
return content.toUtf8();
}

View File

@ -19,8 +19,6 @@
#pragma once #pragma once
#include <QFuture>
#include <QFutureWatcher>
#include "BaseInstance.h" #include "BaseInstance.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {
const QString output; const QString output;
const MMCZip::FilterFunction filter; const MMCZip::FilterFunction filter;
typedef std::optional<QString> BuildZipResult;
struct ResolvedFile { struct ResolvedFile {
int addonId; int addonId;
int version; int version;
@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {
QMap<QString, HashInfo> pendingHashes{}; QMap<QString, HashInfo> pendingHashes{};
QMap<QString, ResolvedFile> resolvedFiles{}; QMap<QString, ResolvedFile> resolvedFiles{};
Task::Ptr task; Task::Ptr task;
QFuture<BuildZipResult> buildZipFuture;
QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles(); void collectFiles();
void collectHashes(); void collectHashes();
void makeApiRequest(); void makeApiRequest();
void getProjectsInfo(); void getProjectsInfo();
void buildZip(); void buildZip();
void finish();
QByteArray generateIndex(); QByteArray generateIndex();
QByteArray generateHTML();
}; };

View File

@ -10,6 +10,8 @@
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
{ {
auto search_url_optional = getSearchURL(args); auto search_url_optional = getSearchURL(args);
@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] { QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -85,7 +87,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] { QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -113,7 +115,7 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
return netJob; return netJob;
} }

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
*/
#include "modplatform/import_ftb/PackHelpers.h"
#include <QIcon>
#include <QString>
#include <QVariant>
#include "FileSystem.h"
#include "Json.h"
namespace FTBImportAPP {
Modpack parseDirectory(QString path)
{
Modpack modpack{ path };
auto instanceFile = QFileInfo(FS::PathCombine(path, "instance.json"));
if (!instanceFile.exists() || !instanceFile.isFile())
return {};
try {
auto doc = Json::requireDocument(instanceFile.absoluteFilePath(), "FTB_APP instance JSON file");
const auto root = doc.object();
modpack.uuid = Json::requireString(root, "uuid", "uuid");
modpack.id = Json::requireInteger(root, "id", "id");
modpack.versionId = Json::requireInteger(root, "versionId", "versionId");
modpack.name = Json::requireString(root, "name", "name");
modpack.version = Json::requireString(root, "version", "version");
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
} catch (const Exception& e) {
qDebug() << "Couldn't load ftb instance json: " << e.cause();
return {};
}
auto versionsFile = QFileInfo(FS::PathCombine(path, "version.json"));
if (!versionsFile.exists() || !versionsFile.isFile())
return {};
try {
auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file");
const auto root = doc.object();
auto targets = Json::requireArray(root, "targets", "targets");
for (auto target : targets) {
auto obj = Json::requireObject(target, "target");
auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version");
if (name == "forge") {
modpack.loaderType = ResourceAPI::Forge;
modpack.version = version;
break;
} else if (name == "fabric") {
modpack.loaderType = ResourceAPI::Fabric;
modpack.version = version;
break;
} else if (name == "quilt") {
modpack.loaderType = ResourceAPI::Quilt;
modpack.version = version;
break;
}
}
} catch (const Exception& e) {
qDebug() << "Couldn't load ftb version json: " << e.cause();
return {};
}
auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg"));
if (iconFile.exists() && iconFile.isFile()) {
modpack.icon = QIcon(iconFile.absoluteFilePath());
}
return modpack;
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QIcon>
#include <QList>
#include <QMetaType>
#include <QString>
#include <QVariant>
#include "modplatform/ResourceAPI.h"
namespace FTBImportAPP {
struct Modpack {
QString path;
// json data
QString uuid;
int id;
int versionId;
QString name;
QString version;
QString mcVersion;
// not needed for instance creation
QVariant jvmArgs;
std::optional<ResourceAPI::ModLoaderType> loaderType;
QString loaderVersion;
QIcon icon;
};
typedef QList<Modpack> ModpackList;
Modpack parseDirectory(QString path);
} // namespace FTBImportAPP
// We need it for the proxy model
Q_DECLARE_METATYPE(FTBImportAPP::Modpack)

View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
*/
#include "PackInstallTask.h"
#include <QtConcurrent>
#include "BaseInstance.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/import_ftb/PackHelpers.h"
#include "settings/INISettingsObject.h"
namespace FTBImportAPP {
void PackInstallTask::executeTask()
{
setStatus(tr("Copying files..."));
setAbortable(false);
progress(1, 2);
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft"));
folderCopy.followSymlinks(true);
return folderCopy();
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &PackInstallTask::copySettings);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &PackInstallTask::emitAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void PackInstallTask::copySettings()
{
setStatus(tr("Copying settings..."));
progress(2, 2);
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
instance.settings()->set("InstanceType", "OneSix");
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
instance.settings()->set("OverrideJavaArgs", true);
instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString());
}
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
auto modloader = m_pack.loaderType;
if (modloader.has_value())
switch (modloader.value()) {
case ResourceAPI::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break;
}
case ResourceAPI::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break;
}
case ResourceAPI::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break;
}
case ResourceAPI::Cauldron:
break;
case ResourceAPI::LiteLoader:
break;
}
components->saveNow();
instance.setName(name());
if (m_instIcon == "default")
m_instIcon = "ftb_logo";
instance.setIconKey(m_instIcon);
instanceSettings->resumeSave();
emitSucceeded();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QFuture>
#include <QFutureWatcher>
#include "InstanceTask.h"
#include "PackHelpers.h"
namespace FTBImportAPP {
class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit PackInstallTask(const Modpack& pack) : m_pack(pack) {}
virtual ~PackInstallTask() = default;
protected:
virtual void executeTask() override;
private slots:
void copySettings();
private:
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
const Modpack m_pack;
};
} // namespace FTBImportAPP

View File

@ -40,6 +40,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Application.h" #include "Application.h"
#include "net/ApiDownload.h"
namespace LegacyFTB { namespace LegacyFTB {
void PackFetchTask::fetch() void PackFetchTask::fetch()
@ -51,7 +53,7 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml"); QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
qDebug() << "Downloading public version info from" << publicPacksUrl.toString(); qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData)); jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml"); QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString(); qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
for (auto& packCode : toFetch) { for (auto& packCode : toFetch) {
auto data = std::make_shared<QByteArray>(); auto data = std::make_shared<QByteArray>();
NetJob* job = new NetJob("Fetching private pack", m_network); NetJob* job = new NetJob("Fetching private pack", m_network);
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data)); job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data));
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] { QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
ModpackList packs; ModpackList packs;

View File

@ -48,6 +48,8 @@
#include "Application.h" #include "Application.h"
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/ApiDownload.h"
namespace LegacyFTB { namespace LegacyFTB {
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version) PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version)
@ -77,7 +79,7 @@ void PackInstallTask::downloadPack()
} else { } else {
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath); url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
} }
netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath)); netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip); connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);

View File

@ -6,6 +6,8 @@
#include "Application.h" #include "Application.h"
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/Upload.h" #include "net/Upload.h"
@ -13,7 +15,7 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::sh
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
netJob->addNetAction(Net::Download::makeByteArray( netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response)); QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
return netJob; return netJob;
@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw)); netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
return netJob; return netJob;
} }
@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray( netJob->addNetAction(Net::ApiUpload::makeByteArray(
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw)); QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
return netJob; return netJob;
@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QJsonDocument body(body_obj); QJsonDocument body(body_obj);
auto body_raw = body.toJson(); auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw)); netJob->addNetAction(
Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
return netJob; return netJob;
} }
@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds); auto searchUrl = getMultipleModInfoURL(addonIds);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
return netJob; return netJob;
} }

View File

@ -12,6 +12,7 @@
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/ApiDownload.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()
} }
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path); auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(dl); m_files_job->addNetAction(dl);
@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()
// MultipleOptionsTask's , once those exist :) // MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef(); auto param = dl.toWeakRef();
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] { connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path); auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
m_files_job->addNetAction(ndl); m_files_job->addNetAction(ndl);
if (auto shared = param.lock()) shared->succeeded(); if (auto shared = param.lock()) shared->succeeded();

View File

@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()
bool ModrinthPackExportTask::abort() bool ModrinthPackExportTask::abort()
{ {
if (task != nullptr) { if (task) {
task->abort(); task->abort();
task = nullptr;
emitAborted(); emitAborted();
return true; return true;
} }
if (buildZipFuture.isRunning()) {
buildZipFuture.cancel();
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
// immediately.
return true;
}
return false; return false;
} }
@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()
{ {
setStatus(tr("Adding files...")); setStatus(tr("Adding files..."));
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() { auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
QuaZip zip(output); zipTask->addExtraFile("modrinth.index.json", generateIndex());
if (!zip.open(QuaZip::mdCreate)) {
QFile::remove(output);
return BuildZipResult(tr("Could not create file"));
}
if (buildZipFuture.isCanceled()) zipTask->setExcludeFiles(resolvedFiles.keys());
return BuildZipResult();
QuaZipFile indexFile(&zip); auto progressStep = std::make_shared<TaskStepProgress>();
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) { connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
QFile::remove(output); progressStep->state = TaskStepState::Succeeded;
return BuildZipResult(tr("Could not create index")); stepProgress(*progressStep);
}
indexFile.write(generateIndex());
size_t progress = 0;
for (const QFileInfo& file : files) {
if (buildZipFuture.isCanceled()) {
QFile::remove(output);
return BuildZipResult();
}
setProgress(progress, files.length());
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
QFile::remove(output);
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
}
progress++;
}
zip.close();
if (zip.getZipError() != 0) {
QFile::remove(output);
return BuildZipResult(tr("A zip error occurred"));
}
return BuildZipResult();
}); });
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
buildZipWatcher.setFuture(buildZipFuture);
}
void ModrinthPackExportTask::finish() connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
{ connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
if (buildZipFuture.isCanceled()) connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
emitAborted(); progressStep->state = TaskStepState::Failed;
else { stepProgress(*progressStep);
const BuildZipResult result = buildZipFuture.result(); emitFailed(reason);
if (result.has_value()) });
emitFailed(result.value()); connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
else
emitSucceeded(); connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
} progressStep->update(current, total);
stepProgress(*progressStep);
});
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
progressStep->status = status;
stepProgress(*progressStep);
});
task.reset(zipTask);
zipTask->start();
} }
QByteArray ModrinthPackExportTask::generateIndex() QByteArray ModrinthPackExportTask::generateIndex()

View File

@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
const QString output; const QString output;
const MMCZip::FilterFunction filter; const MMCZip::FilterFunction filter;
typedef std::optional<QString> BuildZipResult;
ModrinthAPI api; ModrinthAPI api;
QFileInfoList files; QFileInfoList files;
QMap<QString, QString> pendingHashes; QMap<QString, QString> pendingHashes;
QMap<QString, ResolvedFile> resolvedFiles; QMap<QString, ResolvedFile> resolvedFiles;
Task::Ptr task; Task::Ptr task;
QFuture<BuildZipResult> buildZipFuture;
QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles(); void collectFiles();
void collectHashes(); void collectHashes();
void makeApiRequest(); void makeApiRequest();
void parseApiResponse(const std::shared_ptr<QByteArray> response); void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip(); void buildZip();
void finish();
QByteArray generateIndex(); QByteArray generateIndex();
}; };

View File

@ -23,6 +23,8 @@
#include "Application.h" #include "Application.h"
#include "net/ApiDownload.h"
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion) Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
{ {
m_sourceUrl = sourceUrl; m_sourceUrl = sourceUrl;
@ -45,7 +47,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
auto entry = APPLICATION->metacache()->resolveEntry("general", path); auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true); entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network())); m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath(); m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded); connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);

View File

@ -43,6 +43,7 @@
#include "SolderPackManifest.h" #include "SolderPackManifest.h"
#include "TechnicPackProcessor.h" #include "TechnicPackProcessor.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "net/ApiDownload.h"
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl& solderUrl, const QUrl& solderUrl,
@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network)); m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version); auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response)); m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
for (const auto& mod : build.mods) { for (const auto& mod : build.mods) {
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i)); auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
auto dl = Net::Download::makeFile(mod.url, path); auto dl = Net::ApiDownload::makeFile(mod.url, path);
if (!mod.md5.isEmpty()) { if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1()); auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
*
*/
#include "net/ApiDownload.h"
#include "ByteArraySink.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net {
auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("CACHE:") + url.toString());
dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
dl->m_sink.reset(cachedNode);
return dl;
}
auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("BYTES:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output));
return dl;
}
auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("FILE:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new FileSink(path));
return dl;
}
void ApiDownload::init()
{
qDebug() << "Setting up api download";
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
#include "Download.h"
namespace Net {
class ApiDownload : public Download {
public:
virtual ~ApiDownload() = default;
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
void init() override;
};
} // namespace Net

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "Application.h"
#include "BuildConfig.h"
#include "net/HeaderProxy.h"
namespace Net {
class ApiHeaderProxy : public HeaderProxy {
public:
ApiHeaderProxy() : HeaderProxy() {}
virtual ~ApiHeaderProxy() = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
{
QList<HeaderPair> hdrs;
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
hdrs.append({ "Authorization", token.toUtf8() });
}
return hdrs;
};
};
} // namespace Net

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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/>.
*
*/
#include "net/ApiUpload.h"
#include "ByteArraySink.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net {
Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
{
auto up = makeShared<ApiUpload>();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
void ApiUpload::init()
{
qDebug() << "Setting up api upload";
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net

36
launcher/net/ApiUpload.h Normal file
View File

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "ApiHeaderProxy.h"
#include "Upload.h"
namespace Net {
class ApiUpload : public Upload {
public:
virtual ~ApiUpload() = default;
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
void init() override;
};
} // namespace Net

View File

@ -42,8 +42,6 @@ namespace Net {
/* /*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target. * 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 { class ByteArraySink : public Sink {
public: public:

View File

@ -47,17 +47,11 @@
#include "ChecksumValidator.h" #include "ChecksumValidator.h"
#include "MetaCacheSink.h" #include "MetaCacheSink.h"
#include "Application.h"
#include "BuildConfig.h"
#include "net/Logging.h"
#include "net/NetAction.h" #include "net/NetAction.h"
#include "MMCTime.h"
#include "StringUtils.h"
namespace Net { namespace Net {
#if defined(LAUNCHER_APPLICATION)
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{ {
auto dl = makeShared<Download>(); auto dl = makeShared<Download>();
@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
dl->m_sink.reset(cachedNode); dl->m_sink.reset(cachedNode);
return dl; return dl;
} }
#endif
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{ {
@ -90,260 +85,8 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
return dl; return dl;
} }
void Download::addValidator(Validator* v) QNetworkReply* Download::getReply(QNetworkRequest& request)
{ {
m_sink->addValidator(v); return m_network->get(request);
} }
void Download::executeTask()
{
setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
if (getState() == Task::State::AbortedByUser) {
qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
emitAborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emit succeeded();
qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
return;
case State::Running:
qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed();
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
// TODO remove duplication
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
request.setRawHeader("Authorization", token.toUtf8());
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setTransferTimeout();
#endif
m_last_progress_time = m_clock.now();
m_last_progress_bytes = 0;
QNetworkReply* rep = m_network->get(request);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
#else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Download::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
}
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
auto now = m_clock.now();
auto elapsed = now - m_last_progress_time;
// use milliseconds for speed precision
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
QString dl_progress =
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
QString dl_speed_str;
if (elapsed_ms.count() > 0) {
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
//: Download speed, in bytes per second (remaining download time in parenthesis)
dl_speed_str =
tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
} else {
//: Download speed at 0 bytes per second
dl_speed_str = tr("0 B/s");
}
setDetails(dl_progress + "\n" + dl_speed_str);
setProgress(bytesReceived, bytesTotal);
}
void Download::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
m_state = State::Succeeded;
return;
}
}
// error happened during download.
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Download::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
<< error.errorString();
auto cert = error.certificate();
qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
auto Download::handleRedirect() -> bool
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void Download::downloadFinished()
{
// handle HTTP redirection first
if (handleRedirect()) {
qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
{
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
emit succeeded();
}
void Download::downloadReadyRead()
{
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
if (m_state == State::Failed) {
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
}
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
} else {
qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
}
}
} // namespace Net } // namespace Net
auto Net::Download::abort() -> bool
{
if (m_reply) {
m_reply->abort();
} else {
m_state = State::AbortedByUser;
}
return true;
}

View File

@ -38,57 +38,26 @@
#pragma once #pragma once
#include <chrono>
#include "HttpMetaCache.h" #include "HttpMetaCache.h"
#include "NetAction.h"
#include "Sink.h"
#include "Validator.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "net/NetRequest.h"
namespace Net { namespace Net {
class Download : public NetAction { class Download : public NetRequest {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<class Download>; using Ptr = shared_qobject_ptr<class Download>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
Q_DECLARE_FLAGS(Options, Option)
public:
~Download() override = default;
#if defined(LAUNCHER_APPLICATION)
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr; static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
#endif
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr; static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr; static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
public: protected:
void addValidator(Validator* v); virtual QNetworkReply* getReply(QNetworkRequest&) override;
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; };
private:
auto handleRedirect() -> bool;
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
Options m_options;
std::chrono::steady_clock m_clock;
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
qint64 m_last_progress_bytes;
}; };
} // namespace Net } // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 <QDebug>
#include <QNetworkRequest>
namespace Net {
struct HeaderPair {
QByteArray headerName;
QByteArray headerValue;
};
class HeaderProxy {
public:
HeaderProxy(){};
virtual ~HeaderProxy(){};
public:
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const = 0;
public:
void writeHeaders(QNetworkRequest& request)
{
for (auto header : headers(request)) {
request.setRawHeader(header.headerName, header.headerValue);
}
}
};
} // namespace Net

View File

@ -42,6 +42,8 @@
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "HeaderProxy.h"
class NetAction : public Task { class NetAction : public Task {
Q_OBJECT Q_OBJECT
protected: protected:
@ -56,13 +58,17 @@ class NetAction : public Task {
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; } void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
virtual void init() = 0;
protected slots: protected slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0; virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0; virtual void downloadReadyRead() = 0;
virtual void sslErrors(const QList<QSslError>& errors) { virtual void sslErrors(const QList<QSslError>& errors)
{
int i = 1; int i = 1;
for (auto error : errors) { for (auto error : errors) {
qCritical() << "Network SSL Error #" << i << " : " << error.errorString(); qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
@ -70,7 +76,6 @@ class NetAction : public Task {
qCritical() << "Certificate in question:\n" << cert.toText(); qCritical() << "Certificate in question:\n" << cert.toText();
i++; i++;
} }
}; };
public slots: public slots:
@ -91,4 +96,5 @@ class NetAction : public Task {
/// source URL /// source URL
QUrl m_url; QUrl m_url;
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
}; };

331
launcher/net/NetRequest.cpp Normal file
View File

@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "NetRequest.h"
#include <QUrl>
#include <QDateTime>
#include <QFileInfo>
#include <memory>
#if defined(LAUNCHER_APPLICATION)
#include "Application.h"
#endif
#include "net/NetAction.h"
#include "MMCTime.h"
#include "StringUtils.h"
namespace Net {
void NetRequest::addValidator(Validator* v)
{
m_sink->addValidator(v);
}
void NetRequest::executeTask()
{
init();
setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
if (getState() == Task::State::AbortedByUser) {
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
emitAborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
qCDebug(logCat) << getUid().toString() << "Request cache hit " << m_url.toString();
emit succeeded();
emit finished();
return;
case State::Running:
qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed();
return;
case State::AbortedByUser:
emitAborted();
return;
}
#if defined(LAUNCHER_APPLICATION)
auto user_agent = APPLICATION->getUserAgent();
#else
auto user_agent = BuildConfig.USER_AGENT;
#endif
request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8());
for (auto& header_proxy : m_headerProxies) {
header_proxy->writeHeaders(request);
}
// TODO remove duplication
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setTransferTimeout();
#endif
m_last_progress_time = m_clock.now();
m_last_progress_bytes = 0;
QNetworkReply* rep = getReply(request);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
#else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &NetRequest::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
}
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
auto now = m_clock.now();
auto elapsed = now - m_last_progress_time;
// use milliseconds for speed precision
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
QString dl_progress =
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
QString dl_speed_str;
if (elapsed_ms.count() > 0) {
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown");
//: Download speed, in bytes per second (remaining download time in parenthesis)
dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
} else {
//: Download speed at 0 bytes per second
dl_speed_str = tr("0 B/s");
}
setDetails(dl_progress + "\n" + dl_speed_str);
setProgress(bytesReceived, bytesTotal);
}
void NetRequest::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(logCat) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::Failed;
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
m_state = State::Succeeded;
return;
}
}
// error happened during download.
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void NetRequest::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCCritical(logCat) << getUid().toString() << "Request" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
auto NetRequest::handleRedirect() -> bool
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(logCat) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(logCat) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(logCat) << getUid().toString() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void NetRequest::downloadFinished()
{
// handle HTTP redirection first
if (handleRedirect()) {
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
{
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
emit finished();
return;
} else if (m_state == State::Failed) {
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
emit finished();
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
emit finished();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(logCat) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
if (m_state != State::Succeeded) {
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
m_sink->abort();
emit failed("");
emit finished();
return;
}
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
emit finished();
return;
}
m_reply.reset();
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
emit succeeded();
emit finished();
}
void NetRequest::downloadReadyRead()
{
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
if (m_state == State::Failed) {
qCCritical(logCat) << getUid().toString() << "Failed to process response chunk";
}
// qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
} else {
qCCritical(logCat) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
}
}
auto NetRequest::abort() -> bool
{
m_state = State::AbortedByUser;
if (m_reply) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr);
#else
disconnect(m_reply.get(), QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), nullptr, nullptr);
#endif
m_reply->abort();
}
return true;
}
} // namespace Net

99
launcher/net/NetRequest.h Normal file
View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 <qloggingcategory.h>
#include <chrono>
#include "NetAction.h"
#include "Sink.h"
#include "Validator.h"
#include "QObjectPtr.h"
#include "net/Logging.h"
namespace Net {
class NetRequest : public NetAction {
Q_OBJECT
protected:
explicit NetRequest() : NetAction(){};
public:
using Ptr = shared_qobject_ptr<class NetRequest>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
Q_DECLARE_FLAGS(Options, Option)
public:
~NetRequest() override = default;
void init() override{};
public:
void addValidator(Validator* v);
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; }
private:
auto handleRedirect() -> bool;
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
protected:
std::unique_ptr<Sink> m_sink;
Options m_options;
typedef const QLoggingCategory& (*logCatFunc)();
logCatFunc logCat = taskUploadLogC;
std::chrono::steady_clock m_clock;
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
qint64 m_last_progress_bytes;
};
} // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::NetRequest::Options)

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/HeaderProxy.h"
namespace Net {
class RawHeaderProxy : public HeaderProxy {
public:
RawHeaderProxy() : HeaderProxy() {}
virtual ~RawHeaderProxy() = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest&) const override { return m_headers; };
void addHeader(const HeaderPair& header) { m_headers.append(header); }
void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); }
void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); }
private:
QList<HeaderPair> m_headers;
};
} // namespace Net

View File

@ -38,219 +38,16 @@
#include "Upload.h" #include "Upload.h"
#include <memory>
#include <utility> #include <utility>
#include "Application.h"
#include "BuildConfig.h"
#include "ByteArraySink.h" #include "ByteArraySink.h"
#include "net/Logging.h"
namespace Net { namespace Net {
bool Upload::abort() QNetworkReply* Upload::getReply(QNetworkRequest& request)
{ {
if (m_reply) {
m_reply->abort();
} else {
m_state = State::AbortedByUser;
}
return true;
}
void Upload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
setProgress(bytesReceived, bytesTotal);
}
void Upload::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::AbortedByUser;
} else {
// error happened during download.
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void Upload::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (const auto& error : errors) {
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : "
<< error.errorString();
auto cert = error.certificate();
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
bool Upload::handleRedirect()
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void Upload::downloadFinished()
{
// handle HTTP redirection first
// very unlikely for post requests, still can happen
if (handleRedirect()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
return;
} else if (m_state == State::Failed) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
return;
}
m_reply.reset();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
emit succeeded();
}
void Upload::downloadReadyRead()
{
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
}
}
void Upload::executeTask()
{
setStatus(tr("Uploading %1").arg(m_url.toString()));
if (m_state == State::AbortedByUser) {
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
emit aborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
emitSucceeded();
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
return;
case State::Running:
qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed("");
return;
case State::AbortedByUser:
emitAborted();
return;
}
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
// TODO remove duplication
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
request.setRawHeader("Authorization", token.toUtf8());
}
// TODO other types of post requests ?
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* rep = m_network->post(request, m_post_data); return m_network->post(request, m_post_data);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
#else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
} }
Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data) Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)

View File

@ -37,36 +37,21 @@
#pragma once #pragma once
#include "NetAction.h" #include "net/NetRequest.h"
#include "Sink.h"
namespace Net { namespace Net {
class Upload : public NetAction { class Upload : public NetRequest {
Q_OBJECT Q_OBJECT
public: public:
using Ptr = shared_qobject_ptr<Upload>; using Ptr = shared_qobject_ptr<Upload>;
explicit Upload() : NetRequest() { logCat = taskUploadLogC; };
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data); static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; };
protected slots: protected:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; virtual QNetworkReply* getReply(QNetworkRequest&) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
private:
std::unique_ptr<Sink> m_sink;
QByteArray m_post_data; QByteArray m_post_data;
bool handleRedirect();
}; };
} // namespace Net } // namespace Net

View File

@ -57,6 +57,8 @@ public:
return m_id; return m_id;
} }
void init() override {};
protected protected
slots: slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override; void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;

View File

@ -46,6 +46,7 @@ public:
static Ptr make(ScreenShot::Ptr shot) { static Ptr make(ScreenShot::Ptr shot) {
return Ptr(new ImgurUpload(shot)); return Ptr(new ImgurUpload(shot));
} }
void init() override {};
protected protected
slots: slots:

View File

@ -37,11 +37,12 @@
#include "settings/INIFile.h" #include "settings/INIFile.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <QFile>
#include <QTextStream>
#include <QStringList>
#include <QSaveFile>
#include <QDebug> #include <QDebug>
#include <QFile>
#include <QSaveFile>
#include <QStringList>
#include <QTemporaryFile>
#include <QTextStream>
#include <QSettings> #include <QSettings>
@ -71,6 +72,7 @@ bool INIFile::saveFile(QString fileName)
return true; return true;
} }
QString unescape(QString orig) QString unescape(QString orig)
{ {
QString out; QString out;
@ -185,6 +187,19 @@ bool INIFile::loadFile(QString fileName)
return true; return true;
} }
bool INIFile::loadFile(QByteArray data)
{
QTemporaryFile file;
if (!file.open())
return false;
file.write(data);
file.flush();
file.close();
auto loaded = loadFile(file.fileName());
file.remove();
return loaded;
}
QVariant INIFile::get(QString key, QVariant def) const QVariant INIFile::get(QString key, QVariant def) const
{ {
if (!this->contains(key)) if (!this->contains(key))

View File

@ -50,6 +50,7 @@ public:
explicit INIFile(); explicit INIFile();
bool loadFile(QString fileName); bool loadFile(QString fileName);
bool loadFile(QByteArray data);
bool saveFile(QString fileName); bool saveFile(QString fileName);
QVariant get(QString key, QVariant def) const; QVariant get(QString key, QVariant def) const;

View File

@ -1342,16 +1342,10 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
if (m_selectedInstance) { if (m_selectedInstance) {
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
if (instance) { if (instance) {
QString errorMsg; if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
errorMsg = tr("Quilt is currently not supported by CurseForge modpacks.");
} else if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") { cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks."); QMessageBox msgBox(this);
} msgBox.setText("Snapshots are currently not supported by CurseForge modpacks.");
if (!errorMsg.isEmpty()) {
QMessageBox msgBox;
msgBox.setText(errorMsg);
msgBox.exec(); msgBox.exec();
return; return;
} }

View File

@ -170,7 +170,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>"); QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT)); ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
QString copyText("© 2022 %1"); QString copyText("© 2022-2023 %1");
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT)); ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close())); connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));

View File

@ -70,6 +70,8 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget* parent
auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot()); auto prefix = QDir(instance->instanceRoot()).relativeFilePath(instance->gameRoot());
proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") }); proxyModel->ignoreFilesWithPath().insert({ FS::PathCombine(prefix, "logs"), FS::PathCombine(prefix, "crash-reports") });
proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); proxyModel->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
proxyModel->ignoreFilesWithPath().insert(
{ FS::PathCombine(prefix, ".cache"), FS::PathCombine(prefix, ".fabric"), FS::PathCombine(prefix, ".quilt") });
loadPackIgnore(); loadPackIgnore();
ui->treeView->setModel(proxyModel); ui->treeView->setModel(proxyModel);

View File

@ -61,7 +61,7 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
// use the game root - everything outside cannot be exported // use the game root - everything outside cannot be exported
const QDir root(instance->gameRoot()); const QDir root(instance->gameRoot());
proxy = new FileIgnoreProxy(instance->gameRoot(), this); proxy = new FileIgnoreProxy(instance->gameRoot(), this);
proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports" }); proxy->ignoreFilesWithPath().insert({ "logs", "crash-reports", ".cache", ".fabric", ".quilt" });
proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" }); proxy->ignoreFilesWithName().append({ ".DS_Store", "thumbs.db", "Thumbs.db" });
proxy->setSourceModel(model); proxy->setSourceModel(model);

View File

@ -33,38 +33,37 @@
* limitations under the License. * limitations under the License.
*/ */
#include "Application.h"
#include "NewInstanceDialog.h" #include "NewInstanceDialog.h"
#include "Application.h"
#include "ui/pages/modplatform/import_ftb/ImportFTBPage.h"
#include "ui_NewInstanceDialog.h" #include "ui_NewInstanceDialog.h"
#include <BaseVersion.h> #include <BaseVersion.h>
#include <InstanceList.h>
#include <icons/IconList.h> #include <icons/IconList.h>
#include <tasks/Task.h> #include <tasks/Task.h>
#include <InstanceList.h>
#include "VersionSelectDialog.h"
#include "ProgressDialog.h"
#include "IconPickerDialog.h" #include "IconPickerDialog.h"
#include "ProgressDialog.h"
#include "VersionSelectDialog.h"
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLayout> #include <QLayout>
#include <QPushButton> #include <QPushButton>
#include <QFileDialog>
#include <QValidator> #include <QValidator>
#include <QDialogButtonBox>
#include <utility> #include <utility>
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/CustomPage.h" #include "ui/pages/modplatform/CustomPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#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/ImportPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h"
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog) : QDialog(parent), ui(new Ui::NewInstanceDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -168,6 +167,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame) if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(new FlamePage(this)); pages.append(new FlamePage(this));
pages.append(new LegacyFTB::Page(this)); pages.append(new LegacyFTB::Page(this));
pages.append(new FTBImportAPP::ImportFTBPage(this));
pages.append(new ModrinthPage(this)); pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this)); pages.append(new TechnicPage(this));

View File

@ -23,6 +23,8 @@
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h" #include "ui/dialogs/ProgressDialog.h"
#include "net/ApiDownload.h"
/** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen. /** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen.
* ... thanks Qt. * ... thanks Qt.
*/ */
@ -226,7 +228,7 @@ void ModrinthManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID(); QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction( m_fetch_job->addNetAction(
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};
@ -376,7 +378,7 @@ void FlameManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID(); QString id = m_inst->getManagedPackID();
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response)); m_fetch_job->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] { QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -17,7 +17,7 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/Download.h" #include "net/ApiDownload.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
@ -281,7 +281,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
auto cache_entry = APPLICATION->metacache()->resolveEntry( auto cache_entry = APPLICATION->metacache()->resolveEntry(
metaEntryBase(), metaEntryBase(),
QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
auto icon_fetch_action = Net::Download::makeCached(url, cache_entry); auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
auto full_file_path = cache_entry->getFullPath(); auto full_file_path = cache_entry->getFullPath();
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] { connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {

View File

@ -20,6 +20,8 @@
#include <BuildConfig.h> #include <BuildConfig.h>
#include <Json.h> #include <Json.h>
#include "net/ApiDownload.h"
namespace Atl { namespace Atl {
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@ -75,7 +77,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network()); auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
@ -169,7 +171,7 @@ void ListModel::requestLogo(QString file, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network()); auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {

View File

@ -43,6 +43,8 @@
#include "modplatform/atlauncher/ATLShareCode.h" #include "modplatform/atlauncher/ATLShareCode.h"
#include "Application.h" #include "Application.h"
#include "net/ApiDownload.h"
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_version(version) , m_version(version)
@ -152,7 +154,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
void AtlOptionalModListModel::useShareCode(const QString& code) { void AtlOptionalModListModel::useShareCode(const QString& code) {
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network())); m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code); auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response)); m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response));
connect(m_jobPtr.get(), &NetJob::succeeded, connect(m_jobPtr.get(), &NetJob::succeeded,
this, &AtlOptionalModListModel::shareCodeSuccess); this, &AtlOptionalModListModel::shareCodeSuccess);

View File

@ -3,6 +3,8 @@
#include "Application.h" #include "Application.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
#include <Version.h> #include <Version.h>
#include <QtMath> #include <QtMath>
@ -106,7 +108,7 @@ void ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
@ -173,7 +175,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort + 1); .arg(currentSort + 1);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);

View File

@ -46,6 +46,8 @@
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
static FlameAPI api; static FlameAPI api;
FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog) FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
@ -132,7 +134,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network()); auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
int addonId = current.addonId; int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] { QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
if (addonId != current.addonId) { if (addonId != current.addonId) {

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
*/
#include "ImportFTBPage.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
#include "FileSystem.h"
#include "ListModel.h"
#include "modplatform/import_ftb/PackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
namespace FTBImportAPP {
ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog(dialog), ui(new Ui::ImportFTBPage)
{
ui->setupUi(this);
{
listModel = new ListModel(this);
ui->modpackList->setModel(listModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
ui->modpackList->selectionModel()->reset();
}
ImportFTBPage::~ImportFTBPage()
{
delete ui;
}
void ImportFTBPage::openedImpl()
{
if (!initialized) {
listModel->update();
initialized = true;
}
suggestCurrent();
}
void ImportFTBPage::retranslate()
{
ui->retranslateUi(this);
}
void ImportFTBPage::suggestCurrent()
{
if (!isOpened)
return;
if (selected.path.isEmpty()) {
dialog->setSuggestedPack();
return;
}
dialog->setSuggestedPack(selected.name, new PackInstallTask(selected));
QString editedLogoName = QString("ftb_%1").arg(selected.id);
dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName);
}
void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
{
if (!now.isValid()) {
onPackSelectionChanged();
return;
}
Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
{
if (pack) {
selected = *pack;
suggestCurrent();
return;
}
if (isOpened)
dialog->setSuggestedPack();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QDialog>
#include <QTextBrowser>
#include <QTreeView>
#include <QWidget>
#include <Application.h>
#include "modplatform/import_ftb/PackHelpers.h"
#include "ui/pages/BasePage.h"
#include "ui/pages/modplatform/import_ftb/ListModel.h"
class NewInstanceDialog;
namespace FTBImportAPP {
namespace Ui {
class ImportFTBPage;
}
class ImportFTBPage : public QWidget, public BasePage {
Q_OBJECT
public:
explicit ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent = 0);
virtual ~ImportFTBPage();
QString displayName() const override { return tr("FTB App Import"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); }
QString id() const override { return "import_ftb"; }
QString helpPage() const override { return "FTB-platform"; }
bool shouldDisplay() const override { return true; }
void openedImpl() override;
void retranslate() override;
private:
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
};
} // namespace FTBImportAPP

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FTBImportAPP::ImportFTBPage</class>
<widget class="QWidget" name="FTBImportAPP::ImportFTBPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1461</width>
<height>1011</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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/>.
*/
#include "ListModel.h"
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
QString getPath()
{
QString partialPath;
#if defined(Q_OS_OSX)
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
#elif defined(Q_OS_WIN32)
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
#else
partialPath = QDir::homePath();
#endif
return FS::PathCombine(partialPath, ".ftba");
}
const QString ListModel::FTB_APP_PATH = getPath();
void ListModel::update()
{
beginResetModel();
modpacks.clear();
QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances");
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
QDirIterator::FollowSymlinks);
while (directoryIterator.hasNext()) {
auto modpack = parseDirectory(directoryIterator.next());
if (!modpack.path.isEmpty())
modpacks.append(modpack);
}
} else {
qDebug() << "Couldn't find ftb instances folder: " << instancesPath;
}
endResetModel();
}
QVariant ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QVariant();
}
auto pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
return pack.icon;
} else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
} else if (role == Qt::ToolTipRole) {
return tr("Minecraft %1").arg(pack.mcVersion);
}
return QVariant();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@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 <QAbstractListModel>
#include <QIcon>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
class ListModel : public QAbstractListModel {
Q_OBJECT
public:
ListModel(QObject* parent) : QAbstractListModel(parent) {}
virtual ~ListModel() = default;
int rowCount(const QModelIndex& parent) const { return modpacks.size(); }
int columnCount(const QModelIndex& parent) const { return 1; }
QVariant data(const QModelIndex& index, int role) const;
void update();
static const QString FTB_APP_PATH;
private:
ModpackList modpacks;
};
} // namespace FTBImportAPP

View File

@ -37,6 +37,7 @@
#include "Application.h" #include "Application.h"
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/ApiDownload.h"
#include <Version.h> #include <Version.h>
#include "StringUtils.h" #include "StringUtils.h"
@ -231,7 +232,7 @@ void ListModel::requestLogo(QString file)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));
NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network()); NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] { QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {

View File

@ -42,6 +42,8 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
#include <QMessageBox> #include <QMessageBox>
namespace Modrinth { namespace Modrinth {
@ -142,7 +144,7 @@ void ModpackListModel::performPaginatedSearch()
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort); .arg(currentSort);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] { QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{}; QJsonParseError parse_error_all{};
@ -232,7 +234,7 @@ void ModpackListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); 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)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {

View File

@ -46,6 +46,8 @@
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
#include <QComboBox> #include <QComboBox>
#include <QKeyEvent> #include <QKeyEvent>
#include <QPushButton> #include <QPushButton>
@ -127,7 +129,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
QString id = current.id; QString id = current.id;
netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) { if (id != current.id) {
@ -176,7 +178,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, QModelIndex prev)
QString id = current.id; QString id = current.id;
netJob->addNetAction( netJob->addNetAction(
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] { QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
if (id != current.id) { if (id != current.id) {

View File

@ -38,6 +38,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h"
#include <QIcon> #include <QIcon>
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@ -116,7 +118,7 @@ void Technic::ListModel::performSearch()
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List; searchMode = List;
} }
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@ -254,7 +256,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -49,6 +49,8 @@
#include "Application.h" #include "Application.h"
#include "modplatform/technic/SolderPackManifest.h" #include "modplatform/technic/SolderPackManifest.h"
#include "net/ApiDownload.h"
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
{ {
@ -140,7 +142,7 @@ void TechnicPage::suggestCurrent()
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug; QString slug = current.slug;
netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
{ {
jobPtr.reset(); jobPtr.reset();
@ -246,7 +248,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug); auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);

View File

@ -26,6 +26,7 @@
#include "Application.h" #include "Application.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/ApiDownload.h"
enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 }; enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 };
@ -97,7 +98,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network()); auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(source, entry)); job->addNetAction(Net::ApiDownload::makeCached(source, entry));
auto full_entry_path = entry->getFullPath(); auto full_entry_path = entry->getFullPath();
auto source_url = source; auto source_url = source;

View File

@ -61,7 +61,7 @@ The `standard` and `legacy` launchers are available.
Example (some parts have been censored): Example (some parts have been censored):
``` ```text
mod legacyjavafixer-1.0 mod legacyjavafixer-1.0
mainClass net.minecraft.launchwrapper.Launch mainClass net.minecraft.launchwrapper.Launch
param --username param --username

View File

@ -53,7 +53,8 @@ home.packages = [ pkgs.prismlauncher ];
### Without flakes-enabled nix ### Without flakes-enabled nix
#### Using channels <details>
<summary>Using channels</summary>
```sh ```sh
nix-channel --add https://github.com/PrismLauncher/PrismLauncher/archive/master.tar.gz prismlauncher nix-channel --add https://github.com/PrismLauncher/PrismLauncher/archive/master.tar.gz prismlauncher
@ -61,7 +62,10 @@ nix-channel --update prismlauncher
nix-env -iA prismlauncher nix-env -iA prismlauncher
``` ```
#### Using the overlay </details>
<details>
<summary>Using the overlay</summary>
```nix ```nix
# In your configuration.nix: # In your configuration.nix:
@ -74,6 +78,8 @@ nix-env -iA prismlauncher
} }
``` ```
</details>
## Running ad-hoc ## Running ad-hoc
If you're on a flakes-enabled nix you can run the launcher in one-line If you're on a flakes-enabled nix you can run the launcher in one-line

View File

@ -24,9 +24,9 @@
# Supported systems. # Supported systems.
systems = [ systems = [
"x86_64-linux" "x86_64-linux"
"x86_64-darwin"
"aarch64-linux" "aarch64-linux"
# Disabled due to qtbase being currently broken for "aarch64-darwin." # Disabled due to our packages not supporting darwin yet.
# "x86_64-darwin"
# "aarch64-darwin" # "aarch64-darwin"
]; ];
} }