Merge remote-tracking branch 'upstream/staging' into chore/add-compiler-warnings

This commit is contained in:
Sefa Eyeoglu 2023-08-10 20:38:32 +02:00
commit ef6f9487f4
No known key found for this signature in database
GPG Key ID: E13DFD4B47127951
97 changed files with 2445 additions and 1611 deletions

View File

@ -24,6 +24,12 @@ on:
CACHIX_AUTH_TOKEN: CACHIX_AUTH_TOKEN:
description: Private token for authenticating against Cachix cache description: Private token for authenticating against Cachix cache
required: false 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: jobs:
build: build:
@ -68,7 +74,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: '' qt_arch: ''
qt_version: '6.5.1' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -80,7 +86,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: windows qt_host: windows
qt_arch: 'win64_msvc2019_arm64' qt_arch: 'win64_msvc2019_arm64'
qt_version: '6.5.1' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -90,7 +96,7 @@ jobs:
qt_ver: 6 qt_ver: 6
qt_host: mac qt_host: mac
qt_arch: '' qt_arch: ''
qt_version: '6.5.0' qt_version: '6.5.2'
qt_modules: 'qt5compat qtimageformats' qt_modules: 'qt5compat qtimageformats'
qt_tools: '' qt_tools: ''
@ -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-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/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 ${{ github.workspace }}/.github/scripts/prepare_JREs.sh
sudo apt install libopengl0 sudo apt install libopengl0
@ -387,8 +395,8 @@ jobs:
cd ${{ env.INSTALL_DIR }} cd ${{ env.INSTALL_DIR }}
if ("${{ matrix.qt_ver }}" -eq "5") 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 ${{ runner.workspace }}/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/libssl-1_1.dll -Destination libssl-1_1.dll
} }
cd ${{ github.workspace }} cd ${{ github.workspace }}
@ -466,11 +474,15 @@ jobs:
- name: Package AppImage (Linux) - name: Package AppImage (Linux)
if: runner.os == 'Linux' && matrix.qt_ver != 5 if: runner.os == 'Linux' && matrix.qt_ver != 5
shell: bash shell: bash
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: | run: |
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr 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 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 "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 chmod +x linuxdeploy-*.AppImage
@ -481,7 +493,7 @@ jobs:
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk 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/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/libssl.so.1.1 ${{ 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" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
export LD_LIBRARY_PATH 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 ./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 # UPLOAD BUILDS
## ##
@ -563,6 +600,13 @@ jobs:
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
path: 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) - name: ccache stats (Windows MinGW-w64)
if: runner.os == 'Windows' && matrix.msystem != '' if: runner.os == 'Windows' && matrix.msystem != ''
shell: msys2 {0} shell: msys2 {0}

View File

@ -43,7 +43,8 @@ jobs:
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz 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-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-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-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
@ -78,9 +79,8 @@ jobs:
- name: Create release - name: Create release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}
name: Prism Launcher ${{ env.VERSION }} name: Prism Launcher ${{ env.VERSION }}
draft: true draft: true
@ -88,7 +88,8 @@ jobs:
files: | files: |
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Portable-${{ 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-${{ env.VERSION }}.tar.gz
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip

View File

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

View File

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

View File

@ -131,17 +131,12 @@
#include "MangoHud.h" #include "MangoHud.h"
#endif #endif
#ifdef Q_OS_MAC #if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
#include "updater/MacSparkleUpdater.h" #include "updater/MacSparkleUpdater.h"
#endif #endif
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN #include "WindowsConsole.h"
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <stdio.h>
#endif #endif
#define STRINGIFY(x) #x #define STRINGIFY(x) #x
@ -168,31 +163,15 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
fflush(stderr); 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 #if defined Q_OS_WIN32
// attach the parent console // attach the parent console if stdout not already captured
if(AttachConsole(ATTACH_PARENT_PROCESS)) if (AttachWindowsConsole()) {
{
// 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);
consoleAttached = true; consoleAttached = true;
} }
#endif #endif
@ -281,7 +260,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
} }
else else
{ {
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); QDir foo;
if (DesktopServices::isSnap())
{
foo = QDir(getenv("SNAP_USER_COMMON"));
}
else
{
foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
}
dataPath = foo.absolutePath(); dataPath = foo.absolutePath();
adjustedBy = "Persistent data path"; adjustedBy = "Persistent data path";
@ -628,9 +616,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGlobalGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true); m_settings->registerSetting("RecordGameTime", true);
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
// Minecraft mods // Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModMetadataDisabled", false);
@ -704,7 +689,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
QUrl metaUrl(m_settings->get("MetaURLOverride").toString()); QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
// get rid of invalid meta urls // get rid of invalid meta urls
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https") if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https"))
m_settings->reset("MetaURLOverride"); m_settings->reset("MetaURLOverride");
} }
@ -776,7 +761,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
if(BuildConfig.UPDATER_ENABLED) if(BuildConfig.UPDATER_ENABLED)
{ {
qDebug() << "Initializing updater"; qDebug() << "Initializing updater";
#ifdef Q_OS_MAC #if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
m_updater.reset(new MacSparkleUpdater()); m_updater.reset(new MacSparkleUpdater());
#endif #endif
qDebug() << "<> Updater started."; qDebug() << "<> Updater started.";

View File

@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h net/Validator.h
net/Upload.cpp net/Upload.cpp
net/Upload.h net/Upload.h
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
net/ApiUpload.h
net/NetRequest.cpp
net/NetRequest.h
) )
# Game launch logic # Game launch logic
@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
minecraft/launch/CreateGameFolders.h minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/DirectJavaLaunch.h
minecraft/launch/ExtractNatives.cpp minecraft/launch/ExtractNatives.cpp
minecraft/launch/ExtractNatives.h minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp minecraft/launch/LauncherPartLaunch.cpp
@ -501,6 +508,11 @@ set(FTB_SOURCES
modplatform/legacy_ftb/PrivatePackManager.cpp modplatform/legacy_ftb/PrivatePackManager.cpp
modplatform/legacy_ftb/PackHelpers.h modplatform/legacy_ftb/PackHelpers.h
modplatform/import_ftb/PackInstallTask.h
modplatform/import_ftb/PackInstallTask.cpp
modplatform/import_ftb/PackHelpers.h
modplatform/import_ftb/PackHelpers.cpp
) )
set(FLAME_SOURCES set(FLAME_SOURCES
@ -563,6 +575,9 @@ set(ATLAUNCHER_SOURCES
) )
set(LINKEXE_SOURCES set(LINKEXE_SOURCES
WindowsConsole.cpp
WindowsConsole.h
filelink/FileLink.h filelink/FileLink.h
filelink/FileLink.cpp filelink/FileLink.cpp
FileSystem.h FileSystem.h
@ -668,7 +683,7 @@ set(LOGIC_SOURCES
${ATLAUNCHER_SOURCES} ${ATLAUNCHER_SOURCES}
) )
if(APPLE) if(APPLE AND Launcher_ENABLE_UPDATER)
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES}) set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
endif() endif()
@ -872,6 +887,11 @@ SET(LAUNCHER_SOURCES
ui/pages/modplatform/legacy_ftb/ListModel.h ui/pages/modplatform/legacy_ftb/ListModel.h
ui/pages/modplatform/legacy_ftb/ListModel.cpp ui/pages/modplatform/legacy_ftb/ListModel.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
ui/pages/modplatform/import_ftb/ImportFTBPage.h
ui/pages/modplatform/import_ftb/ListModel.h
ui/pages/modplatform/import_ftb/ListModel.cpp
ui/pages/modplatform/flame/FlameModel.cpp ui/pages/modplatform/flame/FlameModel.cpp
ui/pages/modplatform/flame/FlameModel.h ui/pages/modplatform/flame/FlameModel.h
ui/pages/modplatform/flame/FlamePage.cpp ui/pages/modplatform/flame/FlamePage.cpp
@ -1018,6 +1038,14 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h ui/instanceview/VisualGroup.h
) )
if(WIN32)
set(LAUNCHER_SOURCES
WindowsConsole.cpp
WindowsConsole.h
${LAUNCHER_SOURCES}
)
endif()
qt_wrap_ui(LAUNCHER_UI qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui ui/setupwizard/PasteWizardPage.ui
@ -1046,6 +1074,7 @@ qt_wrap_ui(LAUNCHER_UI
ui/pages/modplatform/ResourcePage.ui ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui ui/pages/modplatform/technic/TechnicPage.ui
@ -1108,6 +1137,7 @@ set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}" "${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}" "${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}") "${Launcher_GCC_WARNINGS}")
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
systeminfo systeminfo
@ -1147,18 +1177,24 @@ if(APPLE)
set(CMAKE_MACOSX_RPATH 1) set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/") set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
if(Launcher_ENABLE_UPDATER)
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256}) file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle) file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle") find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
add_compile_definitions(SPARKLE_ENABLED)
endif()
target_link_libraries(Launcher_logic target_link_libraries(Launcher_logic
"-framework AppKit" "-framework AppKit"
"-framework Carbon" "-framework Carbon"
"-framework Foundation" "-framework Foundation"
"-framework ApplicationServices" "-framework ApplicationServices"
) )
if(Launcher_ENABLE_UPDATER)
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK}) target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
endif() endif()
endif()
target_link_libraries(Launcher_logic) target_link_libraries(Launcher_logic)
@ -1224,7 +1260,7 @@ if(WIN32)
) )
endif() endif()
if (UNIX AND APPLE) if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
# Add Sparkle updater # Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of # It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
# the framework aren't installed # the framework aren't installed

