aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml62
-rw-r--r--.github/workflows/trigger_release.yml9
-rw-r--r--CMakeLists.txt18
-rw-r--r--flatpak/org.prismlauncher.PrismLauncher.yml2
-rw-r--r--launcher/Application.cpp55
-rw-r--r--launcher/CMakeLists.txt52
-rw-r--r--launcher/DesktopServices.cpp24
-rw-r--r--launcher/DesktopServices.h13
-rw-r--r--launcher/InstanceImportTask.cpp4
-rw-r--r--launcher/ResourceDownloadTask.cpp4
-rw-r--r--launcher/WindowsConsole.cpp132
-rw-r--r--launcher/WindowsConsole.h25
-rw-r--r--launcher/filelink/FileLink.cpp25
-rw-r--r--launcher/icons/IconList.cpp241
-rw-r--r--launcher/icons/IconList.h105
-rw-r--r--launcher/icons/IconUtils.cpp90
-rw-r--r--launcher/icons/IconUtils.h40
-rw-r--r--launcher/icons/MMCIcon.cpp50
-rw-r--r--launcher/icons/MMCIcon.h65
-rw-r--r--launcher/meta/BaseEntity.cpp4
-rw-r--r--launcher/minecraft/AssetsUtils.cpp4
-rw-r--r--launcher/minecraft/Library.cpp6
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp41
-rw-r--r--launcher/minecraft/MinecraftInstance.h2
-rw-r--r--launcher/minecraft/launch/DirectJavaLaunch.cpp166
-rw-r--r--launcher/minecraft/launch/DirectJavaLaunch.h58
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp1
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h1
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/TexturePackFolderModel.cpp1
-rw-r--r--launcher/minecraft/update/AssetUpdateTask.cpp4
-rw-r--r--launcher/minecraft/update/FMLLibrariesTask.cpp4
-rw-r--r--launcher/modplatform/atlauncher/ATLPackInstallTask.cpp14
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp8
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp14
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp6
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp3
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp132
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.h7
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp8
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.cpp87
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.h55
-rw-r--r--launcher/modplatform/import_ftb/PackInstallTask.cpp99
-rw-r--r--launcher/modplatform/import_ftb/PackInstallTask.h49
-rw-r--r--launcher/modplatform/legacy_ftb/PackFetchTask.cpp6
-rw-r--r--launcher/modplatform/legacy_ftb/PackInstallTask.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.cpp13
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp5
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.cpp88
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.h5
-rw-r--r--launcher/modplatform/technic/SingleZipPackInstallTask.cpp4
-rw-r--r--launcher/modplatform/technic/SolderPackInstallTask.cpp5
-rw-r--r--launcher/net/ApiDownload.cpp66
-rw-r--r--launcher/net/ApiDownload.h38
-rw-r--r--launcher/net/ApiHeaderProxy.h49
-rw-r--r--launcher/net/ApiUpload.cpp43
-rw-r--r--launcher/net/ApiUpload.h36
-rw-r--r--launcher/net/ByteArraySink.h2
-rw-r--r--launcher/net/Download.cpp267
-rw-r--r--launcher/net/Download.h47
-rw-r--r--launcher/net/HeaderProxy.h49
-rw-r--r--launcher/net/NetAction.h10
-rw-r--r--launcher/net/NetRequest.cpp331
-rw-r--r--launcher/net/NetRequest.h99
-rw-r--r--launcher/net/RawHeaderProxy.h44
-rw-r--r--launcher/net/Upload.cpp209
-rw-r--r--launcher/net/Upload.h25
-rw-r--r--launcher/screenshots/ImgurAlbumCreation.h2
-rw-r--r--launcher/screenshots/ImgurUpload.h1
-rw-r--r--launcher/ui/MainWindow.cpp14
-rw-r--r--launcher/ui/dialogs/AboutDialog.cpp2
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp35
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.ui2
-rw-r--r--launcher/ui/pages/global/ExternalToolsPage.ui8
-rw-r--r--launcher/ui/pages/global/JavaPage.ui150
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui8
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui62
-rw-r--r--launcher/ui/pages/instance/ManagedPackPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp11
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp18
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp15
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp104
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h67
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui28
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.cpp88
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.h46
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp13
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp10
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp17
-rw-r--r--launcher/ui/widgets/PageContainer_p.h2
-rw-r--r--launcher/ui/widgets/VariableSizedImageObject.cpp3
-rw-r--r--launcher/ui/widgets/WideBar.cpp4
97 files changed, 2405 insertions, 1571 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c710d54b..fc223a07 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -24,6 +24,12 @@ on:
CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache
required: false
+ GPG_PRIVATE_KEY:
+ description: Private key for AppImage signing
+ required: false
+ GPG_PRIVATE_KEY_ID:
+ description: ID for the GPG_PRIVATE_KEY, to select the signing key
+ required: false
jobs:
build:
@@ -68,7 +74,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: ''
- qt_version: '6.5.1'
+ qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -80,7 +86,7 @@ jobs:
qt_ver: 6
qt_host: windows
qt_arch: 'win64_msvc2019_arm64'
- qt_version: '6.5.1'
+ qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -90,7 +96,7 @@ jobs:
qt_ver: 6
qt_host: mac
qt_arch: ''
- qt_version: '6.5.0'
+ qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats'
qt_tools: ''
@@ -249,6 +255,8 @@ jobs:
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
+ wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
+
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
sudo apt install libopengl0
@@ -387,8 +395,8 @@ jobs:
cd ${{ env.INSTALL_DIR }}
if ("${{ matrix.qt_ver }}" -eq "5")
{
- Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
- Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
+ Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
+ Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
}
cd ${{ github.workspace }}
@@ -425,7 +433,7 @@ jobs:
run: |
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
-
+
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
- name: Package (Windows, installer)
@@ -466,11 +474,15 @@ jobs:
- name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash
+ env:
+ GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
+
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
- export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
+
+ export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
chmod +x linuxdeploy-*.AppImage
@@ -481,8 +493,8 @@ jobs:
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
- cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
-
+ cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
+
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
@@ -494,8 +506,33 @@ jobs:
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH
+ chmod +x AppImageUpdate-x86_64.AppImage
+ ./AppImageUpdate-x86_64.AppImage --appimage-extract
+
+ mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
+ mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
+
+ cp -r squashfs-root/usr/bin/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
+ cp -r squashfs-root/usr/lib/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
+ cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
+ cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
+
+ export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
+
+ if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
+ export SIGN=1
+ export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
+ mkdir -p ~/.gnupg/
+ printf "$GPG_PRIVATE_KEY" | base64 --decode > ~/.gnupg/private.key
+ gpg --import ~/.gnupg/private.key
+ else
+ echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
+ fi
+
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
+ mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
+
##
# UPLOAD BUILDS
##
@@ -562,6 +599,13 @@ jobs:
with:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
+
+ - name: Upload AppImage Zsync (Linux)
+ if: runner.os == 'Linux' && matrix.qt_ver != 5
+ uses: actions/upload-artifact@v3
+ with:
+ name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
+ path: PrismLauncher-Linux-x86_64.AppImage.zsync
- name: ccache stats (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != ''
diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml
index f19b8398..2a46ff5e 100644
--- a/.github/workflows/trigger_release.yml
+++ b/.github/workflows/trigger_release.yml
@@ -43,7 +43,8 @@ jobs:
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
- mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
+ mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
+ mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
@@ -78,9 +79,8 @@ jobs:
- name: Create release
id: create_release
uses: softprops/action-gh-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
+ token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }}
name: Prism Launcher ${{ env.VERSION }}
draft: true
@@ -88,7 +88,8 @@ jobs:
files: |
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
- PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
+ PrismLauncher-Linux-x86_64.AppImage
+ PrismLauncher-Linux-x86_64.AppImage.zsync
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e01a0a2f..98e97950 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -327,6 +327,8 @@ add_subdirectory(program_info)
####################################### Install layout #######################################
+set(Launcher_ENABLE_UPDATER NO)
+
if(NOT (UNIX AND APPLE))
# Install "portable.txt" if selected component is "portable"
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
@@ -351,9 +353,9 @@ if(UNIX AND APPLE)
set(MACOSX_BUNDLE_SHORT_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_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
- set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
- set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
+ set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
+ 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" 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_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
@@ -362,8 +364,12 @@ if(UNIX AND APPLE)
# directories to look for dependencies
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
- set(INSTALL_BUNDLE "full")
+ set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
# Add the icon
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
@@ -376,7 +382,7 @@ elseif(UNIX)
set(JARS_DEST_DIR "share/${Launcher_Name}")
# 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(Launcher_BINARY_RPATH "$ORIGIN/")
@@ -410,7 +416,7 @@ elseif(WIN32)
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
- set(INSTALL_BUNDLE "full")
+ set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
else()
message(FATAL_ERROR "Platform not supported")
endif()
diff --git a/flatpak/org.prismlauncher.PrismLauncher.yml b/flatpak/org.prismlauncher.PrismLauncher.yml
index a9b9762f..46b6da36 100644
--- a/flatpak/org.prismlauncher.PrismLauncher.yml
+++ b/flatpak/org.prismlauncher.PrismLauncher.yml
@@ -18,6 +18,8 @@ finish-args:
- --filesystem=xdg-run/app/com.discordapp.Discord:create
# Mod drag&drop
- --filesystem=xdg-download:ro
+ # FTBApp import
+ - --filesystem=~/.ftba:ro
cleanup:
- /lib/libGLU*
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index aeea90f1..e89b7659 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -131,17 +131,12 @@
#include "MangoHud.h"
#endif
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h"
#endif
-
#if defined Q_OS_WIN32
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <windows.h>
-#include <stdio.h>
+#include "WindowsConsole.h"
#endif
#define STRINGIFY(x) #x
@@ -168,31 +163,15 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr);
}
-}
+} // namespace
+
+
-Application::Application(int &argc, char **argv) : QApplication(argc, argv)
+Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
- // attach the parent console
- if(AttachConsole(ATTACH_PARENT_PROCESS))
- {
- // if attach succeeds, reopen and sync all the i/o
- if(freopen("CON", "w", stdout))
- {
- std::cout.sync_with_stdio();
- }
- if(freopen("CON", "w", stderr))
- {
- std::cerr.sync_with_stdio();
- }
- if(freopen("CON", "r", stdin))
- {
- std::cin.sync_with_stdio();
- }
- auto out = GetStdHandle (STD_OUTPUT_HANDLE);
- DWORD written;
- const char * endline = "\n";
- WriteConsole(out, endline, strlen(endline), &written, NULL);
+ // attach the parent console if stdout not already captured
+ if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@@ -281,7 +260,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
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();
adjustedBy = "Persistent data path";
@@ -628,9 +616,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
- // Minecraft launch method
- m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
-
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
@@ -704,7 +689,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
// 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");
}
@@ -776,7 +761,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
if(BuildConfig.UPDATER_ENABLED)
{
qDebug() << "Initializing updater";
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater());
#endif
qDebug() << "<> Updater started.";
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 87e7b29a..9b73be1a 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h
net/Upload.cpp
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
@@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h
- minecraft/launch/DirectJavaLaunch.cpp
- minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
@@ -501,6 +508,11 @@ set(FTB_SOURCES
modplatform/legacy_ftb/PrivatePackManager.cpp
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
@@ -563,6 +575,9 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
+ WindowsConsole.cpp
+ WindowsConsole.h
+
filelink/FileLink.h
filelink/FileLink.cpp
FileSystem.h
@@ -668,7 +683,7 @@ set(LOGIC_SOURCES
${ATLAUNCHER_SOURCES}
)
-if(APPLE)
+if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
endif()
@@ -872,6 +887,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/legacy_ftb/ListModel.h
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.h
ui/pages/modplatform/flame/FlamePage.cpp
@@ -1018,6 +1038,14 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
+if(WIN32)
+ set(LAUNCHER_SOURCES
+ WindowsConsole.cpp
+ WindowsConsole.h
+ ${LAUNCHER_SOURCES}
+ )
+endif()
+
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
@@ -1046,6 +1074,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
+ ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
@@ -1108,6 +1137,7 @@ set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
+target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic
systeminfo
@@ -1147,17 +1177,23 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
- 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)
+ if(Launcher_ENABLE_UPDATER)
+ 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)
+
+ find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
+ add_compile_definitions(SPARKLE_ENABLED)
+ endif()
- find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
target_link_libraries(Launcher_logic
"-framework AppKit"
"-framework Carbon"
"-framework Foundation"
"-framework ApplicationServices"
)
- target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
+ if(Launcher_ENABLE_UPDATER)
+ target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
+ endif()
endif()
target_link_libraries(Launcher_logic)
@@ -1224,7 +1260,7 @@ if(WIN32)
)
endif()
-if (UNIX AND APPLE)
+if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
# Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
# the framework aren't installed
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index c2002126..0a0934fb 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -118,7 +118,7 @@ bool openDirectory(const QString &path, [[maybe_unused]] bool ensureExists)
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
- if(!isFlatpak())
+ if(!isSandbox())
{
return IndirectOpen(f);
}
@@ -139,7 +139,7 @@ bool openFile(const QString &path)
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
- if(!isFlatpak())
+ if(!isSandbox())
{
return IndirectOpen(f);
}
@@ -157,7 +157,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
qDebug() << "Opening file" << path << "using" << application;
#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
- if(!isFlatpak())
+ if(!isSandbox())
{
return IndirectOpen([&]()
{
@@ -177,7 +177,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
{
qDebug() << "Running" << application << "with args" << args.join(' ');
#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
return IndirectOpen([&]()
@@ -202,7 +202,7 @@ bool openUrl(const QUrl &url)
return QDesktopServices::openUrl(url);
};
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
- if(!isFlatpak())
+ if(!isSandbox())
{
return IndirectOpen(f);
}
@@ -224,4 +224,18 @@ bool isFlatpak()
#endif
}
+bool isSnap()
+{
+#ifdef Q_OS_LINUX
+ return getenv("SNAP");
+#else
+ return false;
+#endif
+}
+
+bool isSandbox()
+{
+ return isSnap() || isFlatpak();
+}
+
}
diff --git a/launcher/DesktopServices.h b/launcher/DesktopServices.h
index 21c9cae0..b1948cc2 100644
--- a/launcher/DesktopServices.h
+++ b/launcher/DesktopServices.h
@@ -34,5 +34,18 @@ namespace DesktopServices
*/
bool openUrl(const QUrl &url);
+ /**
+ * Determine whether the launcher is running in a Flatpak environment
+ */
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();
}
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index d6a96deb..98ed14b9 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -51,6 +51,8 @@
#include "settings/INISettingsObject.h"
+#include "net/ApiDownload.h"
+
#include <QtConcurrentRun>
#include <algorithm>
@@ -95,7 +97,7 @@ void InstanceImportTask::executeTask()
m_archivePath = entry->getFullPath();
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::progress, this, &InstanceImportTask::downloadProgressChanged);
diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp
index 8bb9b64e..a02151ca 100644
--- a/launcher/ResourceDownloadTask.cpp
+++ b/launcher/ResourceDownloadTask.cpp
@@ -24,6 +24,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
+#include "net/ApiDownload.h"
+
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
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::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
diff --git a/launcher/WindowsConsole.cpp b/launcher/WindowsConsole.cpp
new file mode 100644
index 00000000..860af01f
--- /dev/null
+++ b/launcher/WindowsConsole.cpp
@@ -0,0 +1,132 @@
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 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/>.
+ *
+ */
+
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <fcntl.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+#include <iostream>
+
+void RedirectHandle(DWORD handle, FILE* stream, const char* mode ) {
+
+ HANDLE stdHandle = GetStdHandle(handle);
+ if (stdHandle != INVALID_HANDLE_VALUE) {
+ int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
+ if (fileDescriptor != -1) {
+ FILE* file = _fdopen(fileDescriptor, mode);
+ if (file != NULL) {
+ int dup2Result = _dup2(_fileno(file), _fileno(stream));
+ if (dup2Result == 0) {
+ setvbuf(stream, NULL, _IONBF, 0);
+ }
+ }
+ }
+ }
+
+}
+
+// taken from https://stackoverflow.com/a/25927081
+// getting a proper output to console with redirection support on windows is apparently hell
+void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
+{
+ // Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
+ // observed that the file number of our standard handle file objects can be assigned internally to a value of -2
+ // when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
+ // call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
+ // before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
+ // use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
+ // using the "_dup2" function.
+ if (bindStdIn) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "r", stdin);
+ }
+ if (bindStdOut) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "w", stdout);
+ }
+ if (bindStdErr) {
+ FILE* dummyFile;
+ freopen_s(&dummyFile, "nul", "w", stderr);
+ }
+
+ // Redirect unbuffered stdin from the current standard input handle
+ if (bindStdIn) {
+ RedirectHandle(STD_INPUT_HANDLE, stdin, "r");
+ }
+
+ // Redirect unbuffered stdout to the current standard output handle
+ if (bindStdOut) {
+ RedirectHandle(STD_OUTPUT_HANDLE, stdout, "w");
+ }
+
+ // Redirect unbuffered stderr to the current standard error handle
+ if (bindStdErr) {
+ RedirectHandle(STD_ERROR_HANDLE, stderr, "w");
+ }
+
+ // Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
+ // standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
+ // versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
+ // has been read from or written to the targets or not.
+ if (bindStdIn) {
+ std::wcin.clear();
+ std::cin.clear();
+ }
+ if (bindStdOut) {
+ std::wcout.clear();
+ std::cout.clear();
+ }
+ if (bindStdErr) {
+ std::wcerr.clear();
+ std::cerr.clear();
+ }
+}
+
+
+bool AttachWindowsConsole() {
+ auto stdinType = GetFileType(GetStdHandle(STD_INPUT_HANDLE));
+ auto stdoutType = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE));
+ auto stderrType = GetFileType(GetStdHandle(STD_ERROR_HANDLE));
+
+ bool bindStdIn = false;
+ bool bindStdOut = false;
+ bool bindStdErr = false;
+
+ if (stdinType == FILE_TYPE_CHAR || stdinType == FILE_TYPE_UNKNOWN) {
+ bindStdIn = true;
+ }
+ if (stdoutType == FILE_TYPE_CHAR || stdoutType == FILE_TYPE_UNKNOWN) {
+ bindStdOut = true;
+ }
+ if (stderrType == FILE_TYPE_CHAR || stderrType == FILE_TYPE_UNKNOWN) {
+ bindStdErr = true;
+ }
+
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ BindCrtHandlesToStdHandles(bindStdIn, bindStdOut, bindStdErr);
+ return true;
+ }
+
+ return false;
+}
+
+
diff --git a/launcher/WindowsConsole.h b/launcher/WindowsConsole.h
new file mode 100644
index 00000000..ab53864b
--- /dev/null
+++ b/launcher/WindowsConsole.h
@@ -0,0 +1,25 @@
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 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
+
+void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
+bool AttachWindowsConsole();
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 2650bb03..3774b382 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -37,11 +37,7 @@
#include <sys.h>
#if defined Q_OS_WIN32
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-#include <stdio.h>
-#include <windows.h>
+#include "WindowsConsole.h"
#endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
@@ -67,21 +63,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
{
#if defined Q_OS_WIN32
// attach the parent console
- if (AttachConsole(ATTACH_PARENT_PROCESS)) {
- // if attach succeeds, reopen and sync all the i/o
- if (freopen("CON", "w", stdout)) {
- std::cout.sync_with_stdio();
- }
- if (freopen("CON", "w", stderr)) {
- std::cerr.sync_with_stdio();
- }
- if (freopen("CON", "r", stdin)) {
- std::cin.sync_with_stdio();
- }
- auto out = GetStdHandle(STD_OUTPUT_HANDLE);
- DWORD written;
- const char* endline = "\n";
- WriteConsole(out, endline, strlen(endline), &written, NULL);
+ if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@@ -188,7 +170,7 @@ void FileLinkApp::runLink()
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
- FS::LinkResult result = { src_path, dst_path };
+ FS::LinkResult result = { src_path, dst_path, "", 0};
m_path_results.append(result);
}
}
@@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
fclose(stdout);
fclose(stdin);
fclose(stderr);
- FreeConsole();
}
#endif
}
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 13174f6e..5576b974 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * 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
@@ -35,32 +36,30 @@
#include "IconList.h"
#include <FileSystem.h>
-#include <QMap>
+#include <QDebug>
#include <QEventLoop>
-#include <QMimeData>
-#include <QUrl>
#include <QFileSystemWatcher>
+#include <QMap>
+#include <QMimeData>
#include <QSet>
-#include <QDebug>
+#include <QUrl>
+#include "icons/IconUtils.h"
#define MAX_SIZE 1024
-IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
+IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
{
QSet<QString> builtinNames;
// add builtin icons
- for(auto & builtinPath: builtinPaths)
- {
+ for (auto& builtinPath : builtinPaths) {
QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
- for (auto file_info : file_info_list)
- {
+ for (auto file_info : file_info_list) {
builtinNames.insert(file_info.completeBaseName());
}
}
- for(auto & builtinName : builtinNames)
- {
+ for (auto& builtinName : builtinNames) {
addThemeIcon(builtinName);
}
@@ -78,31 +77,27 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
void IconList::sortIconList()
{
qDebug() << "Sorting icon list...";
- std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
- return a.m_key.localeAwareCompare(b.m_key) < 0;
- });
+ std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
reindex();
}
-void IconList::directoryChanged(const QString &path)
+void IconList::directoryChanged(const QString& path)
{
- QDir new_dir (path);
- if(m_dir.absolutePath() != new_dir.absolutePath())
- {
+ QDir new_dir(path);
+ if (m_dir.absolutePath() != new_dir.absolutePath()) {
m_dir.setPath(path);
m_dir.refresh();
- if(is_watching)
+ if (is_watching)
stopWatching();
startWatching();
}
- if(!m_dir.exists())
- if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
+ if (!m_dir.exists())
+ if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
- for (auto it = new_list.begin(); it != new_list.end(); it++)
- {
- QString &foo = (*it);
+ for (auto it = new_list.begin(); it != new_list.end(); it++) {
+ QString& foo = (*it);
foo = m_dir.filePath(foo);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
@@ -111,8 +106,7 @@ void IconList::directoryChanged(const QString &path)
auto new_set = new_list.toSet();
#endif
QList<QString> current_list;
- for (auto &it : icons)
- {
+ for (auto& it : icons) {
if (!it.has(IconType::FileBased))
continue;
current_list.push_back(it.m_images[IconType::FileBased].filename);
@@ -129,38 +123,33 @@ void IconList::directoryChanged(const QString &path)
QSet<QString> to_add = new_set;
to_add -= current_set;
- for (auto remove : to_remove)
- {
+ for (auto remove : to_remove) {
qDebug() << "Removing " << remove;
QFileInfo rmfile(remove);
QString key = rmfile.completeBaseName();
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
+ if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(IconType::FileBased);
- if (icons[idx].type() == IconType::ToBeDeleted)
- {
+ if (icons[idx].type() == IconType::ToBeDeleted) {
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
reindex();
endRemoveRows();
- }
- else
- {
+ } else {
dataChanged(index(idx), index(idx));
}
m_watcher->removePath(remove);
emit iconUpdated(key);
}
- for (auto add : to_add)
- {
+ for (auto add : to_add) {
qDebug() << "Adding " << add;
QFileInfo addfile(add);
@@ -168,11 +157,10 @@ void IconList::directoryChanged(const QString &path)
QString suffix = addfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
+ if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
- if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
- {
+ if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
m_watcher->addPath(add);
emit iconUpdated(key);
}
@@ -181,7 +169,7 @@ void IconList::directoryChanged(const QString &path)
sortIconList();
}
-void IconList::fileChanged(const QString &path)
+void IconList::fileChanged(const QString& path)
{
qDebug() << "Checking " << path;
QFileInfo checkfile(path);
@@ -200,9 +188,9 @@ void IconList::fileChanged(const QString &path)
emit iconUpdated(key);
}
-void IconList::SettingChanged(const Setting &setting, QVariant value)
+void IconList::SettingChanged(const Setting& setting, QVariant value)
{
- if(setting.id() != "IconsDir")
+ if (setting.id() != "IconsDir")
return;
directoryChanged(value.toString());
@@ -213,12 +201,9 @@ void IconList::startWatching()
auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
- if (is_watching)
- {
+ if (is_watching) {
qDebug() << "Started watching " << abs_path;
- }
- else
- {
+ } else {
qDebug() << "Failed to start watching " << abs_path;
}
}
@@ -241,7 +226,11 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction;
}
-bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
+bool IconList::dropMimeData(const QMimeData* data,
+ Qt::DropAction action,
+ [[maybe_unused]] int row,
+ [[maybe_unused]] int column,
+ [[maybe_unused]] const QModelIndex& parent)
{
if (action == Qt::IgnoreAction)
return true;
@@ -250,12 +239,10 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
return false;
// files dropped from outside?
- if (data->hasUrls())
- {
+ if (data->hasUrls()) {
auto urls = data->urls();
QStringList iconFiles;
- for (auto url : urls)
- {
+ for (auto url : urls) {
// only local files may be dropped...
if (!url.isLocalFile())
continue;
@@ -267,16 +254,13 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
return false;
}
-Qt::ItemFlags IconList::flags(const QModelIndex &index) const
+Qt::ItemFlags IconList::flags(const QModelIndex& index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsDropEnabled | defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
+ return Qt::ItemIsDropEnabled | defaultFlags;
}
-QVariant IconList::data(const QModelIndex &index, int role) const
+QVariant IconList::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -286,64 +270,49 @@ QVariant IconList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= icons.size())
return QVariant();
- switch (role)
- {
- case Qt::DecorationRole:
- return icons[row].icon();
- case Qt::DisplayRole:
- return icons[row].name();
- case Qt::UserRole:
- return icons[row].m_key;
- default:
- return QVariant();
+ switch (role) {
+ case Qt::DecorationRole:
+ return icons[row].icon();
+ case Qt::DisplayRole:
+ return icons[row].name();
+ case Qt::UserRole:
+ return icons[row].m_key;
+ default:
+ return QVariant();
}
}
-int IconList::rowCount(const QModelIndex &parent) const
+int IconList::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : icons.size();
}
-void IconList::installIcons(const QStringList &iconFiles)
+void IconList::installIcons(const QStringList& iconFiles)
{
for (QString file : iconFiles)
- {
- QFileInfo fileinfo(file);
- if (!fileinfo.isReadable() || !fileinfo.isFile())
- continue;
- QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
-
- QString suffix = fileinfo.suffix();
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
- continue;
-
- if (!QFile::copy(file, target))
- continue;
- }
+ installIcon(file, {});
}
-void IconList::installIcon(const QString &file, const QString &name)
+void IconList::installIcon(const QString& file, const QString& name)
{
QFileInfo fileinfo(file);
- if(!fileinfo.isReadable() || !fileinfo.isFile())
+ if (!fileinfo.isReadable() || !fileinfo.isFile())
return;
- QString target = FS::PathCombine(getDirectory(), name);
+ if (!IconUtils::isIconSuffix(fileinfo.suffix()))
+ return;
+ QString target = FS::PathCombine(getDirectory(), name.isEmpty() ? fileinfo.fileName() : name);
QFile::copy(file, target);
}
-bool IconList::iconFileExists(const QString &key) const
+bool IconList::iconFileExists(const QString& key) const
{
auto iconEntry = icon(key);
- if(!iconEntry)
- {
- return false;
- }
- return iconEntry->has(IconType::FileBased);
+ return iconEntry && iconEntry->has(IconType::FileBased);
}
-const MMCIcon *IconList::icon(const QString &key) const
+const MMCIcon* IconList::icon(const QString& key) const
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
@@ -351,100 +320,84 @@ const MMCIcon *IconList::icon(const QString &key) const
return &icons[iconIdx];
}
-bool IconList::deleteIcon(const QString &key)
+bool IconList::deleteIcon(const QString& key)
{
- if (!iconFileExists(key))
- return false;
-
- return QFile::remove(icon(key)->getFilePath());
+ return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
}
-bool IconList::trashIcon(const QString &key)
+bool IconList::trashIcon(const QString& key)
{
- if (!iconFileExists(key))
- return false;
-
- return FS::trash(icon(key)->getFilePath(), nullptr);
+ return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
}
bool IconList::addThemeIcon(const QString& key)
{
auto iter = name_index.find(key);
- if (iter != name_index.end())
- {
- auto &oldOne = icons[*iter];
+ if (iter != name_index.end()) {
+ auto& oldOne = icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
- else
+ // add a new icon
+ beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
- // add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
- {
- MMCIcon mmc_icon;
- mmc_icon.m_name = key;
- mmc_icon.m_key = key;
- mmc_icon.replace(Builtin, key);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
- }
- endInsertRows();
- return true;
+ MMCIcon mmc_icon;
+ mmc_icon.m_name = key;
+ mmc_icon.m_key = key;
+ mmc_icon.replace(Builtin, key);
+ icons.push_back(mmc_icon);
+ name_index[key] = icons.size() - 1;
}
+ endInsertRows();
+ return true;
}
-bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
+bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
{
// replace the icon even? is the input valid?
QIcon icon(path);
if (icon.isNull())
return false;
auto iter = name_index.find(key);
- if (iter != name_index.end())
- {
- auto &oldOne = icons[*iter];
+ if (iter != name_index.end()) {
+ auto& oldOne = icons[*iter];
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
}
- else
+ // add a new icon
+ beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
- // add a new icon
- beginInsertRows(QModelIndex(), icons.size(), icons.size());
- {
- MMCIcon mmc_icon;
- mmc_icon.m_name = name;
- mmc_icon.m_key = key;
- mmc_icon.replace(type, icon, path);
- icons.push_back(mmc_icon);
- name_index[key] = icons.size() - 1;
- }
- endInsertRows();
- return true;
+ MMCIcon mmc_icon;
+ mmc_icon.m_name = name;
+ mmc_icon.m_key = key;
+ mmc_icon.replace(type, icon, path);
+ icons.push_back(mmc_icon);
+ name_index[key] = icons.size() - 1;
}
+ endInsertRows();
+ return true;
}
-void IconList::saveIcon(const QString &key, const QString &path, const char * format) const
+void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
{
auto icon = getIcon(key);
auto pixmap = icon.pixmap(128, 128);
pixmap.save(path, format);
}
-
void IconList::reindex()
{
name_index.clear();
int i = 0;
- for (auto &iter : icons)
- {
+ for (auto& iter : icons) {
name_index[iter.m_key] = i;
i++;
}
}
-QIcon IconList::getIcon(const QString &key) const
+QIcon IconList::getIcon(const QString& key) const
{
int icon_index = getIconIndex(key);
@@ -459,7 +412,7 @@ QIcon IconList::getIcon(const QString &key) const
return QIcon();
}
-int IconList::getIconIndex(const QString &key) const
+int IconList::getIconIndex(const QString& key) const
{
auto iter = name_index.find(key == "default" ? "grass" : key);
if (iter != name_index.end())
@@ -472,5 +425,3 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
-
-//#include "IconList.moc"
diff --git a/launcher/icons/IconList.h b/launcher/icons/IconList.h
index 97141e4a..8afd0557 100644
--- a/launcher/icons/IconList.h
+++ b/launcher/icons/IconList.h
@@ -1,24 +1,43 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-
#pragma once
-#include <QMutex>
#include <QAbstractListModel>
-#include <QFile>
#include <QDir>
+#include <QFile>
+#include <QMutex>
#include <QtGui/QIcon>
#include <memory>
@@ -29,58 +48,58 @@
class QFileSystemWatcher;
-class IconList : public QAbstractListModel
-{
+class IconList : public QAbstractListModel {
Q_OBJECT
-public:
- explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
- virtual ~IconList() {};
+ public:
+ explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
+ virtual ~IconList(){};
- QIcon getIcon(const QString &key) const;
- int getIconIndex(const QString &key) const;
+ QIcon getIcon(const QString& key) const;
+ int getIconIndex(const QString& key) const;
QString getDirectory() const;
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
virtual QStringList mimeTypes() const override;
virtual Qt::DropActions supportedDropActions() const override;
- virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
- virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+ virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
+ virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
- bool addThemeIcon(const QString &key);
- bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type);
- void saveIcon(const QString &key, const QString &path, const char * format) const;
- bool deleteIcon(const QString &key);
- bool trashIcon(const QString &key);
- bool iconFileExists(const QString &key) const;
+ bool addThemeIcon(const QString& key);
+ bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
+ void saveIcon(const QString& key, const QString& path, const char* format) const;
+ bool deleteIcon(const QString& key);
+ bool trashIcon(const QString& key);
+ bool iconFileExists(const QString& key) const;
- void installIcons(const QStringList &iconFiles);
- void installIcon(const QString &file, const QString &name);
+ void installIcons(const QStringList& iconFiles);
+ void installIcon(const QString& file, const QString& name);
- const MMCIcon * icon(const QString &key) const;
+ const MMCIcon* icon(const QString& key) const;
void startWatching();
void stopWatching();
-signals:
+ signals:
void iconUpdated(QString key);
-private:
+ private:
// hide copy constructor
- IconList(const IconList &) = delete;
+ IconList(const IconList&) = delete;
// hide assign op
- IconList &operator=(const IconList &) = delete;
+ IconList& operator=(const IconList&) = delete;
void reindex();
void sortIconList();
-public slots:
- void directoryChanged(const QString &path);
+ public slots:
+ void directoryChanged(const QString& path);
+
+ protected slots:
+ void fileChanged(const QString& path);
+ void SettingChanged(const Setting& setting, QVariant value);
-protected slots:
- void fileChanged(const QString &path);
- void SettingChanged(const Setting & setting, QVariant value);
-private:
+ private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
diff --git a/launcher/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp
index 4015b329..99c38f47 100644
--- a/launcher/icons/IconUtils.cpp
+++ b/launcher/icons/IconUtils.cpp
@@ -1,25 +1,51 @@
+// 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/>.
+ *
+ * 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 "IconUtils.h"
-#include "FileSystem.h"
#include <QDirIterator>
-
-#include <array>
+#include "FileSystem.h"
namespace {
-std::array<const char *, 6> validIconExtensions = {{
- "svg",
- "png",
- "ico",
- "gif",
- "jpg",
- "jpeg"
-}};
+static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
}
-namespace IconUtils{
+namespace IconUtils {
-QString findBestIconIn(const QString &folder, const QString & iconKey) {
- size_t best_found = validIconExtensions.size();
+QString findBestIconIn(const QString& folder, const QString& iconKey)
+{
QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
@@ -27,36 +53,20 @@ QString findBestIconIn(const QString &folder, const QString & iconKey) {
it.next();
auto fileInfo = it.fileInfo();
- if(fileInfo.completeBaseName() != iconKey)
- continue;
-
- auto extension = fileInfo.suffix();
-
- for(size_t i = 0; i < best_found; i++) {
- if(extension == validIconExtensions[i]) {
- best_found = i;
- qDebug() << i << " : " << fileInfo.fileName();
- best_filename = fileInfo.fileName();
- }
- }
+ if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
+ return fileInfo.absoluteFilePath();
}
- return FS::PathCombine(folder, best_filename);
+ return {};
}
-QString getIconFilter() {
- QString out;
- QTextStream stream(&out);
- stream << '(';
- for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
- if(i > 0) {
- stream << " ";
- }
- stream << "*." << validIconExtensions[i];
- }
- stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
- stream << ')';
- return out;
+QString getIconFilter()
+{
+ return "(*." + validIconExtensions.join(" *.") + ")";
}
+bool isIconSuffix(QString suffix)
+{
+ return validIconExtensions.contains(suffix);
}
+} // namespace IconUtils
diff --git a/launcher/icons/IconUtils.h b/launcher/icons/IconUtils.h
index be93d914..90cdfe5a 100644
--- a/launcher/icons/IconUtils.h
+++ b/launcher/icons/IconUtils.h
@@ -1,3 +1,38 @@
+// 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/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#pragma once
#include <QString>
@@ -5,9 +40,10 @@
namespace IconUtils {
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
-QString findBestIconIn(const QString &folder, const QString & iconKey);
+QString findBestIconIn(const QString& folder, const QString& iconKey);
// Get icon file type filter for file browser dialogs
QString getIconFilter();
-}
+bool isIconSuffix(QString suffix);
+} // namespace IconUtils
diff --git a/launcher/icons/MMCIcon.cpp b/launcher/icons/MMCIcon.cpp
index 436ef75f..991b470c 100644
--- a/launcher/icons/MMCIcon.cpp
+++ b/launcher/icons/MMCIcon.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * 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
@@ -37,23 +38,21 @@
#include <QFileInfo>
#include <QIcon>
-IconType operator--(IconType &t, int)
+IconType operator--(IconType& t, int)
{
IconType temp = t;
- switch (t)
- {
- case IconType::Builtin:
- t = IconType::ToBeDeleted;
- break;
- case IconType::Transient:
- t = IconType::Builtin;
- break;
- case IconType::FileBased:
- t = IconType::Transient;
- break;
- default:
- {
- }
+ switch (t) {
+ case IconType::Builtin:
+ t = IconType::ToBeDeleted;
+ break;
+ case IconType::Transient:
+ t = IconType::Builtin;
+ break;
+ case IconType::FileBased:
+ t = IconType::Transient;
+ break;
+ default:
+ break;
}
return temp;
}
@@ -79,8 +78,8 @@ QIcon MMCIcon::icon() const
{
if (m_current_type == IconType::ToBeDeleted)
return QIcon();
- auto & icon = m_images[m_current_type].icon;
- if(!icon.isNull())
+ auto& icon = m_images[m_current_type].icon;
+ if (!icon.isNull())
return icon;
// FIXME: inject this.
return QIcon::fromTheme(m_images[m_current_type].key);
@@ -90,10 +89,8 @@ void MMCIcon::remove(IconType rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
- for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
- {
- if (m_images[iter].present())
- {
+ for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) {
+ if (m_images[iter].present()) {
m_current_type = iter;
return;
}
@@ -103,8 +100,7 @@ void MMCIcon::remove(IconType rm_type)
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
{
- if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
- {
+ if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
m_current_type = new_type;
}
m_images[new_type].icon = icon;
@@ -114,8 +110,7 @@ void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
void MMCIcon::replace(IconType new_type, const QString& key)
{
- if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
- {
+ if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
m_current_type = new_type;
}
m_images[new_type].icon = QIcon();
@@ -125,13 +120,12 @@ void MMCIcon::replace(IconType new_type, const QString& key)
QString MMCIcon::getFilePath() const
{
- if(m_current_type == IconType::ToBeDeleted){
+ if (m_current_type == IconType::ToBeDeleted) {
return QString();
}
return m_images[m_current_type].filename;
}
-
bool MMCIcon::isBuiltIn() const
{
return m_current_type == IconType::Builtin;
diff --git a/launcher/icons/MMCIcon.h b/launcher/icons/MMCIcon.h
index 13d99318..a6e30566 100644
--- a/launcher/icons/MMCIcon.h
+++ b/launcher/icons/MMCIcon.h
@@ -1,45 +1,52 @@
-/* Copyright 2013-2021 MultiMC Contributors
+// SPDX-License-Identifier: GPL-3.0-only
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *
+ * Copyright 2013-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-
#pragma once
-#include <QString>
#include <QDateTime>
#include <QIcon>
+#include <QString>
-enum IconType : unsigned
-{
- Builtin,
- Transient,
- FileBased,
- ICONS_TOTAL,
- ToBeDeleted
-};
+enum IconType : unsigned { Builtin, Transient, FileBased, ICONS_TOTAL, ToBeDeleted };
-struct MMCImage
-{
+struct MMCImage {
QIcon icon;
QString key;
QString filename;
- bool present() const
- {
- return !icon.isNull() || !key.isEmpty();
- }
+ bool present() const { return !icon.isNull() || !key.isEmpty(); }
};
-struct MMCIcon
-{
+struct MMCIcon {
QString m_key;
QString m_name;
MMCImage m_images[ICONS_TOTAL];
@@ -51,7 +58,7 @@ struct MMCIcon
QIcon icon() const;
void remove(IconType rm_type);
void replace(IconType new_type, QIcon icon, QString path = QString());
- void replace(IconType new_type, const QString &key);
+ void replace(IconType new_type, const QString& key);
bool isBuiltIn() const;
QString getFilePath() const;
};
diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp
index 6acf2bbd..7d83db2a 100644
--- a/launcher/meta/BaseEntity.cpp
+++ b/launcher/meta/BaseEntity.cpp
@@ -15,7 +15,7 @@
#include "BaseEntity.h"
-#include "net/Download.h"
+#include "net/ApiDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
#include "Json.h"
@@ -130,7 +130,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
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.
* If that fails, the file is not written to storage.
diff --git a/launcher/minecraft/AssetsUtils.cpp b/launcher/minecraft/AssetsUtils.cpp
index 16fdfdb1..65ad6da6 100644
--- a/launcher/minecraft/AssetsUtils.cpp
+++ b/launcher/minecraft/AssetsUtils.cpp
@@ -45,7 +45,7 @@
#include "AssetsUtils.h"
#include "FileSystem.h"
-#include "net/Download.h"
+#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "BuildConfig.h"
@@ -311,7 +311,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
QFileInfo objectFile(getLocalPath());
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())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());
diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp
index cb2b5254..e0318ef2 100644
--- a/launcher/minecraft/Library.cpp
+++ b/launcher/minecraft/Library.cpp
@@ -36,7 +36,7 @@
#include "Library.h"
#include "MinecraftInstance.h"
-#include <net/Download.h>
+#include <net/ApiDownload.h>
#include <net/ChecksumValidator.h>
#include <FileSystem.h>
#include <BuildConfig.h>
@@ -129,14 +129,14 @@ QList<NetAction::Ptr> Library::getDownloads(
if(sha1.size())
{
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));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
}
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;
}
return true;
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 5b797c1b..07ed04f9 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -61,7 +61,6 @@
#include "launch/steps/QuitAfterGameStop.h"
#include "minecraft/launch/LauncherPartLaunch.h"
-#include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.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("PermGen"), memorySetting);
- // Minecraft launch method
- auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
- m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
-
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
@@ -990,15 +985,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
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)
{
process->appendStep(makeShared<CreateGameFolders>(pptr));
@@ -1072,23 +1058,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
{
// actually launch the game
- auto launch_method = launchMethod();
- if(launch_method == "LauncherPart")
- {
- auto step = makeShared<LauncherPartLaunch>(pptr);
- step->setWorkingDirectory(gameRoot());
- step->setAuthSession(session);
- step->setServerToJoin(serverToJoin);
- process->appendStep(step);
- }
- else if (launch_method == "DirectJava")
- {
- auto step = makeShared<DirectJavaLaunch>(pptr);
- step->setWorkingDirectory(gameRoot());
- step->setAuthSession(session);
- step->setServerToJoin(serverToJoin);
- process->appendStep(step);
- }
+ auto step = makeShared<LauncherPartLaunch>(pptr);
+ step->setWorkingDirectory(gameRoot());
+ step->setAuthSession(session);
+ step->setServerToJoin(serverToJoin);
+ process->appendStep(step);
}
// run post-exit command if that's needed
@@ -1111,11 +1085,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
return m_launchProcess;
}
-QString MinecraftInstance::launchMethod()
-{
- return settings()->get("MCLaunchMethod").toString();
-}
-
JavaVersion MinecraftInstance::getJavaVersion()
{
return JavaVersion(settings()->get("JavaVersion").toString());
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 068b3008..4afcf04b 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -165,8 +165,6 @@ public:
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
- QStringList validLaunchMethods();
- QString launchMethod();
protected: // data
std::shared_ptr<PackProfile> m_components;
diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp
deleted file mode 100644
index ca55cd2e..00000000
--- a/launcher/minecraft/launch/DirectJavaLaunch.cpp
+++ /dev/null
@@ -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;
-}
-
diff --git a/launcher/minecraft/launch/DirectJavaLaunch.h b/launcher/minecraft/launch/DirectJavaLaunch.h
deleted file mode 100644
index 58b119b8..00000000
--- a/launcher/minecraft/launch/DirectJavaLaunch.h
+++ /dev/null
@@ -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;
-};
-
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 8e4486fd..7b6d936c 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -61,6 +61,7 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
+ m_columnsHideable = { false, true, false, true, true, true };
}
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index 3c58c5eb..f48ddb64 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -552,6 +552,8 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) {
+ // Skip creating actions for columns that should not be hidden
+ if (!m_columnsHideable.at(col)) continue;
auto act = new QAction(menu);
setupHeaderAction(act, col);
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index ee905e3b..45a5cc3a 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -202,6 +202,7 @@ class ResourceFolderModel : public QAbstractListModel {
QStringList m_column_names = {"Enable", "Name", "Last Modified"};
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
+ QList<bool> m_columnsHideable = { false, false, true };
QDir m_dir;
BaseInstance* m_instance;
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index f1a0572a..0d316e36 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -54,7 +54,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE};
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
-
+ m_columnsHideable = { false, true, false, true, true };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp
index 3795db58..7dbedc3f 100644
--- a/launcher/minecraft/mod/TexturePackFolderModel.cpp
+++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp
@@ -49,6 +49,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
+ m_columnsHideable = { false, true, false, true };
}
diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp
index fda85ba8..ec0c90b7 100644
--- a/launcher/minecraft/update/AssetUpdateTask.cpp
+++ b/launcher/minecraft/update/AssetUpdateTask.cpp
@@ -7,6 +7,8 @@
#include "Application.h"
+#include "net/ApiDownload.h"
+
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
{
m_inst = inst;
@@ -34,7 +36,7 @@ void AssetUpdateTask::executeTask()
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
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());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);
diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp
index d9fa0595..51b6d74c 100644
--- a/launcher/minecraft/update/FMLLibrariesTask.cpp
+++ b/launcher/minecraft/update/FMLLibrariesTask.cpp
@@ -8,6 +8,8 @@
#include "BuildConfig.h"
#include "Application.h"
+#include "net/ApiDownload.h"
+
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
@@ -68,7 +70,7 @@ void FMLLibrariesTask::executeTask()
{
auto entry = metacache->resolveEntry("fmllibs", 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);
diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
index 82a51c27..8260a25c 100644
--- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
+++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -53,6 +53,8 @@
#include "meta/Version.h"
#include "meta/VersionList.h"
+#include "net/ApiDownload.h"
+
#include "BuildConfig.h"
#include "Application.h"
@@ -82,9 +84,9 @@ void PackInstallTask::executeTask()
{
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
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);
- 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::failed, this, &PackInstallTask::onDownloadFailed);
@@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
@@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToExtract.insert(entry->getFullPath(), mod);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToDecomp.insert(entry->getFullPath(), mod);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true);
- auto dl = Net::Download::makeCached(url, entry);
+ auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 3f578a7c..9c0cb35b 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -1,7 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
+#include "net/ApiUpload.h"
#include "net/Upload.h"
+#include "net/ApiDownload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
@@ -38,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()
return l;
}));
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);
auto step_progress = std::make_shared<TaskStepProgress>();
@@ -99,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()
if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
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; });
m_checkJob->addNetAction(dl);
@@ -171,7 +173,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>();
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;
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
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 5b0b1d8b..7231b466 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -8,7 +8,9 @@
#include "Application.h"
#include "BuildConfig.h"
#include "Json.h"
+#include "net/ApiUpload.h"
#include "net/NetJob.h"
+#include "net/ApiDownload.h"
#include "net/Upload.h"
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);
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;
}
@@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
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")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response));
@@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
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] {
QJsonParseError parse_error{};
@@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>();
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] {
QJsonParseError parse_error{};
@@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
QJsonDocument body(body_obj);
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; });
@@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
QJsonDocument body(body_obj);
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; });
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
index a2628e34..e10fedc3 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.cpp
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -13,6 +13,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
+#include "net/ApiDownload.h"
+
static FlameAPI api;
bool FlameCheckUpdate::abort()
@@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto response = std::make_shared<QByteArray>();
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);
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 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);
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index e17cf1c2..c170a4f5 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -61,6 +61,7 @@
#include "meta/VersionList.h"
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "net/ApiDownload.h"
static const FlameAPI api;
@@ -523,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
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);
}
break;
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index 87bf780c..f5f3af37 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -26,6 +26,7 @@
#include <QMessageBox>
#include <QtConcurrentRun>
#include <algorithm>
+#include <iterator>
#include <memory>
#include "Json.h"
#include "MMCZip.h"
@@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()
bool FlamePackExportTask::abort()
{
- if (task != nullptr) {
+ if (task) {
task->abort();
- task = nullptr;
emitAborted();
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;
}
@@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()
setStatus(tr("Adding files..."));
setProgress(4, 5);
- buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
- QuaZip zip(output);
- if (!zip.open(QuaZip::mdCreate)) {
- QFile::remove(output);
- return BuildZipResult(tr("Could not create file"));
- }
-
- if (buildZipFuture.isCanceled())
- return BuildZipResult();
+ auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
+ zipTask->addExtraFile("manifest.json", generateIndex());
+ zipTask->addExtraFile("modlist.html", generateHTML());
- QuaZipFile indexFile(&zip);
- if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("manifest.json"))) {
- QFile::remove(output);
- return BuildZipResult(tr("Could not create index"));
- }
- indexFile.write(generateIndex());
+ QStringList exclude;
+ std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
+ [this](QString file) { return gameRoot.relativeFilePath(file); });
+ zipTask->setExcludeFiles(exclude);
- 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>();
-
- 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"));
- }
+ auto progressStep = std::make_shared<TaskStepProgress>();
+ connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
progressStep->state = TaskStepState::Succeeded;
stepProgress(*progressStep);
- return BuildZipResult();
});
- connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
- buildZipWatcher.setFuture(buildZipFuture);
-}
-void FlamePackExportTask::finish()
-{
- if (buildZipFuture.isCanceled())
- emitAborted();
- else {
- const BuildZipResult result = buildZipFuture.result();
- if (result.has_value())
- emitFailed(result.value());
- else
- emitSucceeded();
- }
+ connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded);
+ connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
+ connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
+ });
+ connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
+
+ 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()
@@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()
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();
+} \ No newline at end of file
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
index 3dee0a7e..d3dc6281 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.h
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -19,8 +19,6 @@
#pragma once
-#include <QFuture>
-#include <QFutureWatcher>
#include "BaseInstance.h"
#include "MMCZip.h"
#include "minecraft/MinecraftInstance.h"
@@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {
const QString output;
const MMCZip::FilterFunction filter;
- typedef std::optional<QString> BuildZipResult;
struct ResolvedFile {
int addonId;
int version;
@@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {
QMap<QString, HashInfo> pendingHashes{};
QMap<QString, ResolvedFile> resolvedFiles{};
Task::Ptr task;
- QFuture<BuildZipResult> buildZipFuture;
- QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles();
void collectHashes();
void makeApiRequest();
void getProjectsInfo();
void buildZip();
- void finish();
QByteArray generateIndex();
+ QByteArray generateHTML();
};
diff --git a/launcher/modplatform/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
index e5718acb..5c3f2003 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
@@ -10,6 +10,8 @@
#include "modplatform/ModIndex.h"
+#include "net/ApiDownload.h"
+
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
{
auto search_url_optional = getSearchURL(args);
@@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
auto response = std::make_shared<QByteArray>();
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] {
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 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] {
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());
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
return netJob;
}
diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp
new file mode 100644
index 00000000..4a1bbef9
--- /dev/null
+++ b/launcher/modplatform/import_ftb/PackHelpers.cpp
@@ -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
diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h
new file mode 100644
index 00000000..8ea4f3fa
--- /dev/null
+++ b/launcher/modplatform/import_ftb/PackHelpers.h
@@ -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)
diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp
new file mode 100644
index 00000000..b5e424d1
--- /dev/null
+++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp
@@ -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
diff --git a/launcher/modplatform/import_ftb/PackInstallTask.h b/launcher/modplatform/import_ftb/PackInstallTask.h
new file mode 100644
index 00000000..842e4b35
--- /dev/null
+++ b/launcher/modplatform/import_ftb/PackInstallTask.h
@@ -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
diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
index a8a0fc2c..e5ad8244 100644
--- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp
@@ -40,6 +40,8 @@
#include "BuildConfig.h"
#include "Application.h"
+#include "net/ApiDownload.h"
+
namespace LegacyFTB {
void PackFetchTask::fetch()
@@ -51,7 +53,7 @@ void PackFetchTask::fetch()
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
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");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
@@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
for (auto& packCode : toFetch) {
auto data = std::make_shared<QByteArray>();
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] {
ModpackList packs;
diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
index 1afe5783..beb295dc 100644
--- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -48,6 +48,8 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "net/ApiDownload.h"
+
namespace LegacyFTB {
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version)
@@ -77,7 +79,7 @@ void PackInstallTask::downloadPack()
} else {
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::failed, this, &PackInstallTask::emitFailed);
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp
index 364cf3f3..466c5b10 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.cpp
+++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp
@@ -6,6 +6,8 @@
#include "Application.h"
#include "Json.h"
+#include "net/ApiDownload.h"
+#include "net/ApiUpload.h"
#include "net/NetJob.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());
- 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));
return netJob;
@@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
QJsonDocument body(body_obj);
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;
}
@@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
QJsonDocument body(body_obj);
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));
return netJob;
@@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QJsonDocument body(body_obj);
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;
}
@@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
auto searchUrl = getMultipleModInfoURL(addonIds);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
return netJob;
}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
index bd0b828c..2e42d6e7 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
@@ -12,6 +12,7 @@
#include "net/ChecksumValidator.h"
#include "net/NetJob.h"
+#include "net/ApiDownload.h"
#include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h"
@@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()
}
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));
m_files_job->addNetAction(dl);
@@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()
// MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef();
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));
m_files_job->addNetAction(ndl);
if (auto shared = param.lock()) shared->succeeded();
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
index c7df32a3..64d31f42 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
@@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()
bool ModrinthPackExportTask::abort()
{
- if (task != nullptr) {
+ if (task) {
task->abort();
- task = nullptr;
emitAborted();
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;
}
@@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()
{
setStatus(tr("Adding files..."));
- buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
- QuaZip zip(output);
- if (!zip.open(QuaZip::mdCreate)) {
- QFile::remove(output);
- return BuildZipResult(tr("Could not create file"));
- }
-
- if (buildZipFuture.isCanceled())
- return BuildZipResult();
-
- QuaZipFile indexFile(&zip);
- if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
- QFile::remove(output);
- return BuildZipResult(tr("Could not create index"));
- }
- 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++;
- }
+ auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
+ zipTask->addExtraFile("modrinth.index.json", generateIndex());
- zip.close();
+ zipTask->setExcludeFiles(resolvedFiles.keys());
- if (zip.getZipError() != 0) {
- QFile::remove(output);
- return BuildZipResult(tr("A zip error occurred"));
- }
+ auto progressStep = std::make_shared<TaskStepProgress>();
+ connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
+ progressStep->state = TaskStepState::Succeeded;
+ stepProgress(*progressStep);
+ });
- return BuildZipResult();
+ connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
+ connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
+ connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
+ progressStep->state = TaskStepState::Failed;
+ stepProgress(*progressStep);
+ emitFailed(reason);
});
- connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
- buildZipWatcher.setFuture(buildZipFuture);
-}
+ connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
-void ModrinthPackExportTask::finish()
-{
- if (buildZipFuture.isCanceled())
- emitAborted();
- else {
- const BuildZipResult result = buildZipFuture.result();
- if (result.has_value())
- emitFailed(result.value());
- 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()
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
index 96f292c1..1f9e0eb7 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
@@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
const QString output;
const MMCZip::FilterFunction filter;
- typedef std::optional<QString> BuildZipResult;
-
ModrinthAPI api;
QFileInfoList files;
QMap<QString, QString> pendingHashes;
QMap<QString, ResolvedFile> resolvedFiles;
Task::Ptr task;
- QFuture<BuildZipResult> buildZipFuture;
- QFutureWatcher<BuildZipResult> buildZipWatcher;
void collectFiles();
void collectHashes();
void makeApiRequest();
void parseApiResponse(const std::shared_ptr<QByteArray> response);
void buildZip();
- void finish();
QByteArray generateIndex();
};
diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
index ab91c466..b2097c1d 100644
--- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
+++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp
@@ -23,6 +23,8 @@
#include "Application.h"
+#include "net/ApiDownload.h"
+
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
{
m_sourceUrl = sourceUrl;
@@ -45,7 +47,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
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();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp
index cc1d261e..d7e9f640 100644
--- a/launcher/modplatform/technic/SolderPackInstallTask.cpp
+++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp
@@ -43,6 +43,7 @@
#include "SolderPackManifest.h"
#include "TechnicPackProcessor.h"
#include "net/ChecksumValidator.h"
+#include "net/ApiDownload.h"
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
const QUrl& solderUrl,
@@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
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);
- m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
@@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
for (const auto& mod : build.mods) {
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()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp
new file mode 100644
index 00000000..aaa8ff65
--- /dev/null
+++ b/launcher/net/ApiDownload.cpp
@@ -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
diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h
new file mode 100644
index 00000000..638c94e1
--- /dev/null
+++ b/launcher/net/ApiDownload.h
@@ -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
diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h
new file mode 100644
index 00000000..789a6fad
--- /dev/null
+++ b/launcher/net/ApiHeaderProxy.h
@@ -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
diff --git a/launcher/net/ApiUpload.cpp b/launcher/net/ApiUpload.cpp
new file mode 100644
index 00000000..c1221b76
--- /dev/null
+++ b/launcher/net/ApiUpload.cpp
@@ -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
diff --git a/launcher/net/ApiUpload.h b/launcher/net/ApiUpload.h
new file mode 100644
index 00000000..b12842b0
--- /dev/null
+++ b/launcher/net/ApiUpload.h
@@ -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
diff --git a/launcher/net/ByteArraySink.h b/launcher/net/ByteArraySink.h
index d6b17d60..7b8f0f8a 100644
--- a/launcher/net/ByteArraySink.h
+++ b/launcher/net/ByteArraySink.h
@@ -42,8 +42,6 @@ namespace Net {
/*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
- * FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
- * causing a segmentation fault.
*/
class ByteArraySink : public Sink {
public:
diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp
index 4ea45c63..d25447b2 100644
--- a/launcher/net/Download.cpp
+++ b/launcher/net/Download.cpp
@@ -47,17 +47,11 @@
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
-#include "Application.h"
-#include "BuildConfig.h"
-
-#include "net/Logging.h"
#include "net/NetAction.h"
-#include "MMCTime.h"
-#include "StringUtils.h"
-
namespace Net {
+#if defined(LAUNCHER_APPLICATION)
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
auto dl = makeShared<Download>();
@@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
dl->m_sink.reset(cachedNode);
return dl;
}
+#endif
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;
}
-void Download::addValidator(Validator* v)
-{
- m_sink->addValidator(v);
-}
-
-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
-
-auto Net::Download::abort() -> bool
+QNetworkReply* Download::getReply(QNetworkRequest& request)
{
- if (m_reply) {
- m_reply->abort();
- } else {
- m_state = State::AbortedByUser;
- }
- return true;
+ return m_network->get(request);
}
+} // namespace Net \ No newline at end of file
diff --git a/launcher/net/Download.h b/launcher/net/Download.h
index 0f11c52f..5f6a5caf 100644
--- a/launcher/net/Download.h
+++ b/launcher/net/Download.h
@@ -38,57 +38,26 @@
#pragma once
-#include <chrono>
-
#include "HttpMetaCache.h"
-#include "NetAction.h"
-#include "Sink.h"
-#include "Validator.h"
#include "QObjectPtr.h"
+#include "net/NetRequest.h"
namespace Net {
-class Download : public NetAction {
+class Download : public NetRequest {
Q_OBJECT
-
public:
using Ptr = shared_qobject_ptr<class Download>;
- enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
- Q_DECLARE_FLAGS(Options, Option)
-
- public:
- ~Download() override = default;
+ explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
+#if defined(LAUNCHER_APPLICATION)
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 makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
- public:
- void addValidator(Validator* v);
- auto abort() -> bool override;
- auto canAbort() const -> bool override { return true; }
-
- private:
- 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;
+ protected:
+ virtual QNetworkReply* getReply(QNetworkRequest&) override;
};
} // namespace Net
-
-Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
diff --git a/launcher/net/HeaderProxy.h b/launcher/net/HeaderProxy.h
new file mode 100644
index 00000000..f41c5875
--- /dev/null
+++ b/launcher/net/HeaderProxy.h
@@ -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
diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h
index c22eaf9f..247b34c0 100644
--- a/launcher/net/NetAction.h
+++ b/launcher/net/NetAction.h
@@ -42,6 +42,8 @@
#include "QObjectPtr.h"
#include "tasks/Task.h"
+#include "HeaderProxy.h"
+
class NetAction : public Task {
Q_OBJECT
protected:
@@ -56,13 +58,17 @@ class NetAction : public Task {
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:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
- virtual void sslErrors(const QList<QSslError>& errors) {
+ virtual void sslErrors(const QList<QSslError>& errors)
+ {
int i = 1;
for (auto error : errors) {
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
@@ -70,7 +76,6 @@ class NetAction : public Task {
qCritical() << "Certificate in question:\n" << cert.toText();
i++;
}
-
}
public slots:
@@ -91,4 +96,5 @@ class NetAction : public Task {
/// source URL
QUrl m_url;
+ std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
};
diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp
new file mode 100644
index 00000000..ff59da18
--- /dev/null
+++ b/launcher/net/NetRequest.cpp
@@ -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
diff --git a/launcher/net/NetRequest.h b/launcher/net/NetRequest.h
new file mode 100644
index 00000000..9a602162
--- /dev/null
+++ b/launcher/net/NetRequest.h
@@ -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)
diff --git a/launcher/net/RawHeaderProxy.h b/launcher/net/RawHeaderProxy.h
new file mode 100644
index 00000000..09b3d4d0
--- /dev/null
+++ b/launcher/net/RawHeaderProxy.h
@@ -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
diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp
index 3f6f5829..726572e5 100644
--- a/launcher/net/Upload.cpp
+++ b/launcher/net/Upload.cpp
@@ -38,219 +38,16 @@
#include "Upload.h"
+#include <memory>
#include <utility>
-#include "Application.h"
-#include "BuildConfig.h"
#include "ByteArraySink.h"
-#include "net/Logging.h"
-
namespace Net {
-bool Upload::abort()
-{
- 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()
+QNetworkReply* Upload::getReply(QNetworkRequest& request)
{
- 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");
- QNetworkReply* rep = 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);
+ return m_network->post(request, m_post_data);
}
Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
diff --git a/launcher/net/Upload.h b/launcher/net/Upload.h
index 0b0c9497..f920e556 100644
--- a/launcher/net/Upload.h
+++ b/launcher/net/Upload.h
@@ -37,36 +37,21 @@
#pragma once
-#include "NetAction.h"
-#include "Sink.h"
+#include "net/NetRequest.h"
namespace Net {
-class Upload : public NetAction {
+class Upload : public NetRequest {
Q_OBJECT
-
public:
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);
- auto abort() -> bool override;
- auto canAbort() const -> bool override { return true; };
-
- 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;
+ protected:
+ virtual QNetworkReply* getReply(QNetworkRequest&) override;
QByteArray m_post_data;
-
- bool handleRedirect();
};
} // namespace Net
diff --git a/launcher/screenshots/ImgurAlbumCreation.h b/launcher/screenshots/ImgurAlbumCreation.h
index 0228b6e4..a2b70d8b 100644
--- a/launcher/screenshots/ImgurAlbumCreation.h
+++ b/launcher/screenshots/ImgurAlbumCreation.h
@@ -57,6 +57,8 @@ public:
return m_id;
}
+ void init() override {};
+
protected
slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
diff --git a/launcher/screenshots/ImgurUpload.h b/launcher/screenshots/ImgurUpload.h
index 404dc876..e8a6d8d7 100644
--- a/launcher/screenshots/ImgurUpload.h
+++ b/launcher/screenshots/ImgurUpload.h
@@ -46,6 +46,7 @@ public:
static Ptr make(ScreenShot::Ptr shot) {
return Ptr(new ImgurUpload(shot));
}
+ void init() override {};
protected
slots:
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 1069ab25..423cd48d 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -1345,16 +1345,10 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
if (m_selectedInstance) {
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
if (instance) {
- QString errorMsg;
- 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") {
- errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks.");
- }
- if (!errorMsg.isEmpty()) {
- QMessageBox msgBox;
- msgBox.setText(errorMsg);
+ if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
+ cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
+ QMessageBox msgBox(this);
+ msgBox.setText("Snapshots are currently not supported by CurseForge modpacks.");
msgBox.exec();
return;
}
diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp
index 88739463..b1734eff 100644
--- a/launcher/ui/dialogs/AboutDialog.cpp
+++ b/launcher/ui/dialogs/AboutDialog.cpp
@@ -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>");
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));
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index 95ded16e..3e6456bc 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -33,38 +33,37 @@
* limitations under the License.
*/
-#include "Application.h"
#include "NewInstanceDialog.h"
+#include "Application.h"
+#include "ui/pages/modplatform/import_ftb/ImportFTBPage.h"
#include "ui_NewInstanceDialog.h"
#include <BaseVersion.h>
+#include <InstanceList.h>
#include <icons/IconList.h>
#include <tasks/Task.h>
-#include <InstanceList.h>
-#include "VersionSelectDialog.h"
-#include "ProgressDialog.h"
#include "IconPickerDialog.h"
+#include "ProgressDialog.h"
+#include "VersionSelectDialog.h"
+#include <QDialogButtonBox>
+#include <QFileDialog>
#include <QLayout>
#include <QPushButton>
-#include <QFileDialog>
#include <QValidator>
-#include <QDialogButtonBox>
#include <utility>
-#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/CustomPage.h"
+#include "ui/pages/modplatform/ImportPage.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/legacy_ftb/Page.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.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)
{
ui->setupUi(this);
@@ -168,6 +167,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(new FlamePage(this));
pages.append(new LegacyFTB::Page(this));
+ pages.append(new FTBImportAPP::ImportFTBPage(this));
pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this));
@@ -284,28 +284,27 @@ QString NewInstanceDialog::iconKey() const
void NewInstanceDialog::on_iconButton_clicked()
{
- importIconNow(); //so the user can switch back
+ importIconNow(); // so the user can switch back
IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey);
- if (dlg.result() == QDialog::Accepted)
- {
+ if (dlg.result() == QDialog::Accepted) {
InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false;
}
}
-void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString &arg1)
+void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString& arg1)
{
updateDialogState();
}
void NewInstanceDialog::importIconNow()
{
- if(importIcon) {
+ if (importIcon) {
APPLICATION->icons()->installIcon(importIconPath, importIconName);
- InstIconKey = importIconName;
+ InstIconKey = importIconName.mid(0, importIconName.lastIndexOf('.'));
importIcon = false;
}
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
diff --git a/launcher/ui/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui
index c7b16645..2c81a7fe 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.ui
+++ b/launcher/ui/dialogs/SkinUploadDialog.ui
@@ -42,7 +42,7 @@
</size>
</property>
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/ExternalToolsPage.ui b/launcher/ui/pages/global/ExternalToolsPage.ui
index 3643094d..47c77842 100644
--- a/launcher/ui/pages/global/ExternalToolsPage.ui
+++ b/launcher/ui/pages/global/ExternalToolsPage.ui
@@ -47,7 +47,7 @@
<item>
<widget class="QPushButton" name="jprofilerPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -84,7 +84,7 @@
<item>
<widget class="QPushButton" name="jvisualvmPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -121,7 +121,7 @@
<item>
<widget class="QPushButton" name="mceditPathBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -166,7 +166,7 @@
<item row="0" column="2">
<widget class="QToolButton" name="jsonEditorBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/global/JavaPage.ui b/launcher/ui/pages/global/JavaPage.ui
index 561cf79b..5a547637 100644
--- a/launcher/ui/pages/global/JavaPage.ui
+++ b/launcher/ui/pages/global/JavaPage.ui
@@ -160,37 +160,73 @@
<string>Java Runtime</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
- <item row="3" column="1">
- <widget class="QPushButton" name="javaDetectBtn">
+ <item row="7" column="0" colspan="3">
+ <widget class="QPlainTextEdit" name="jvmArgsTextBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>&amp;Auto-detect...</string>
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>100</height>
+ </size>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="labelJVMArgs">
+ <item row="4" column="0">
+ <widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="text">
- <string>JVM arguments:</string>
+ <property name="toolTip">
+ <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
</property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ <property name="text">
+ <string>&amp;Skip Java compatibility checks</string>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QLabel" name="labelJavaPath">
+ <item row="2" column="0" colspan="3">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -198,69 +234,50 @@
</sizepolicy>
</property>
<property name="text">
- <string>&amp;Java path:</string>
- </property>
- <property name="buddy">
- <cstring>javaPathTextBox</cstring>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QPushButton" name="javaTestBtn">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <string>JVM arguments:</string>
</property>
- <property name="text">
- <string>&amp;Test</string>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
- <item row="0" column="1" colspan="2">
+ <item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
+ <widget class="QLabel" name="labelJavaPath">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Java path:</string>
+ </property>
+ <property name="buddy">
+ <cstring>javaPathTextBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="maximumSize">
- <size>
- <width>28</width>
- <height>16777215</height>
- </size>
- </property>
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
- <item row="4" column="1">
- <widget class="QCheckBox" name="skipCompatibilityCheckbox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
- </property>
- <property name="text">
- <string>&amp;Skip Java compatibility checks</string>
- </property>
- </widget>
- </item>
- <item row="5" column="1">
+ <item row="5" column="0">
<widget class="QCheckBox" name="skipJavaWizardCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not prompt you to choose a Java version if one isn't found.</string>
@@ -270,25 +287,6 @@
</property>
</widget>
</item>
- <item row="2" column="1" colspan="2">
- <widget class="QPlainTextEdit" name="jvmArgsTextBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>100</height>
- </size>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
@@ -317,8 +315,6 @@
<tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop>
- <tabstop>javaDetectBtn</tabstop>
- <tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 26408f44..bc259a9b 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -99,7 +99,7 @@
<item row="3" column="2">
<widget class="QToolButton" name="downloadsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -109,7 +109,7 @@
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
@@ -126,14 +126,14 @@
<item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Browse</string>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index 245433fe..380d8c88 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -61,31 +61,7 @@
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0" colspan="3">
- <widget class="QLineEdit" name="javaPathTextBox"/>
- </item>
- <item row="1" column="0">
- <widget class="QPushButton" name="javaDetectBtn">
- <property name="text">
- <string>Auto-detect...</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="javaBrowseBtn">
- <property name="text">
- <string>Browse...</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QPushButton" name="javaTestBtn">
- <property name="text">
- <string>Test</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
+ <item row="4" column="0">
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
@@ -95,6 +71,38 @@
</property>
</widget>
</item>
+ <item row="1" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="javaPathTextBox"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaBrowseBtn">
+ <property name="text">
+ <string>Browse</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="javaDetectBtn">
+ <property name="text">
+ <string>Auto-detect...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="javaTestBtn">
+ <property name="text">
+ <string>Test</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -699,10 +707,6 @@
<tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop>
- <tabstop>javaPathTextBox</tabstop>
- <tabstop>javaDetectBtn</tabstop>
- <tabstop>javaBrowseBtn</tabstop>
- <tabstop>javaTestBtn</tabstop>
<tabstop>memoryGroupBox</tabstop>
<tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop>
diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp
index 0fc0c986..82de9742 100644
--- a/launcher/ui/pages/instance/ManagedPackPage.cpp
+++ b/launcher/ui/pages/instance/ManagedPackPage.cpp
@@ -23,6 +23,8 @@
#include "ui/dialogs/CustomMessageBox.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.
* ... thanks Qt.
*/
@@ -226,7 +228,7 @@ void ModrinthManagedPackPage::parseManagedPack()
QString id = m_inst->getManagedPackID();
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] {
QJsonParseError parse_error{};
@@ -376,7 +378,7 @@ void FlameManagedPackPage::parseManagedPack()
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] {
QJsonParseError parse_error{};
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index 26b96d54..0a7edb7b 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -17,7 +17,7 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "net/Download.h"
+#include "net/ApiDownload.h"
#include "net/NetJob.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(
metaEntryBase(),
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();
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index c6b087d6..39f4f346 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -20,6 +20,8 @@
#include <BuildConfig.h>
#include <Json.h>
+#include "net/ApiDownload.h"
+
namespace Atl {
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@@ -75,7 +77,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
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->start();
@@ -137,8 +139,7 @@ void ListModel::requestFailed(QString reason)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(
- APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -168,9 +169,9 @@ void ListModel::requestLogo(QString file, QString url)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
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();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 289173e1..d9f3274d 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -43,6 +43,8 @@
#include "modplatform/atlauncher/ATLShareCode.h"
#include "Application.h"
+#include "net/ApiDownload.h"
+
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
: QAbstractListModel(parent)
, m_version(version)
@@ -152,7 +154,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
void AtlOptionalModListModel::useShareCode(const QString& code) {
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
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,
this, &AtlOptionalModListModel::shareCodeSuccess);
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index c693515f..ff21d010 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -3,6 +3,8 @@
#include "Application.h"
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <Version.h>
#include <QtMath>
@@ -40,14 +42,16 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return edit;
}
return pack.description;
- } case Qt::DecorationRole: {
+ }
+ case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) {
return (m_logoMap.value(pack.logoName));
}
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon;
- } case Qt::UserRole: {
+ }
+ case Qt::UserRole: {
QVariant v;
v.setValue(pack);
return v;
@@ -68,7 +72,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return QVariant();
}
-bool ListModel::setData(const QModelIndex &index, const QVariant &value, [[maybe_unused]] int role)
+bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
{
int pos = index.row();
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
@@ -102,9 +106,9 @@ void ListModel::requestLogo(QString logo, QString url)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
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();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
@@ -128,7 +132,7 @@ void ListModel::requestLogo(QString logo, QString url)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -171,7 +175,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.arg(currentSort + 1);
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 5942883f..ead79c6c 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -42,9 +42,11 @@
#include "FlameModel.h"
#include "InstanceImportTask.h"
#include "Json.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui/dialogs/NewInstanceDialog.h"
#include "ui/widgets/ProjectItem.h"
-#include "modplatform/flame/FlameAPI.h"
+
+#include "net/ApiDownload.h"
static FlameAPI api;
@@ -132,7 +134,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
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] {
if (addonId != current.addonId) {
@@ -207,7 +209,7 @@ void FlamePage::suggestCurrent()
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
QString editedLogoName;
- editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
+ editedLogoName = "curseforge_" + current.logoName;
listModel->getLogo(current.logoName, current.logoUrl,
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
}
@@ -252,10 +254,8 @@ void FlamePage::updateUi()
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
- if(current.extraInfoLoaded) {
- if (!current.extra.issuesUrl.isEmpty()
- || !current.extra.sourceUrl.isEmpty()
- || !current.extra.wikiUrl.isEmpty()) {
+ if (current.extraInfoLoaded) {
+ if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>";
}
@@ -267,7 +267,6 @@ void FlamePage::updateUi()
text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
}
-
text += "<hr>";
text += api.getModDescription(current.addonId).toUtf8();
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
new file mode 100644
index 00000000..5c9ff63b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
@@ -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
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
new file mode 100644
index 00000000..54c49f7b
--- /dev/null
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
@@ -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
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
new file mode 100644
index 00000000..32d548b0
--- /dev/null
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
@@ -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>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
new file mode 100644
index 00000000..dc78f451
--- /dev/null
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -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 \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
new file mode 100644
index 00000000..c67aa896
--- /dev/null
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
@@ -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 \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 75b8f9d1..29b17ab7 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -37,6 +37,7 @@
#include "Application.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
+#include "net/ApiDownload.h"
#include <Version.h>
#include "StringUtils.h"
@@ -229,9 +230,9 @@ void ListModel::requestLogo(QString file)
return;
}
- MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ 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());
- 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();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
@@ -255,7 +256,7 @@ void ListModel::requestLogo(QString file)
void ListModel::getLogo(const QString& logo, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo);
}
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index 14e8e709..823439d3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -42,6 +42,8 @@
#include "minecraft/PackProfile.h"
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <QMessageBox>
namespace Modrinth {
@@ -142,7 +144,7 @@ void ModpackListModel::performPaginatedSearch()
.arg(currentSearchTerm)
.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] {
QJsonParseError parse_error_all{};
@@ -216,9 +218,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)
void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
- callback(APPLICATION->metacache()
- ->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
- ->getFullPath());
+ callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());
} else {
requestLogo(logo, logoUrl);
}
@@ -230,10 +230,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)
return;
}
- MetaEntryPtr entry =
- APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
+ 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());
- job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+ job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 5e084c03..d5c089b4 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -46,6 +46,8 @@
#include "ui/widgets/ProjectItem.h"
+#include "net/ApiDownload.h"
+
#include <QComboBox>
#include <QKeyEvent>
#include <QPushButton>
@@ -127,7 +129,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
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] {
if (id != current.id) {
@@ -176,7 +178,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
QString id = current.id;
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] {
if (id != current.id) {
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index f08eb289..5ba804db 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -38,6 +38,8 @@
#include "BuildConfig.h"
#include "Json.h"
+#include "net/ApiDownload.h"
+
#include <QIcon>
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);
searchMode = List;
}
- netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob;
jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@@ -157,7 +159,7 @@ void Technic::ListModel::searchRequestFinished()
pack.logoName = "null";
} else {
pack.logoUrl = rawURL;
- pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
+ pack.logoName = rawURL.section(QLatin1Char('/'), -1);
}
pack.broken = false;
newList.append(pack);
@@ -179,7 +181,7 @@ void Technic::ListModel::searchRequestFinished()
auto iconUrl = Json::requireString(iconObj, "url");
pack.logoUrl = iconUrl;
- pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
+ pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
} else {
pack.logoUrl = "null";
pack.logoName = "null";
@@ -254,7 +256,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
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();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 656009db..93390454 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -49,6 +49,8 @@
#include "Application.h"
#include "modplatform/technic/SolderPackManifest.h"
+#include "net/ApiDownload.h"
+
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
{
@@ -129,21 +131,18 @@ void TechnicPage::suggestCurrent()
return;
}
- QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
- model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
+ QString editedLogoName = "technic_" + current.logoName;
+ model->getLogo(current.logoName, current.logoUrl,
+ [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
- if (current.metadataLoaded)
- {
+ if (current.metadataLoaded) {
metadataLoaded();
return;
}
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
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]
{
jobPtr.reset();
@@ -249,7 +248,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
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);
diff --git a/launcher/ui/widgets/PageContainer_p.h b/launcher/ui/widgets/PageContainer_p.h
index da1a66f4..8bf26618 100644
--- a/launcher/ui/widgets/PageContainer_p.h
+++ b/launcher/ui/widgets/PageContainer_p.h
@@ -103,6 +103,8 @@ public:
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
setItemDelegate(new PageViewDelegate(this));
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ // Adjust margins when using Breeze theme
+ setProperty("_kde_side_panel_view", true);
}
virtual QSize sizeHint() const
diff --git a/launcher/ui/widgets/VariableSizedImageObject.cpp b/launcher/ui/widgets/VariableSizedImageObject.cpp
index 991b4a04..3aa3343d 100644
--- a/launcher/ui/widgets/VariableSizedImageObject.cpp
+++ b/launcher/ui/widgets/VariableSizedImageObject.cpp
@@ -26,6 +26,7 @@
#include "Application.h"
#include "net/NetJob.h"
+#include "net/ApiDownload.h"
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())));
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 source_url = source;
diff --git a/launcher/ui/widgets/WideBar.cpp b/launcher/ui/widgets/WideBar.cpp
index a77c45fe..3b57ee89 100644
--- a/launcher/ui/widgets/WideBar.cpp
+++ b/launcher/ui/widgets/WideBar.cpp
@@ -204,8 +204,10 @@ static void copyAction(QAction* from, QAction* to)
void WideBar::showVisibilityMenu(QPoint const& position)
{
- if (!m_bar_menu)
+ if (!m_bar_menu) {
m_bar_menu = std::make_unique<QMenu>(this);
+ m_bar_menu->setTearOffEnabled(true);
+ }
if (m_menu_state == MenuState::Dirty) {
for (auto* old_action : m_bar_menu->actions())