View File

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

View File

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

View File

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

View File

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

132
launcher/WindowsConsole.cpp Normal file
View File

@ -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;
}

25
launcher/WindowsConsole.h Normal file
View File

@ -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();

View File

@ -37,11 +37,7 @@
#include <sys.h> #include <sys.h>
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN #include "WindowsConsole.h"
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#endif #endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header // 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 #if defined Q_OS_WIN32
// attach the parent console // attach the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (AttachWindowsConsole()) {
// 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);
consoleAttached = true; consoleAttached = true;
} }
#endif #endif
@ -188,7 +170,7 @@ void FileLinkApp::runLink()
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() }; FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result); m_path_results.append(result);
} else { } else {
FS::LinkResult result = { src_path, dst_path }; FS::LinkResult result = { src_path, dst_path, "", 0};
m_path_results.append(result); m_path_results.append(result);
} }
} }
@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
fclose(stdout); fclose(stdout);
fclose(stdin); fclose(stdin);
fclose(stderr); fclose(stderr);
FreeConsole();
} }
#endif #endif
} }

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -35,13 +36,14 @@
#include "IconList.h" #include "IconList.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
#include <QUrl>
#include <QFileSystemWatcher>
#include <QSet>
#include <QDebug> #include <QDebug>
#include <QEventLoop>
#include <QFileSystemWatcher>
#include <QMap>
#include <QMimeData>
#include <QSet>
#include <QUrl>
#include "icons/IconUtils.h"
#define MAX_SIZE 1024 #define MAX_SIZE 1024
@ -50,17 +52,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
QSet<QString> builtinNames; QSet<QString> builtinNames;
// add builtin icons // add builtin icons
for(auto & builtinPath: builtinPaths) for (auto& builtinPath : builtinPaths) {
{
QDir instance_icons(builtinPath); QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); 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()); builtinNames.insert(file_info.completeBaseName());
} }
} }
for(auto & builtinName : builtinNames) for (auto& builtinName : builtinNames) {
{
addThemeIcon(builtinName); addThemeIcon(builtinName);
} }
@ -78,17 +77,14 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
void IconList::sortIconList() void IconList::sortIconList()
{ {
qDebug() << "Sorting icon list..."; qDebug() << "Sorting icon list...";
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
return a.m_key.localeAwareCompare(b.m_key) < 0;
});
reindex(); reindex();
} }
void IconList::directoryChanged(const QString& path) void IconList::directoryChanged(const QString& path)
{ {
QDir new_dir(path); QDir new_dir(path);
if(m_dir.absolutePath() != new_dir.absolutePath()) if (m_dir.absolutePath() != new_dir.absolutePath()) {
{
m_dir.setPath(path); m_dir.setPath(path);
m_dir.refresh(); m_dir.refresh();
if (is_watching) if (is_watching)
@ -100,8 +96,7 @@ void IconList::directoryChanged(const QString &path)
return; return;
m_dir.refresh(); m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name); auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
for (auto it = new_list.begin(); it != new_list.end(); it++) for (auto it = new_list.begin(); it != new_list.end(); it++) {
{
QString& foo = (*it); QString& foo = (*it);
foo = m_dir.filePath(foo); foo = m_dir.filePath(foo);
} }
@ -111,8 +106,7 @@ void IconList::directoryChanged(const QString &path)
auto new_set = new_list.toSet(); auto new_set = new_list.toSet();
#endif #endif
QList<QString> current_list; QList<QString> current_list;
for (auto &it : icons) for (auto& it : icons) {
{
if (!it.has(IconType::FileBased)) if (!it.has(IconType::FileBased))
continue; continue;
current_list.push_back(it.m_images[IconType::FileBased].filename); 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; QSet<QString> to_add = new_set;
to_add -= current_set; to_add -= current_set;
for (auto remove : to_remove) for (auto remove : to_remove) {
{
qDebug() << "Removing " << remove; qDebug() << "Removing " << remove;
QFileInfo rmfile(remove); QFileInfo rmfile(remove);
QString key = rmfile.completeBaseName(); QString key = rmfile.completeBaseName();
QString suffix = rmfile.suffix(); 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 // 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(); key = rmfile.fileName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
continue; continue;
icons[idx].remove(IconType::FileBased); icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted) if (icons[idx].type() == IconType::ToBeDeleted) {
{
beginRemoveRows(QModelIndex(), idx, idx); beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx); icons.remove(idx);
reindex(); reindex();
endRemoveRows(); endRemoveRows();
} } else {
else
{
dataChanged(index(idx), index(idx)); dataChanged(index(idx), index(idx));
} }
m_watcher->removePath(remove); m_watcher->removePath(remove);
emit iconUpdated(key); emit iconUpdated(key);
} }
for (auto add : to_add) for (auto add : to_add) {
{
qDebug() << "Adding " << add; qDebug() << "Adding " << add;
QFileInfo addfile(add); QFileInfo addfile(add);
@ -168,11 +157,10 @@ void IconList::directoryChanged(const QString &path)
QString suffix = addfile.suffix(); 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 // 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(); key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
{
m_watcher->addPath(add); m_watcher->addPath(add);
emit iconUpdated(key); emit iconUpdated(key);
} }
@ -213,12 +201,9 @@ void IconList::startWatching()
auto abs_path = m_dir.absolutePath(); auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path); FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path); is_watching = m_watcher->addPath(abs_path);
if (is_watching) if (is_watching) {
{
qDebug() << "Started watching " << abs_path; qDebug() << "Started watching " << abs_path;
} } else {
else
{
qDebug() << "Failed to start watching " << abs_path; qDebug() << "Failed to start watching " << abs_path;
} }
} }
@ -241,7 +226,11 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction; 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) if (action == Qt::IgnoreAction)
return true; return true;
@ -250,12 +239,10 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
return false; return false;
// files dropped from outside? // files dropped from outside?
if (data->hasUrls()) if (data->hasUrls()) {
{
auto urls = data->urls(); auto urls = data->urls();
QStringList iconFiles; QStringList iconFiles;
for (auto url : urls) for (auto url : urls) {
{
// only local files may be dropped... // only local files may be dropped...
if (!url.isLocalFile()) if (!url.isLocalFile())
continue; continue;
@ -270,9 +257,6 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
Qt::ItemFlags IconList::flags(const QModelIndex& index) const Qt::ItemFlags IconList::flags(const QModelIndex& index) const
{ {
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags; return Qt::ItemIsDropEnabled | defaultFlags;
} }
@ -286,8 +270,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
if (row < 0 || row >= icons.size()) if (row < 0 || row >= icons.size())
return QVariant(); return QVariant();
switch (role) switch (role) {
{
case Qt::DecorationRole: case Qt::DecorationRole:
return icons[row].icon(); return icons[row].icon();
case Qt::DisplayRole: case Qt::DisplayRole:
@ -307,19 +290,7 @@ int IconList::rowCount(const QModelIndex &parent) const
void IconList::installIcons(const QStringList& iconFiles) void IconList::installIcons(const QStringList& iconFiles)
{ {
for (QString file : iconFiles) for (QString file : iconFiles)
{ installIcon(file, {});
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;
}
} }
void IconList::installIcon(const QString& file, const QString& name) void IconList::installIcon(const QString& file, const QString& name)
@ -328,19 +299,17 @@ void IconList::installIcon(const QString &file, const QString &name)
if (!fileinfo.isReadable() || !fileinfo.isFile()) if (!fileinfo.isReadable() || !fileinfo.isFile())
return; 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); QFile::copy(file, target);
} }
bool IconList::iconFileExists(const QString& key) const bool IconList::iconFileExists(const QString& key) const
{ {
auto iconEntry = icon(key); auto iconEntry = icon(key);
if(!iconEntry) return iconEntry && iconEntry->has(IconType::FileBased);
{
return false;
}
return iconEntry->has(IconType::FileBased);
} }
const MMCIcon* IconList::icon(const QString& key) const const MMCIcon* IconList::icon(const QString& key) const
@ -353,32 +322,23 @@ const MMCIcon *IconList::icon(const QString &key) const
bool IconList::deleteIcon(const QString& key) bool IconList::deleteIcon(const QString& key)
{ {
if (!iconFileExists(key)) return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
return false;
return QFile::remove(icon(key)->getFilePath());
} }
bool IconList::trashIcon(const QString& key) bool IconList::trashIcon(const QString& key)
{ {
if (!iconFileExists(key)) return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
return false;
return FS::trash(icon(key)->getFilePath(), nullptr);
} }
bool IconList::addThemeIcon(const QString& key) bool IconList::addThemeIcon(const QString& key)
{ {
auto iter = name_index.find(key); auto iter = name_index.find(key);
if (iter != name_index.end()) if (iter != name_index.end()) {
{
auto& oldOne = icons[*iter]; auto& oldOne = icons[*iter];
oldOne.replace(Builtin, key); oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
else
{
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), icons.size(), icons.size());
{ {
@ -392,7 +352,6 @@ bool IconList::addThemeIcon(const QString& key)
endInsertRows(); endInsertRows();
return true; 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)
{ {
@ -401,15 +360,12 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
if (icon.isNull()) if (icon.isNull())
return false; return false;
auto iter = name_index.find(key); auto iter = name_index.find(key);
if (iter != name_index.end()) if (iter != name_index.end()) {
{
auto& oldOne = icons[*iter]; auto& oldOne = icons[*iter];
oldOne.replace(type, icon, path); oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
else
{
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), icons.size(), icons.size());
{ {
@ -423,7 +379,6 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
endInsertRows(); endInsertRows();
return true; 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
{ {
@ -432,13 +387,11 @@ void IconList::saveIcon(const QString &key, const QString &path, const char * fo
pixmap.save(path, format); pixmap.save(path, format);
} }
void IconList::reindex() void IconList::reindex()
{ {
name_index.clear(); name_index.clear();
int i = 0; int i = 0;
for (auto &iter : icons) for (auto& iter : icons) {
{
name_index[iter.m_key] = i; name_index[iter.m_key] = i;
i++; i++;
} }
@ -472,5 +425,3 @@ QString IconList::getDirectory() const
{ {
return m_dir.absolutePath(); return m_dir.absolutePath();
} }
//#include "IconList.moc"

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,13 +32,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include <QMutex>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QFile>
#include <QDir> #include <QDir>
#include <QFile>
#include <QMutex>
#include <QtGui/QIcon> #include <QtGui/QIcon>
#include <memory> #include <memory>
@ -29,8 +48,7 @@
class QFileSystemWatcher; class QFileSystemWatcher;
class IconList : public QAbstractListModel class IconList : public QAbstractListModel {
{
Q_OBJECT Q_OBJECT
public: public:
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0); explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
@ -80,6 +98,7 @@ public slots:
protected slots: protected slots:
void fileChanged(const QString& path); void fileChanged(const QString& path);
void SettingChanged(const Setting& setting, QVariant value); void SettingChanged(const Setting& setting, QVariant value);
private: private:
shared_qobject_ptr<QFileSystemWatcher> m_watcher; shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching; bool is_watching;

View File

@ -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 "IconUtils.h"
#include "FileSystem.h"
#include <QDirIterator> #include <QDirIterator>
#include "FileSystem.h"
#include <array>
namespace { namespace {
std::array<const char *, 6> validIconExtensions = {{ static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
"svg",
"png",
"ico",
"gif",
"jpg",
"jpeg"
}};
} }
namespace IconUtils { namespace IconUtils {
QString findBestIconIn(const QString &folder, const QString & iconKey) { QString findBestIconIn(const QString& folder, const QString& iconKey)
size_t best_found = validIconExtensions.size(); {
QString best_filename; QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags); QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
@ -27,36 +53,20 @@ QString findBestIconIn(const QString &folder, const QString & iconKey) {
it.next(); it.next();
auto fileInfo = it.fileInfo(); auto fileInfo = it.fileInfo();
if(fileInfo.completeBaseName() != iconKey) if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
continue; return fileInfo.absoluteFilePath();
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();
} }
} return {};
}
return FS::PathCombine(folder, best_filename);
} }
QString getIconFilter() { QString getIconFilter()
QString out; {
QTextStream stream(&out); return "(*." + validIconExtensions.join(" *.") + ")";
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;
} }
bool isIconSuffix(QString suffix)
{
return validIconExtensions.contains(suffix);
} }
} // namespace IconUtils

View File

@ -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 #pragma once
#include <QString> #include <QString>
@ -10,4 +45,5 @@ QString findBestIconIn(const QString &folder, const QString & iconKey);
// Get icon file type filter for file browser dialogs // Get icon file type filter for file browser dialogs
QString getIconFilter(); QString getIconFilter();
} bool isIconSuffix(QString suffix);
} // namespace IconUtils

View File

@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
/* /*
* PolyMC - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -40,8 +41,7 @@
IconType operator--(IconType& t, int) IconType operator--(IconType& t, int)
{ {
IconType temp = t; IconType temp = t;
switch (t) switch (t) {
{
case IconType::Builtin: case IconType::Builtin:
t = IconType::ToBeDeleted; t = IconType::ToBeDeleted;
break; break;
@ -52,8 +52,7 @@ IconType operator--(IconType &t, int)
t = IconType::Transient; t = IconType::Transient;
break; break;
default: default:
{ break;
}
} }
return temp; return temp;
} }
@ -90,10 +89,8 @@ void MMCIcon::remove(IconType rm_type)
{ {
m_images[rm_type].filename = QString(); m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon(); m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) {
{ if (m_images[iter].present()) {
if (m_images[iter].present())
{
m_current_type = iter; m_current_type = iter;
return; return;
} }
@ -103,8 +100,7 @@ void MMCIcon::remove(IconType rm_type)
void MMCIcon::replace(IconType new_type, QIcon icon, QString path) 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_current_type = new_type;
} }
m_images[new_type].icon = icon; 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) 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_current_type = new_type;
} }
m_images[new_type].icon = QIcon(); m_images[new_type].icon = QIcon();
@ -131,7 +126,6 @@ QString MMCIcon::getFilePath() const
return m_images[m_current_type].filename; return m_images[m_current_type].filename;
} }
bool MMCIcon::isBuiltIn() const bool MMCIcon::isBuiltIn() const
{ {
return m_current_type == IconType::Builtin; return m_current_type == IconType::Builtin;

View File

@ -1,4 +1,24 @@
/* Copyright 2013-2021 MultiMC Contributors // 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,34 +32,21 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#pragma once #pragma once
#include <QString>
#include <QDateTime> #include <QDateTime>
#include <QIcon> #include <QIcon>
#include <QString>
enum IconType : unsigned enum IconType : unsigned { Builtin, Transient, FileBased, ICONS_TOTAL, ToBeDeleted };
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
struct MMCImage struct MMCImage {
{
QIcon icon; QIcon icon;
QString key; QString key;
QString filename; QString filename;
bool present() const bool present() const { return !icon.isNull() || !key.isEmpty(); }
{
return !icon.isNull() || !key.isEmpty();
}
}; };
struct MMCIcon struct MMCIcon {
{
QString m_key; QString m_key;
QString m_name; QString m_name;
MMCImage m_images[ICONS_TOTAL]; MMCImage m_images[ICONS_TOTAL];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,166 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DirectJavaLaunch.h"
#include <QStandardPaths>
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <FileSystem.h>
#include <Commandline.h>
#include "Application.h"
#ifdef Q_OS_LINUX
#include "gamemode_client.h"
#endif
DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)
{
connect(&m_process, &LoggedProcess::log, this, &DirectJavaLaunch::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &DirectJavaLaunch::on_state);
}
void DirectJavaLaunch::executeTask()
{
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
QStringList args = minecraftInstance->javaArguments();
args.append("-Djava.library.path=" + minecraftInstance->getNativePath());
auto classPathEntries = minecraftInstance->getClassPath();
args.append("-cp");
QString classpath;
#ifdef Q_OS_WIN32
classpath = classPathEntries.join(';');
#else
classpath = classPathEntries.join(':');
#endif
args.append(classpath);
args.append(minecraftInstance->getMainClass());
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString());
m_process.setProcessEnvironment(instance->createLaunchEnvironment());
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin);
args.append(mcArgs);
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
{
auto wrapperArgs = Commandline::splitArgs(wrapperCommandStr);
auto wrapperCommand = wrapperArgs.takeFirst();
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
emitFailed(tr(reason).arg(wrapperCommand));
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::Launcher);
args.prepend(javaPath);
m_process.start(wrapperCommand, wrapperArgs + args);
}
else
{
m_process.start(javaPath, args);
}
#ifdef Q_OS_LINUX
if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode)
{
auto pid = m_process.processId();
if (pid)
{
gamemode_request_start_for(pid);
}
}
#endif
}
void DirectJavaLaunch::on_state(LoggedProcess::State state)
{
switch(state)
{
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instance can't start
const char *reason = QT_TR_NOOP("Could not launch Minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(tr(reason));
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
{
m_parent->setPid(-1);
emitFailed(tr("Game crashed."));
return;
}
case LoggedProcess::Finished:
{
m_parent->setPid(-1);
// if the exit code wasn't 0, report this as a crash
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
emitFailed(tr("Game crashed."));
return;
}
//FIXME: make this work again
// m_postlaunchprocess.processEnvironment().insert("INST_EXITCODE", QString(exitCode));
// run post-exit
emitSucceeded();
break;
}
case LoggedProcess::Running:
emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::Launcher);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
break;
default:
break;
}
}
void DirectJavaLaunch::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
void DirectJavaLaunch::proceed()
{
// nil
}
bool DirectJavaLaunch::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@ -1,58 +0,0 @@
/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
#include "MinecraftServerTarget.h"
class DirectJavaLaunch: public LaunchStep
{
Q_OBJECT
public:
explicit DirectJavaLaunch(LaunchTask *parent);
virtual ~DirectJavaLaunch() {};
virtual void executeTask();
virtual bool abort();
virtual void proceed();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
void setAuthSession(AuthSessionPtr session)
{
m_session = session;
}
void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
{
m_serverToJoin = std::move(serverToJoin);
}
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
MinecraftServerTargetPtr m_serverToJoin;
};

View File

@ -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_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_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_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 QVariant ModFolderModel::data(const QModelIndex &index, int role) const

View File

@ -552,6 +552,8 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
menu->addSeparator()->setText(tr("Show / Hide Columns")); menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) { 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); auto act = new QAction(menu);
setupHeaderAction(act, col); setupHeaderAction(act, col);

View File

@ -202,6 +202,7 @@ class ResourceFolderModel : public QAbstractListModel {
QStringList m_column_names = {"Enable", "Name", "Last Modified"}; QStringList m_column_names = {"Enable", "Name", "Last Modified"};
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("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<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
QList<bool> m_columnsHideable = { false, false, true };
QDir m_dir; QDir m_dir;
BaseInstance* m_instance; BaseInstance* m_instance;

View File

@ -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_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_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_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 QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const

View File

@ -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_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_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents}; m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
m_columnsHideable = { false, true, false, true };
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,87 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "modplatform/import_ftb/PackHelpers.h"
#include <QIcon>
#include <QString>
#include <QVariant>
#include "FileSystem.h"
#include "Json.h"
namespace FTBImportAPP {
Modpack parseDirectory(QString path)
{
Modpack modpack{ path };
auto instanceFile = QFileInfo(FS::PathCombine(path, "instance.json"));
if (!instanceFile.exists() || !instanceFile.isFile())
return {};
try {
auto doc = Json::requireDocument(instanceFile.absoluteFilePath(), "FTB_APP instance JSON file");
const auto root = doc.object();
modpack.uuid = Json::requireString(root, "uuid", "uuid");
modpack.id = Json::requireInteger(root, "id", "id");
modpack.versionId = Json::requireInteger(root, "versionId", "versionId");
modpack.name = Json::requireString(root, "name", "name");
modpack.version = Json::requireString(root, "version", "version");
modpack.mcVersion = Json::requireString(root, "mcVersion", "mcVersion");
modpack.jvmArgs = Json::ensureVariant(root, "jvmArgs", {}, "jvmArgs");
} catch (const Exception& e) {
qDebug() << "Couldn't load ftb instance json: " << e.cause();
return {};
}
auto versionsFile = QFileInfo(FS::PathCombine(path, "version.json"));
if (!versionsFile.exists() || !versionsFile.isFile())
return {};
try {
auto doc = Json::requireDocument(versionsFile.absoluteFilePath(), "FTB_APP version JSON file");
const auto root = doc.object();
auto targets = Json::requireArray(root, "targets", "targets");
for (auto target : targets) {
auto obj = Json::requireObject(target, "target");
auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version");
if (name == "forge") {
modpack.loaderType = ResourceAPI::Forge;
modpack.version = version;
break;
} else if (name == "fabric") {
modpack.loaderType = ResourceAPI::Fabric;
modpack.version = version;
break;
} else if (name == "quilt") {
modpack.loaderType = ResourceAPI::Quilt;
modpack.version = version;
break;
}
}
} catch (const Exception& e) {
qDebug() << "Couldn't load ftb version json: " << e.cause();
return {};
}
auto iconFile = QFileInfo(FS::PathCombine(path, "folder.jpg"));
if (iconFile.exists() && iconFile.isFile()) {
modpack.icon = QIcon(iconFile.absoluteFilePath());
}
return modpack;
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QIcon>
#include <QList>
#include <QMetaType>
#include <QString>
#include <QVariant>
#include "modplatform/ResourceAPI.h"
namespace FTBImportAPP {
struct Modpack {
QString path;
// json data
QString uuid;
int id;
int versionId;
QString name;
QString version;
QString mcVersion;
// not needed for instance creation
QVariant jvmArgs;
std::optional<ResourceAPI::ModLoaderType> loaderType;
QString loaderVersion;
QIcon icon;
};
typedef QList<Modpack> ModpackList;
Modpack parseDirectory(QString path);
} // namespace FTBImportAPP
// We need it for the proxy model
Q_DECLARE_METATYPE(FTBImportAPP::Modpack)

View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "PackInstallTask.h"
#include <QtConcurrent>
#include "BaseInstance.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/import_ftb/PackHelpers.h"
#include "settings/INISettingsObject.h"
namespace FTBImportAPP {
void PackInstallTask::executeTask()
{
setStatus(tr("Copying files..."));
setAbortable(false);
progress(1, 2);
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
FS::copy folderCopy(m_pack.path, FS::PathCombine(m_stagingPath, ".minecraft"));
folderCopy.followSymlinks(true);
return folderCopy();
});
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &PackInstallTask::copySettings);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &PackInstallTask::emitAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
}
void PackInstallTask::copySettings()
{
setStatus(tr("Copying settings..."));
progress(2, 2);
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
instanceSettings->suspendSave();
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
instance.settings()->set("InstanceType", "OneSix");
if (m_pack.jvmArgs.isValid() && !m_pack.jvmArgs.toString().isEmpty()) {
instance.settings()->set("OverrideJavaArgs", true);
instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString());
}
auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
auto modloader = m_pack.loaderType;
if (modloader.has_value())
switch (modloader.value()) {
case ResourceAPI::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break;
}
case ResourceAPI::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break;
}
case ResourceAPI::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break;
}
case ResourceAPI::Cauldron:
break;
case ResourceAPI::LiteLoader:
break;
}
components->saveNow();
instance.setName(name());
if (m_instIcon == "default")
m_instIcon = "ftb_logo";
instance.setIconKey(m_instIcon);
instanceSettings->resumeSave();
emitSucceeded();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFuture>
#include <QFutureWatcher>
#include "InstanceTask.h"
#include "PackHelpers.h"
namespace FTBImportAPP {
class PackInstallTask : public InstanceTask {
Q_OBJECT
public:
explicit PackInstallTask(const Modpack& pack) : m_pack(pack) {}
virtual ~PackInstallTask() = default;
protected:
virtual void executeTask() override;
private slots:
void copySettings();
private:
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
const Modpack m_pack;
};
} // namespace FTBImportAPP

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "net/ApiDownload.h"
#include "ByteArraySink.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net {
auto ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("CACHE:") + url.toString());
dl->m_options = options;
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
dl->m_sink.reset(cachedNode);
return dl;
}
auto ApiDownload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("BYTES:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new ByteArraySink(output));
return dl;
}
auto ApiDownload::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
{
auto dl = makeShared<ApiDownload>();
dl->m_url = url;
dl->setObjectName(QString("FILE:") + url.toString());
dl->m_options = options;
dl->m_sink.reset(new FileSink(path));
return dl;
}
void ApiDownload::init()
{
qDebug() << "Setting up api download";
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "ApiHeaderProxy.h"
#include "Download.h"
namespace Net {
class ApiDownload : public Download {
public:
virtual ~ApiDownload() = default;
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
void init() override;
};
} // namespace Net

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "Application.h"
#include "BuildConfig.h"
#include "net/HeaderProxy.h"
namespace Net {
class ApiHeaderProxy : public HeaderProxy {
public:
ApiHeaderProxy() : HeaderProxy() {}
virtual ~ApiHeaderProxy() = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
{
QList<HeaderPair> hdrs;
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull())
hdrs.append({ "Authorization", token.toUtf8() });
}
return hdrs;
};
};
} // namespace Net

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "net/ApiUpload.h"
#include "ByteArraySink.h"
#include "ChecksumValidator.h"
#include "MetaCacheSink.h"
#include "net/NetAction.h"
namespace Net {
Upload::Ptr ApiUpload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
{
auto up = makeShared<ApiUpload>();
up->m_url = std::move(url);
up->m_sink.reset(new ByteArraySink(output));
up->m_post_data = std::move(m_post_data);
return up;
}
void ApiUpload::init()
{
qDebug() << "Setting up api upload";
auto api_headers = new ApiHeaderProxy();
addHeaderProxy(api_headers);
}
} // namespace Net

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

@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "ApiHeaderProxy.h"
#include "Upload.h"
namespace Net {
class ApiUpload : public Upload {
public:
virtual ~ApiUpload() = default;
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
void init() override;
};
} // namespace Net

View File

@ -42,8 +42,6 @@ namespace Net {
/* /*
* Sink object for downloads that uses an external QByteArray it doesn't own as a target. * Sink object for downloads that uses an external QByteArray it doesn't own as a target.
* FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
* causing a segmentation fault.
*/ */
class ByteArraySink : public Sink { class ByteArraySink : public Sink {
public: public:

View File

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

View File

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

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <QDebug>
#include <QNetworkRequest>
namespace Net {
struct HeaderPair {
QByteArray headerName;
QByteArray headerValue;
};
class HeaderProxy {
public:
HeaderProxy(){};
virtual ~HeaderProxy(){};
public:
virtual QList<HeaderPair> headers(const QNetworkRequest& request) const = 0;
public:
void writeHeaders(QNetworkRequest& request)
{
for (auto header : headers(request)) {
request.setRawHeader(header.headerName, header.headerValue);
}
}
};
} // namespace Net

View File

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

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

@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "NetRequest.h"
#include <QUrl>
#include <QDateTime>
#include <QFileInfo>
#include <memory>
#if defined(LAUNCHER_APPLICATION)
#include "Application.h"
#endif
#include "net/NetAction.h"
#include "MMCTime.h"
#include "StringUtils.h"
namespace Net {
void NetRequest::addValidator(Validator* v)
{
m_sink->addValidator(v);
}
void NetRequest::executeTask()
{
init();
setStatus(tr("Requesting %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
if (getState() == Task::State::AbortedByUser) {
qCWarning(logCat) << getUid().toString() << "Attempt to start an aborted Request:" << m_url.toString();
emitAborted();
return;
}
QNetworkRequest request(m_url);
m_state = m_sink->init(request);
switch (m_state) {
case State::Succeeded:
qCDebug(logCat) << getUid().toString() << "Request cache hit " << m_url.toString();
emit succeeded();
emit finished();
return;
case State::Running:
qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
break;
case State::Inactive:
case State::Failed:
emitFailed();
return;
case State::AbortedByUser:
emitAborted();
return;
}
#if defined(LAUNCHER_APPLICATION)
auto user_agent = APPLICATION->getUserAgent();
#else
auto user_agent = BuildConfig.USER_AGENT;
#endif
request.setHeader(QNetworkRequest::UserAgentHeader, user_agent.toUtf8());
for (auto& header_proxy : m_headerProxies) {
header_proxy->writeHeaders(request);
}
// TODO remove duplication
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
request.setTransferTimeout();
#endif
m_last_progress_time = m_clock.now();
m_last_progress_bytes = 0;
QNetworkReply* rep = getReply(request);
m_reply.reset(rep);
connect(rep, &QNetworkReply::downloadProgress, this, &NetRequest::downloadProgress);
connect(rep, &QNetworkReply::finished, this, &NetRequest::downloadFinished);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
connect(rep, &QNetworkReply::errorOccurred, this, &NetRequest::downloadError);
#else
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &NetRequest::downloadError);
#endif
connect(rep, &QNetworkReply::sslErrors, this, &NetRequest::sslErrors);
connect(rep, &QNetworkReply::readyRead, this, &NetRequest::downloadReadyRead);
}
void NetRequest::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
auto now = m_clock.now();
auto elapsed = now - m_last_progress_time;
// use milliseconds for speed precision
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
auto remaining_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
QString dl_progress =
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
QString dl_speed_str;
if (elapsed_ms.count() > 0) {
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaining_time_s) : tr("unknown");
//: Download speed, in bytes per second (remaining download time in parenthesis)
dl_speed_str = tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
} else {
//: Download speed at 0 bytes per second
dl_speed_str = tr("0 B/s");
}
setDetails(dl_progress + "\n" + dl_speed_str);
setProgress(bytesReceived, bytesTotal);
}
void NetRequest::downloadError(QNetworkReply::NetworkError error)
{
if (error == QNetworkReply::OperationCanceledError) {
qCCritical(logCat) << getUid().toString() << "Aborted " << m_url.toString();
m_state = State::Failed;
} else {
if (m_options & Option::AcceptLocalFiles) {
if (m_sink->hasLocalData()) {
m_state = State::Succeeded;
return;
}
}
// error happened during download.
qCCritical(logCat) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
m_state = State::Failed;
}
}
void NetRequest::sslErrors(const QList<QSslError>& errors)
{
int i = 1;
for (auto error : errors) {
qCCritical(logCat) << getUid().toString() << "Request" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
auto cert = error.certificate();
qCCritical(logCat) << getUid().toString() << "Certificate in question:\n" << cert.toText();
i++;
}
}
auto NetRequest::handleRedirect() -> bool
{
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
if (!redirect.isValid()) {
if (!m_reply->hasRawHeader("Location")) {
// no redirect -> it's fine to continue
return false;
}
// there is a Location header, but it's not correct. we need to apply some workarounds...
QByteArray redirectBA = m_reply->rawHeader("Location");
if (redirectBA.size() == 0) {
// empty, yet present redirect header? WTF?
return false;
}
QString redirectStr = QString::fromUtf8(redirectBA);
if (redirectStr.startsWith("//")) {
/*
* IF the URL begins with //, we need to insert the URL scheme.
* See: https://bugreports.qt.io/browse/QTBUG-41061
* See: http://tools.ietf.org/html/rfc3986#section-4.2
*/
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
} else if (redirectStr.startsWith("/")) {
/*
* IF the URL begins with /, we need to process it as a relative URL
*/
auto url = m_reply->url();
url.setPath(redirectStr, QUrl::TolerantMode);
redirectStr = url.toString();
}
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
* FIXME: report Qt bug for this
*/
redirect = QUrl(redirectStr, QUrl::TolerantMode);
if (!redirect.isValid()) {
qCWarning(logCat) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
downloadError(QNetworkReply::ProtocolFailure);
return false;
}
qCDebug(logCat) << getUid().toString() << "Fixed location header:" << redirect;
} else {
qCDebug(logCat) << getUid().toString() << "Location header:" << redirect;
}
m_url = QUrl(redirect.toString());
qCDebug(logCat) << getUid().toString() << "Following redirect to " << m_url.toString();
startAction(m_network);
return true;
}
void NetRequest::downloadFinished()
{
// handle HTTP redirection first
if (handleRedirect()) {
qCDebug(logCat) << getUid().toString() << "Request redirected:" << m_url.toString();
return;
}
// if the download failed before this point ...
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
{
qCDebug(logCat) << getUid().toString() << "Request failed but we are allowed to proceed:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit succeeded();
emit finished();
return;
} else if (m_state == State::Failed) {
qCDebug(logCat) << getUid().toString() << "Request failed in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
emit finished();
return;
} else if (m_state == State::AbortedByUser) {
qCDebug(logCat) << getUid().toString() << "Request aborted in previous step:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit aborted();
emit finished();
return;
}
// make sure we got all the remaining data, if any
auto data = m_reply->readAll();
if (data.size()) {
qCDebug(logCat) << getUid().toString() << "Writing extra" << data.size() << "bytes";
m_state = m_sink->write(data);
if (m_state != State::Succeeded) {
qCDebug(logCat) << getUid().toString() << "Request failed to write:" << m_url.toString();
m_sink->abort();
emit failed("");
emit finished();
return;
}
}
// otherwise, finalize the whole graph
m_state = m_sink->finalize(*m_reply.get());
if (m_state != State::Succeeded) {
qCDebug(logCat) << getUid().toString() << "Request failed to finalize:" << m_url.toString();
m_sink->abort();
m_reply.reset();
emit failed("");
emit finished();
return;
}
m_reply.reset();
qCDebug(logCat) << getUid().toString() << "Request succeeded:" << m_url.toString();
emit succeeded();
emit finished();
}
void NetRequest::downloadReadyRead()
{
if (m_state == State::Running) {
auto data = m_reply->readAll();
m_state = m_sink->write(data);
if (m_state == State::Failed) {
qCCritical(logCat) << getUid().toString() << "Failed to process response chunk";
}
// qDebug() << "Request" << m_url.toString() << "gained" << data.size() << "bytes";
} else {
qCCritical(logCat) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
}
}
auto NetRequest::abort() -> bool
{
m_state = State::AbortedByUser;
if (m_reply) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
disconnect(m_reply.get(), &QNetworkReply::errorOccurred, nullptr, nullptr);
#else
disconnect(m_reply.get(), QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), nullptr, nullptr);
#endif
m_reply->abort();
}
return true;
}
} // namespace Net

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

@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <qloggingcategory.h>
#include <chrono>
#include "NetAction.h"
#include "Sink.h"
#include "Validator.h"
#include "QObjectPtr.h"
#include "net/Logging.h"
namespace Net {
class NetRequest : public NetAction {
Q_OBJECT
protected:
explicit NetRequest() : NetAction(){};
public:
using Ptr = shared_qobject_ptr<class NetRequest>;
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
Q_DECLARE_FLAGS(Options, Option)
public:
~NetRequest() override = default;
void init() override{};
public:
void addValidator(Validator* v);
auto abort() -> bool override;
auto canAbort() const -> bool override { return true; }
private:
auto handleRedirect() -> bool;
virtual QNetworkReply* getReply(QNetworkRequest&) = 0;
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
void sslErrors(const QList<QSslError>& errors) override;
void downloadFinished() override;
void downloadReadyRead() override;
public slots:
void executeTask() override;
protected:
std::unique_ptr<Sink> m_sink;
Options m_options;
typedef const QLoggingCategory& (*logCatFunc)();
logCatFunc logCat = taskUploadLogC;
std::chrono::steady_clock m_clock;
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
qint64 m_last_progress_bytes;
};
} // namespace Net
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::NetRequest::Options)

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "net/HeaderProxy.h"
namespace Net {
class RawHeaderProxy : public HeaderProxy {
public:
RawHeaderProxy() : HeaderProxy() {}
virtual ~RawHeaderProxy() = default;
public:
virtual QList<HeaderPair> headers(const QNetworkRequest&) const override { return m_headers; };
void addHeader(const HeaderPair& header) { m_headers.append(header); }
void addHeader(const QByteArray& headerName, const QByteArray& headerValue) { m_headers.append({ headerName, headerValue }); }
void addHeaders(const QList<HeaderPair>& headers) { m_headers.append(headers); }
private:
QList<HeaderPair> m_headers;
};
} // namespace Net

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,36 +33,35 @@
* limitations under the License. * limitations under the License.
*/ */
#include "Application.h"
#include "NewInstanceDialog.h" #include "NewInstanceDialog.h"
#include "Application.h"
#include "ui/pages/modplatform/import_ftb/ImportFTBPage.h"
#include "ui_NewInstanceDialog.h" #include "ui_NewInstanceDialog.h"
#include <BaseVersion.h> #include <BaseVersion.h>
#include <InstanceList.h>
#include <icons/IconList.h> #include <icons/IconList.h>
#include <tasks/Task.h> #include <tasks/Task.h>
#include <InstanceList.h>
#include "VersionSelectDialog.h"
#include "ProgressDialog.h"
#include "IconPickerDialog.h" #include "IconPickerDialog.h"
#include "ProgressDialog.h"
#include "VersionSelectDialog.h"
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QLayout> #include <QLayout>
#include <QPushButton> #include <QPushButton>
#include <QFileDialog>
#include <QValidator> #include <QValidator>
#include <QDialogButtonBox>
#include <utility> #include <utility>
#include "ui/widgets/PageContainer.h"
#include "ui/pages/modplatform/CustomPage.h" #include "ui/pages/modplatform/CustomPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/ImportPage.h" #include "ui/pages/modplatform/ImportPage.h"
#include "ui/pages/modplatform/atlauncher/AtlPage.h"
#include "ui/pages/modplatform/flame/FlamePage.h"
#include "ui/pages/modplatform/legacy_ftb/Page.h"
#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h"
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent) NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog) : QDialog(parent), ui(new Ui::NewInstanceDialog)
@ -168,6 +167,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame) if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(new FlamePage(this)); pages.append(new FlamePage(this));
pages.append(new LegacyFTB::Page(this)); pages.append(new LegacyFTB::Page(this));
pages.append(new FTBImportAPP::ImportFTBPage(this));
pages.append(new ModrinthPage(this)); pages.append(new ModrinthPage(this));
pages.append(new TechnicPage(this)); pages.append(new TechnicPage(this));
@ -288,8 +288,7 @@ void NewInstanceDialog::on_iconButton_clicked()
IconPickerDialog dlg(this); IconPickerDialog dlg(this);
dlg.execWithSelection(InstIconKey); dlg.execWithSelection(InstIconKey);
if (dlg.result() == QDialog::Accepted) if (dlg.result() == QDialog::Accepted) {
{
InstIconKey = dlg.selectedIconKey; InstIconKey = dlg.selectedIconKey;
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey)); ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
importIcon = false; importIcon = false;
@ -305,7 +304,7 @@ void NewInstanceDialog::importIconNow()
{ {
if (importIcon) { if (importIcon) {
APPLICATION->icons()->installIcon(importIconPath, importIconName); APPLICATION->icons()->installIcon(importIconPath, importIconName);
InstIconKey = importIconName; InstIconKey = importIconName.mid(0, importIconName.lastIndexOf('.'));
importIcon = false; importIcon = false;
} }
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64()); APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());

View File

@ -42,7 +42,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -47,7 +47,7 @@
<item> <item>
<widget class="QPushButton" name="jprofilerPathBtn"> <widget class="QPushButton" name="jprofilerPathBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -84,7 +84,7 @@
<item> <item>
<widget class="QPushButton" name="jvisualvmPathBtn"> <widget class="QPushButton" name="jvisualvmPathBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -121,7 +121,7 @@
<item> <item>
<widget class="QPushButton" name="mceditPathBtn"> <widget class="QPushButton" name="mceditPathBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -166,7 +166,7 @@
<item row="0" column="2"> <item row="0" column="2">
<widget class="QToolButton" name="jsonEditorBrowseBtn"> <widget class="QToolButton" name="jsonEditorBrowseBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -160,117 +160,7 @@
<string>Java Runtime</string> <string>Java Runtime</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="3" column="1"> <item row="7" column="0" colspan="3">
<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 row="2" column="0">
<widget class="QLabel" name="labelJVMArgs">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<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 row="3" column="2">
<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>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="javaPathTextBox"/>
</item>
<item>
<widget class="QPushButton" name="javaBrowseBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" 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>
</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">
<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>
</property>
<property name="text">
<string>Skip Java &amp;Wizard</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QPlainTextEdit" name="jvmArgsTextBox"> <widget class="QPlainTextEdit" name="jvmArgsTextBox">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
@ -289,6 +179,114 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<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="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>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>JVM arguments:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<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="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Browse</string>
</property>
</widget>
</item>
</layout>
</item>
<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>
</property>
<property name="text">
<string>Skip Java &amp;Wizard</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -317,8 +315,6 @@
<tabstop>permGenSpinBox</tabstop> <tabstop>permGenSpinBox</tabstop>
<tabstop>javaBrowseBtn</tabstop> <tabstop>javaBrowseBtn</tabstop>
<tabstop>javaPathTextBox</tabstop> <tabstop>javaPathTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>tabWidget</tabstop> <tabstop>tabWidget</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>

View File

@ -99,7 +99,7 @@
<item row="3" column="2"> <item row="3" column="2">
<widget class="QToolButton" name="downloadsDirBrowseBtn"> <widget class="QToolButton" name="downloadsDirBrowseBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -109,7 +109,7 @@
<item row="1" column="2"> <item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn"> <widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -126,14 +126,14 @@
<item row="0" column="2"> <item row="0" column="2">
<widget class="QToolButton" name="instDirBrowseBtn"> <widget class="QToolButton" name="instDirBrowseBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2"> <item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn"> <widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text"> <property name="text">
<string notr="true">...</string> <string>Browse</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -61,31 +61,7 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3"> <item row="4" column="0">
<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">
<widget class="QCheckBox" name="skipCompatibilityCheckbox"> <widget class="QCheckBox" name="skipCompatibilityCheckbox">
<property name="toolTip"> <property name="toolTip">
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string> <string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
@ -95,6 +71,38 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
</item> </item>
@ -699,10 +707,6 @@
<tabstop>openGlobalJavaSettingsButton</tabstop> <tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop> <tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop> <tabstop>javaSettingsGroupBox</tabstop>
<tabstop>javaPathTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaBrowseBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
<tabstop>memoryGroupBox</tabstop> <tabstop>memoryGroupBox</tabstop>
<tabstop>minMemSpinBox</tabstop> <tabstop>minMemSpinBox</tabstop>
<tabstop>maxMemSpinBox</tabstop> <tabstop>maxMemSpinBox</tabstop>

View File

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

View File

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

View File

@ -20,6 +20,8 @@
#include <BuildConfig.h> #include <BuildConfig.h>
#include <Json.h> #include <Json.h>
#include "net/ApiDownload.h"
namespace Atl { namespace Atl {
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@ -75,7 +77,7 @@ void ListModel::request()
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network()); auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json"); auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
@ -137,8 +139,7 @@ void ListModel::requestFailed(QString reason)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{ {
if (m_logoMap.contains(logo)) { if (m_logoMap.contains(logo)) {
callback( callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());
APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
} else { } else {
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
} }
@ -168,9 +169,9 @@ void ListModel::requestLogo(QString file, QString url)
return; 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()); auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {

View File

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

View File

@ -3,6 +3,8 @@
#include "Application.h" #include "Application.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
#include <Version.h> #include <Version.h>
#include <QtMath> #include <QtMath>
@ -40,14 +42,16 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
return edit; return edit;
} }
return pack.description; return pack.description;
} case Qt::DecorationRole: { }
case Qt::DecorationRole: {
if (m_logoMap.contains(pack.logoName)) { if (m_logoMap.contains(pack.logoName)) {
return (m_logoMap.value(pack.logoName)); return (m_logoMap.value(pack.logoName));
} }
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
return icon; return icon;
} case Qt::UserRole: { }
case Qt::UserRole: {
QVariant v; QVariant v;
v.setValue(pack); v.setValue(pack);
return v; return v;
@ -102,9 +106,9 @@ void ListModel::requestLogo(QString logo, QString url)
return; 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()); auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
@ -128,7 +132,7 @@ void ListModel::requestLogo(QString logo, QString url)
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{ {
if (m_logoMap.contains(logo)) { 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 { } else {
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
} }
@ -171,7 +175,7 @@ void ListModel::performPaginatedSearch()
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort + 1); .arg(currentSort + 1);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);

View File

@ -42,9 +42,11 @@
#include "FlameModel.h" #include "FlameModel.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "Json.h" #include "Json.h"
#include "modplatform/flame/FlameAPI.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "modplatform/flame/FlameAPI.h"
#include "net/ApiDownload.h"
static FlameAPI api; 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 netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); auto response = std::make_shared<QByteArray>();
int addonId = current.addonId; int addonId = current.addonId;
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] { QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
if (addonId != current.addonId) { if (addonId != current.addonId) {
@ -207,7 +209,7 @@ void FlamePage::suggestCurrent()
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info))); dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
QString editedLogoName; QString editedLogoName;
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0); editedLogoName = "curseforge_" + current.logoName;
listModel->getLogo(current.logoName, current.logoUrl, listModel->getLogo(current.logoName, current.logoUrl,
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); }); [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
} }
@ -253,9 +255,7 @@ void FlamePage::updateUi()
} }
if (current.extraInfoLoaded) { if (current.extraInfoLoaded) {
if (!current.extra.issuesUrl.isEmpty() if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty()) {
|| !current.extra.sourceUrl.isEmpty()
|| !current.extra.wikiUrl.isEmpty()) {
text += "<br><br>" + tr("External links:") + "<br>"; 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 += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
} }
text += "<hr>"; text += "<hr>";
text += api.getModDescription(current.addonId).toUtf8(); text += api.getModDescription(current.addonId).toUtf8();

View File

@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ImportFTBPage.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
#include "FileSystem.h"
#include "ListModel.h"
#include "modplatform/import_ftb/PackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
namespace FTBImportAPP {
ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog(dialog), ui(new Ui::ImportFTBPage)
{
ui->setupUi(this);
{
listModel = new ListModel(this);
ui->modpackList->setModel(listModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
ui->modpackList->selectionModel()->reset();
}
ImportFTBPage::~ImportFTBPage()
{
delete ui;
}
void ImportFTBPage::openedImpl()
{
if (!initialized) {
listModel->update();
initialized = true;
}
suggestCurrent();
}
void ImportFTBPage::retranslate()
{
ui->retranslateUi(this);
}
void ImportFTBPage::suggestCurrent()
{
if (!isOpened)
return;
if (selected.path.isEmpty()) {
dialog->setSuggestedPack();
return;
}
dialog->setSuggestedPack(selected.name, new PackInstallTask(selected));
QString editedLogoName = QString("ftb_%1").arg(selected.id);
dialog->setSuggestedIconFromFile(FS::PathCombine(selected.path, "folder.jpg"), editedLogoName);
}
void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
{
if (!now.isValid()) {
onPackSelectionChanged();
return;
}
Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
{
if (pack) {
selected = *pack;
suggestCurrent();
return;
}
if (isOpened)
dialog->setSuggestedPack();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDialog>
#include <QTextBrowser>
#include <QTreeView>
#include <QWidget>
#include <Application.h>
#include "modplatform/import_ftb/PackHelpers.h"
#include "ui/pages/BasePage.h"
#include "ui/pages/modplatform/import_ftb/ListModel.h"
class NewInstanceDialog;
namespace FTBImportAPP {
namespace Ui {
class ImportFTBPage;
}
class ImportFTBPage : public QWidget, public BasePage {
Q_OBJECT
public:
explicit ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent = 0);
virtual ~ImportFTBPage();
QString displayName() const override { return tr("FTB App Import"); }
QIcon icon() const override { return APPLICATION->getThemedIcon("ftb_logo"); }
QString id() const override { return "import_ftb"; }
QString helpPage() const override { return "FTB-platform"; }
bool shouldDisplay() const override { return true; }
void openedImpl() override;
void retranslate() override;
private:
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
};
} // namespace FTBImportAPP

View File

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

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ListModel.h"
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
QString getPath()
{
QString partialPath;
#if defined(Q_OS_OSX)
partialPath = FS::PathCombine(QDir::homePath(), "Library/Application Support");
#elif defined(Q_OS_WIN32)
partialPath = QProcessEnvironment::systemEnvironment().value("LOCALAPPDATA", "");
#else
partialPath = QDir::homePath();
#endif
return FS::PathCombine(partialPath, ".ftba");
}
const QString ListModel::FTB_APP_PATH = getPath();
void ListModel::update()
{
beginResetModel();
modpacks.clear();
QString instancesPath = FS::PathCombine(FTB_APP_PATH, "instances");
if (auto instancesInfo = QFileInfo(instancesPath); instancesInfo.exists() && instancesInfo.isDir()) {
QDirIterator directoryIterator(instancesPath, QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable | QDir::Hidden,
QDirIterator::FollowSymlinks);
while (directoryIterator.hasNext()) {
auto modpack = parseDirectory(directoryIterator.next());
if (!modpack.path.isEmpty())
modpacks.append(modpack);
}
} else {
qDebug() << "Couldn't find ftb instances folder: " << instancesPath;
}
endResetModel();
}
QVariant ListModel::data(const QModelIndex& index, int role) const
{
int pos = index.row();
if (pos >= modpacks.size() || pos < 0 || !index.isValid()) {
return QVariant();
}
auto pack = modpacks.at(pos);
if (role == Qt::DisplayRole) {
return pack.name;
} else if (role == Qt::DecorationRole) {
return pack.icon;
} else if (role == Qt::UserRole) {
QVariant v;
v.setValue(pack);
return v;
} else if (role == Qt::ToolTipRole) {
return tr("Minecraft %1").arg(pack.mcVersion);
}
return QVariant();
}
} // namespace FTBImportAPP

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAbstractListModel>
#include <QIcon>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
class ListModel : public QAbstractListModel {
Q_OBJECT
public:
ListModel(QObject* parent) : QAbstractListModel(parent) {}
virtual ~ListModel() = default;
int rowCount(const QModelIndex& parent) const { return modpacks.size(); }
int columnCount(const QModelIndex& parent) const { return 1; }
QVariant data(const QModelIndex& index, int role) const;
void update();
static const QString FTB_APP_PATH;
private:
ModpackList modpacks;
};
} // namespace FTBImportAPP

View File

@ -37,6 +37,7 @@
#include "Application.h" #include "Application.h"
#include "net/HttpMetaCache.h" #include "net/HttpMetaCache.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "net/ApiDownload.h"
#include <Version.h> #include <Version.h>
#include "StringUtils.h" #include "StringUtils.h"
@ -229,9 +230,9 @@ void ListModel::requestLogo(QString file)
return; 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()); NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] { QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
@ -255,7 +256,7 @@ void ListModel::requestLogo(QString file)
void ListModel::getLogo(const QString& logo, LogoCallback callback) void ListModel::getLogo(const QString& logo, LogoCallback callback)
{ {
if (m_logoMap.contains(logo)) { 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 { } else {
requestLogo(logo); requestLogo(logo);
} }

View File

@ -42,6 +42,8 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "ui/widgets/ProjectItem.h" #include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
#include <QMessageBox> #include <QMessageBox>
namespace Modrinth { namespace Modrinth {
@ -142,7 +144,7 @@ void ModpackListModel::performPaginatedSearch()
.arg(currentSearchTerm) .arg(currentSearchTerm)
.arg(currentSort); .arg(currentSort);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] { QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
QJsonParseError parse_error_all{}; QJsonParseError parse_error_all{};
@ -216,9 +218,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)
void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{ {
if (m_logoMap.contains(logo)) { if (m_logoMap.contains(logo)) {
callback(APPLICATION->metacache() callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());
->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
->getFullPath());
} else { } else {
requestLogo(logo, logoUrl); requestLogo(logo, logoUrl);
} }
@ -230,10 +230,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)
return; return;
} }
MetaEntryPtr entry = MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {

View File

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

View File

@ -38,6 +38,8 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h"
#include <QIcon> #include <QIcon>
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {} Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
@ -116,7 +118,7 @@ void Technic::ListModel::performSearch()
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm); QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
searchMode = List; searchMode = List;
} }
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
jobPtr = netJob; jobPtr = netJob;
jobPtr->start(); jobPtr->start();
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished); QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
@ -157,7 +159,7 @@ void Technic::ListModel::searchRequestFinished()
pack.logoName = "null"; pack.logoName = "null";
} else { } else {
pack.logoUrl = rawURL; pack.logoUrl = rawURL;
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); pack.logoName = rawURL.section(QLatin1Char('/'), -1);
} }
pack.broken = false; pack.broken = false;
newList.append(pack); newList.append(pack);
@ -179,7 +181,7 @@ void Technic::ListModel::searchRequestFinished()
auto iconUrl = Json::requireString(iconObj, "url"); auto iconUrl = Json::requireString(iconObj, "url");
pack.logoUrl = iconUrl; pack.logoUrl = iconUrl;
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0); pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
} else { } else {
pack.logoUrl = "null"; pack.logoUrl = "null";
pack.logoName = "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)); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network()); auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath(); auto fullPath = entry->getFullPath();

View File

@ -49,6 +49,8 @@
#include "Application.h" #include "Application.h"
#include "modplatform/technic/SolderPackManifest.h" #include "modplatform/technic/SolderPackManifest.h"
#include "net/ApiDownload.h"
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent) TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
{ {
@ -129,21 +131,18 @@ void TechnicPage::suggestCurrent()
return; return;
} }
QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0); QString editedLogoName = "technic_" + current.logoName;
model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo) model->getLogo(current.logoName, current.logoUrl,
{ [this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
dialog->setSuggestedIconFromFile(logo, editedLogoName);
});
if (current.metadataLoaded) if (current.metadataLoaded) {
{
metadataLoaded(); metadataLoaded();
return; return;
} }
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
QString slug = current.slug; QString slug = current.slug;
netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug] QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
{ {
jobPtr.reset(); jobPtr.reset();
@ -249,7 +248,7 @@ void TechnicPage::metadataLoaded()
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
auto url = QString("%1/modpack/%2").arg(current.url, current.slug); auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response)); netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded); QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);

View File

@ -103,6 +103,8 @@ public:
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
setItemDelegate(new PageViewDelegate(this)); setItemDelegate(new PageViewDelegate(this));
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Adjust margins when using Breeze theme
setProperty("_kde_side_panel_view", true);
} }
virtual QSize sizeHint() const virtual QSize sizeHint() const

View File

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

View File

@ -204,8 +204,10 @@ static void copyAction(QAction* from, QAction* to)
void WideBar::showVisibilityMenu(QPoint const& position) 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 = std::make_unique<QMenu>(this);
m_bar_menu->setTearOffEnabled(true);
}
if (m_menu_state == MenuState::Dirty) { if (m_menu_state == MenuState::Dirty) {
for (auto* old_action : m_bar_menu->actions()) for (auto* old_action : m_bar_menu->actions())