Merge remote-tracking branch 'upstream/staging' into chore/add-compiler-warnings
This commit is contained in:
commit
ef6f9487f4
62
.github/workflows/build.yml
vendored
62
.github/workflows/build.yml
vendored
@ -24,6 +24,12 @@ on:
|
||||
CACHIX_AUTH_TOKEN:
|
||||
description: Private token for authenticating against Cachix cache
|
||||
required: false
|
||||
GPG_PRIVATE_KEY:
|
||||
description: Private key for AppImage signing
|
||||
required: false
|
||||
GPG_PRIVATE_KEY_ID:
|
||||
description: ID for the GPG_PRIVATE_KEY, to select the signing key
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -68,7 +74,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.5.1'
|
||||
qt_version: '6.5.2'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -80,7 +86,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.5.1'
|
||||
qt_version: '6.5.2'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -90,7 +96,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ''
|
||||
qt_version: '6.5.0'
|
||||
qt_version: '6.5.2'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -249,6 +255,8 @@ jobs:
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
|
||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
|
||||
|
||||
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
|
||||
sudo apt install libopengl0
|
||||
|
||||
@ -387,8 +395,8 @@ jobs:
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
if ("${{ matrix.qt_ver }}" -eq "5")
|
||||
{
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
||||
Copy-Item ${{ runner.workspace }}/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
||||
}
|
||||
cd ${{ github.workspace }}
|
||||
|
||||
@ -425,7 +433,7 @@ jobs:
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
|
||||
|
||||
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows, installer)
|
||||
@ -466,11 +474,15 @@ jobs:
|
||||
- name: Package AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
shell: bash
|
||||
env:
|
||||
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||
|
||||
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
||||
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
||||
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||
|
||||
export OUTPUT="PrismLauncher-Linux-x86_64.AppImage"
|
||||
|
||||
chmod +x linuxdeploy-*.AppImage
|
||||
|
||||
@ -481,8 +493,8 @@ jobs:
|
||||
|
||||
cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
|
||||
|
||||
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||
|
||||
cp -r ${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||
|
||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
@ -494,8 +506,33 @@ jobs:
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib"
|
||||
export LD_LIBRARY_PATH
|
||||
|
||||
chmod +x AppImageUpdate-x86_64.AppImage
|
||||
./AppImageUpdate-x86_64.AppImage --appimage-extract
|
||||
|
||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
|
||||
mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
|
||||
|
||||
cp -r squashfs-root/usr/bin/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/bin
|
||||
cp -r squashfs-root/usr/lib/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib
|
||||
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/optional
|
||||
cp -r squashfs-root/usr/optional/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins
|
||||
|
||||
export UPDATE_INFORMATION="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|PrismLauncher-Linux-x86_64.AppImage.zsync"
|
||||
|
||||
if [ '${{ secrets.GPG_PRIVATE_KEY_ID }}' != '' ]; then
|
||||
export SIGN=1
|
||||
export SIGN_KEY=${{ secrets.GPG_PRIVATE_KEY_ID }}
|
||||
mkdir -p ~/.gnupg/
|
||||
printf "$GPG_PRIVATE_KEY" | base64 --decode > ~/.gnupg/private.key
|
||||
gpg --import ~/.gnupg/private.key
|
||||
else
|
||||
echo ":warning: Skipped code signing for Linux AppImage, as gpg key was not present." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.prismlauncher.PrismLauncher.svg
|
||||
|
||||
mv "PrismLauncher-Linux-x86_64.AppImage" "PrismLauncher-Linux-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||
|
||||
##
|
||||
# UPLOAD BUILDS
|
||||
##
|
||||
@ -562,6 +599,13 @@ jobs:
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
path: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage
|
||||
|
||||
- name: Upload AppImage Zsync (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage.zsync
|
||||
path: PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
|
||||
- name: ccache stats (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
|
9
.github/workflows/trigger_release.yml
vendored
9
.github/workflows/trigger_release.yml
vendored
@ -43,7 +43,8 @@ jobs:
|
||||
mv PrismLauncher-Linux-Qt6*/PrismLauncher.tar.gz PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-Linux*/PrismLauncher.tar.gz PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||
|
||||
@ -78,9 +79,8 @@ jobs:
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag_name: ${{ github.ref }}
|
||||
name: Prism Launcher ${{ env.VERSION }}
|
||||
draft: true
|
||||
@ -88,7 +88,8 @@ jobs:
|
||||
files: |
|
||||
PrismLauncher-Linux-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||
PrismLauncher-Linux-x86_64.AppImage
|
||||
PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
PrismLauncher-Linux-Qt6-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Linux-Qt6-Portable-${{ env.VERSION }}.tar.gz
|
||||
PrismLauncher-Windows-MinGW-w64-${{ env.VERSION }}.zip
|
||||
|
@ -327,6 +327,8 @@ add_subdirectory(program_info)
|
||||
|
||||
####################################### Install layout #######################################
|
||||
|
||||
set(Launcher_ENABLE_UPDATER NO)
|
||||
|
||||
if(NOT (UNIX AND APPLE))
|
||||
# Install "portable.txt" if selected component is "portable"
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL)
|
||||
@ -351,9 +353,9 @@ if(UNIX AND APPLE)
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022 ${Launcher_Copyright_Mac}")
|
||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=")
|
||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml")
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "© 2022-2023 ${Launcher_Copyright_Mac}")
|
||||
set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "v55ZWWD6QlPoXGV6VLzOTZxZUggWeE51X8cRQyQh6vA=" CACHE STRING "Public key for Sparkle update feed")
|
||||
set(MACOSX_SPARKLE_UPDATE_FEED_URL "https://prismlauncher.org/feed/appcast.xml" CACHE STRING "URL for Sparkle update feed")
|
||||
|
||||
set(MACOSX_SPARKLE_DOWNLOAD_URL "https://github.com/sparkle-project/Sparkle/releases/download/2.1.0/Sparkle-2.1.0.tar.xz" CACHE STRING "URL to Sparkle release archive")
|
||||
set(MACOSX_SPARKLE_SHA256 "bf6ac1caa9f8d321d5784859c88da874f28412f37fb327bc21b7b14c5d61ef94" CACHE STRING "SHA256 checksum for Sparkle release archive")
|
||||
@ -362,8 +364,12 @@ if(UNIX AND APPLE)
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} ${MACOSX_SPARKLE_DIR})
|
||||
|
||||
if(NOT MACOSX_SPARKLE_UPDATE_PUBLIC_KEY STREQUAL "" AND NOT MACOSX_SPARKLE_UPDATE_FEED_URL STREQUAL "")
|
||||
set(Launcher_ENABLE_UPDATER YES)
|
||||
endif()
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||
|
||||
# Add the icon
|
||||
install(FILES ${Launcher_Branding_ICNS} DESTINATION ${RESOURCES_DEST_DIR} RENAME ${Launcher_Name}.icns)
|
||||
@ -376,7 +382,7 @@ elseif(UNIX)
|
||||
set(JARS_DEST_DIR "share/${Launcher_Name}")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
set(INSTALL_BUNDLE "nodeps" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||
|
||||
# Set RPATH
|
||||
SET(Launcher_BINARY_RPATH "$ORIGIN/")
|
||||
@ -410,7 +416,7 @@ elseif(WIN32)
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
set(INSTALL_BUNDLE "full" CACHE STRING "Use fixup_bundle to bundle dependencies")
|
||||
else()
|
||||
message(FATAL_ERROR "Platform not supported")
|
||||
endif()
|
||||
|
@ -18,6 +18,8 @@ finish-args:
|
||||
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||
# Mod drag&drop
|
||||
- --filesystem=xdg-download:ro
|
||||
# FTBApp import
|
||||
- --filesystem=~/.ftba:ro
|
||||
|
||||
cleanup:
|
||||
- /lib/libGLU*
|
||||
|
@ -131,17 +131,12 @@
|
||||
#include "MangoHud.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||
#include "updater/MacSparkleUpdater.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include "WindowsConsole.h"
|
||||
#endif
|
||||
|
||||
#define STRINGIFY(x) #x
|
||||
@ -168,31 +163,15 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QSt
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
|
||||
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
// attach the parent console
|
||||
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
{
|
||||
// if attach succeeds, reopen and sync all the i/o
|
||||
if(freopen("CON", "w", stdout))
|
||||
{
|
||||
std::cout.sync_with_stdio();
|
||||
}
|
||||
if(freopen("CON", "w", stderr))
|
||||
{
|
||||
std::cerr.sync_with_stdio();
|
||||
}
|
||||
if(freopen("CON", "r", stdin))
|
||||
{
|
||||
std::cin.sync_with_stdio();
|
||||
}
|
||||
auto out = GetStdHandle (STD_OUTPUT_HANDLE);
|
||||
DWORD written;
|
||||
const char * endline = "\n";
|
||||
WriteConsole(out, endline, strlen(endline), &written, NULL);
|
||||
// attach the parent console if stdout not already captured
|
||||
if (AttachWindowsConsole()) {
|
||||
consoleAttached = true;
|
||||
}
|
||||
#endif
|
||||
@ -281,7 +260,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
}
|
||||
else
|
||||
{
|
||||
QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
||||
QDir foo;
|
||||
if (DesktopServices::isSnap())
|
||||
{
|
||||
foo = QDir(getenv("SNAP_USER_COMMON"));
|
||||
}
|
||||
else
|
||||
{
|
||||
foo = QDir(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), ".."));
|
||||
}
|
||||
|
||||
dataPath = foo.absolutePath();
|
||||
adjustedBy = "Persistent data path";
|
||||
|
||||
@ -628,9 +616,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_settings->registerSetting("ShowGlobalGameTime", true);
|
||||
m_settings->registerSetting("RecordGameTime", true);
|
||||
|
||||
// Minecraft launch method
|
||||
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
|
||||
|
||||
// Minecraft mods
|
||||
m_settings->registerSetting("ModMetadataDisabled", false);
|
||||
|
||||
@ -704,7 +689,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
|
||||
|
||||
// get rid of invalid meta urls
|
||||
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https")
|
||||
if (!metaUrl.isValid() || (metaUrl.scheme() != "http" && metaUrl.scheme() != "https"))
|
||||
m_settings->reset("MetaURLOverride");
|
||||
}
|
||||
|
||||
@ -776,7 +761,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
if(BuildConfig.UPDATER_ENABLED)
|
||||
{
|
||||
qDebug() << "Initializing updater";
|
||||
#ifdef Q_OS_MAC
|
||||
#if defined(Q_OS_MAC) && defined(SPARKLE_ENABLED)
|
||||
m_updater.reset(new MacSparkleUpdater());
|
||||
#endif
|
||||
qDebug() << "<> Updater started.";
|
||||
|
@ -136,6 +136,15 @@ set(NET_SOURCES
|
||||
net/Validator.h
|
||||
net/Upload.cpp
|
||||
net/Upload.h
|
||||
net/HeaderProxy.h
|
||||
net/RawHeaderProxy.h
|
||||
net/ApiHeaderProxy.h
|
||||
net/ApiDownload.h
|
||||
net/ApiDownload.cpp
|
||||
net/ApiUpload.cpp
|
||||
net/ApiUpload.h
|
||||
net/NetRequest.cpp
|
||||
net/NetRequest.h
|
||||
)
|
||||
|
||||
# Game launch logic
|
||||
@ -262,8 +271,6 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/launch/CreateGameFolders.h
|
||||
minecraft/launch/ModMinecraftJar.cpp
|
||||
minecraft/launch/ModMinecraftJar.h
|
||||
minecraft/launch/DirectJavaLaunch.cpp
|
||||
minecraft/launch/DirectJavaLaunch.h
|
||||
minecraft/launch/ExtractNatives.cpp
|
||||
minecraft/launch/ExtractNatives.h
|
||||
minecraft/launch/LauncherPartLaunch.cpp
|
||||
@ -501,6 +508,11 @@ set(FTB_SOURCES
|
||||
modplatform/legacy_ftb/PrivatePackManager.cpp
|
||||
|
||||
modplatform/legacy_ftb/PackHelpers.h
|
||||
|
||||
modplatform/import_ftb/PackInstallTask.h
|
||||
modplatform/import_ftb/PackInstallTask.cpp
|
||||
modplatform/import_ftb/PackHelpers.h
|
||||
modplatform/import_ftb/PackHelpers.cpp
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
@ -563,6 +575,9 @@ set(ATLAUNCHER_SOURCES
|
||||
)
|
||||
|
||||
set(LINKEXE_SOURCES
|
||||
WindowsConsole.cpp
|
||||
WindowsConsole.h
|
||||
|
||||
filelink/FileLink.h
|
||||
filelink/FileLink.cpp
|
||||
FileSystem.h
|
||||
@ -668,7 +683,7 @@ set(LOGIC_SOURCES
|
||||
${ATLAUNCHER_SOURCES}
|
||||
)
|
||||
|
||||
if(APPLE)
|
||||
if(APPLE AND Launcher_ENABLE_UPDATER)
|
||||
set (LOGIC_SOURCES ${LOGIC_SOURCES} ${MAC_UPDATE_SOURCES})
|
||||
endif()
|
||||
|
||||
@ -872,6 +887,11 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/modplatform/legacy_ftb/ListModel.h
|
||||
ui/pages/modplatform/legacy_ftb/ListModel.cpp
|
||||
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.h
|
||||
ui/pages/modplatform/import_ftb/ListModel.h
|
||||
ui/pages/modplatform/import_ftb/ListModel.cpp
|
||||
|
||||
ui/pages/modplatform/flame/FlameModel.cpp
|
||||
ui/pages/modplatform/flame/FlameModel.h
|
||||
ui/pages/modplatform/flame/FlamePage.cpp
|
||||
@ -1018,6 +1038,14 @@ SET(LAUNCHER_SOURCES
|
||||
ui/instanceview/VisualGroup.h
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(LAUNCHER_SOURCES
|
||||
WindowsConsole.cpp
|
||||
WindowsConsole.h
|
||||
${LAUNCHER_SOURCES}
|
||||
)
|
||||
endif()
|
||||
|
||||
qt_wrap_ui(LAUNCHER_UI
|
||||
ui/MainWindow.ui
|
||||
ui/setupwizard/PasteWizardPage.ui
|
||||
@ -1046,6 +1074,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/modplatform/ResourcePage.ui
|
||||
ui/pages/modplatform/flame/FlamePage.ui
|
||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
|
||||
ui/pages/modplatform/ImportPage.ui
|
||||
ui/pages/modplatform/modrinth/ModrinthPage.ui
|
||||
ui/pages/modplatform/technic/TechnicPage.ui
|
||||
@ -1108,6 +1137,7 @@ set_project_warnings(Launcher_logic
|
||||
"${Launcher_MSVC_WARNINGS}"
|
||||
"${Launcher_CLANG_WARNINGS}"
|
||||
"${Launcher_GCC_WARNINGS}")
|
||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(Launcher_logic
|
||||
systeminfo
|
||||
@ -1147,17 +1177,23 @@ if(APPLE)
|
||||
set(CMAKE_MACOSX_RPATH 1)
|
||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
||||
|
||||
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
||||
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
||||
if(Launcher_ENABLE_UPDATER)
|
||||
file(DOWNLOAD ${MACOSX_SPARKLE_DOWNLOAD_URL} ${CMAKE_BINARY_DIR}/Sparkle.tar.xz EXPECTED_HASH SHA256=${MACOSX_SPARKLE_SHA256})
|
||||
file(ARCHIVE_EXTRACT INPUT ${CMAKE_BINARY_DIR}/Sparkle.tar.xz DESTINATION ${CMAKE_BINARY_DIR}/frameworks/Sparkle)
|
||||
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||
add_compile_definitions(SPARKLE_ENABLED)
|
||||
endif()
|
||||
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle "${CMAKE_BINARY_DIR}/frameworks/Sparkle")
|
||||
target_link_libraries(Launcher_logic
|
||||
"-framework AppKit"
|
||||
"-framework Carbon"
|
||||
"-framework Foundation"
|
||||
"-framework ApplicationServices"
|
||||
)
|
||||
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
||||
if(Launcher_ENABLE_UPDATER)
|
||||
target_link_libraries(Launcher_logic ${SPARKLE_FRAMEWORK})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(Launcher_logic)
|
||||
@ -1224,7 +1260,7 @@ if(WIN32)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (UNIX AND APPLE)
|
||||
if (UNIX AND APPLE AND Launcher_ENABLE_UPDATER)
|
||||
# Add Sparkle updater
|
||||
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
|
||||
# the framework aren't installed
|
||||
|
@ -118,7 +118,7 @@ bool openDirectory(const QString &path, [[maybe_unused]] bool ensureExists)
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!isFlatpak())
|
||||
if(!isSandbox())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -139,7 +139,7 @@ bool openFile(const QString &path)
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!isFlatpak())
|
||||
if(!isSandbox())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -157,7 +157,7 @@ bool openFile(const QString &application, const QString &path, const QString &wo
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
if(!isFlatpak())
|
||||
if(!isSandbox())
|
||||
{
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
@ -177,7 +177,7 @@ bool run(const QString &application, const QStringList &args, const QString &wor
|
||||
{
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!isFlatpak())
|
||||
if(!isSandbox())
|
||||
{
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
@ -202,7 +202,7 @@ bool openUrl(const QUrl &url)
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
if(!isFlatpak())
|
||||
if(!isSandbox())
|
||||
{
|
||||
return IndirectOpen(f);
|
||||
}
|
||||
@ -224,4 +224,18 @@ bool isFlatpak()
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isSnap()
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
return getenv("SNAP");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isSandbox()
|
||||
{
|
||||
return isSnap() || isFlatpak();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,5 +34,18 @@ namespace DesktopServices
|
||||
*/
|
||||
bool openUrl(const QUrl &url);
|
||||
|
||||
/**
|
||||
* Determine whether the launcher is running in a Flatpak environment
|
||||
*/
|
||||
bool isFlatpak();
|
||||
|
||||
/**
|
||||
* Determine whether the launcher is running in a Snap environment
|
||||
*/
|
||||
bool isSnap();
|
||||
|
||||
/**
|
||||
* Determine whether the launcher is running in a sandboxed (Flatpak or Snap) environment
|
||||
*/
|
||||
bool isSandbox();
|
||||
}
|
||||
|
@ -51,6 +51,8 @@
|
||||
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QtConcurrentRun>
|
||||
#include <algorithm>
|
||||
|
||||
@ -95,7 +97,7 @@ void InstanceImportTask::executeTask()
|
||||
m_archivePath = entry->getFullPath();
|
||||
|
||||
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
||||
|
||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||
ModPlatform::IndexedVersion version,
|
||||
const std::shared_ptr<ResourceFolderModel> packs,
|
||||
@ -51,7 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||
}
|
||||
}
|
||||
|
||||
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
||||
m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
|
||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
|
||||
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);
|
||||
|
132
launcher/WindowsConsole.cpp
Normal file
132
launcher/WindowsConsole.cpp
Normal 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
25
launcher/WindowsConsole.h
Normal 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();
|
@ -37,11 +37,7 @@
|
||||
#include <sys.h>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include "WindowsConsole.h"
|
||||
#endif
|
||||
|
||||
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
||||
@ -67,21 +63,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
// attach the parent console
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||
// if attach succeeds, reopen and sync all the i/o
|
||||
if (freopen("CON", "w", stdout)) {
|
||||
std::cout.sync_with_stdio();
|
||||
}
|
||||
if (freopen("CON", "w", stderr)) {
|
||||
std::cerr.sync_with_stdio();
|
||||
}
|
||||
if (freopen("CON", "r", stdin)) {
|
||||
std::cin.sync_with_stdio();
|
||||
}
|
||||
auto out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD written;
|
||||
const char* endline = "\n";
|
||||
WriteConsole(out, endline, strlen(endline), &written, NULL);
|
||||
if (AttachWindowsConsole()) {
|
||||
consoleAttached = true;
|
||||
}
|
||||
#endif
|
||||
@ -188,7 +170,7 @@ void FileLinkApp::runLink()
|
||||
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
|
||||
m_path_results.append(result);
|
||||
} else {
|
||||
FS::LinkResult result = { src_path, dst_path };
|
||||
FS::LinkResult result = { src_path, dst_path, "", 0};
|
||||
m_path_results.append(result);
|
||||
}
|
||||
}
|
||||
@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
|
||||
fclose(stdout);
|
||||
fclose(stdin);
|
||||
fclose(stderr);
|
||||
FreeConsole();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -35,32 +36,30 @@
|
||||
|
||||
#include "IconList.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QMap>
|
||||
#include <QEventLoop>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
#include <QEventLoop>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMap>
|
||||
#include <QMimeData>
|
||||
#include <QSet>
|
||||
#include <QUrl>
|
||||
#include "icons/IconUtils.h"
|
||||
|
||||
#define MAX_SIZE 1024
|
||||
|
||||
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
|
||||
IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
|
||||
{
|
||||
QSet<QString> builtinNames;
|
||||
|
||||
// add builtin icons
|
||||
for(auto & builtinPath: builtinPaths)
|
||||
{
|
||||
for (auto& builtinPath : builtinPaths) {
|
||||
QDir instance_icons(builtinPath);
|
||||
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
||||
for (auto file_info : file_info_list)
|
||||
{
|
||||
for (auto file_info : file_info_list) {
|
||||
builtinNames.insert(file_info.completeBaseName());
|
||||
}
|
||||
}
|
||||
for(auto & builtinName : builtinNames)
|
||||
{
|
||||
for (auto& builtinName : builtinNames) {
|
||||
addThemeIcon(builtinName);
|
||||
}
|
||||
|
||||
@ -78,31 +77,27 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
|
||||
void IconList::sortIconList()
|
||||
{
|
||||
qDebug() << "Sorting icon list...";
|
||||
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
|
||||
return a.m_key.localeAwareCompare(b.m_key) < 0;
|
||||
});
|
||||
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
|
||||
reindex();
|
||||
}
|
||||
|
||||
void IconList::directoryChanged(const QString &path)
|
||||
void IconList::directoryChanged(const QString& path)
|
||||
{
|
||||
QDir new_dir (path);
|
||||
if(m_dir.absolutePath() != new_dir.absolutePath())
|
||||
{
|
||||
QDir new_dir(path);
|
||||
if (m_dir.absolutePath() != new_dir.absolutePath()) {
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if(is_watching)
|
||||
if (is_watching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
if(!m_dir.exists())
|
||||
if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
if (!m_dir.exists())
|
||||
if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
return;
|
||||
m_dir.refresh();
|
||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
||||
for (auto it = new_list.begin(); it != new_list.end(); it++)
|
||||
{
|
||||
QString &foo = (*it);
|
||||
for (auto it = new_list.begin(); it != new_list.end(); it++) {
|
||||
QString& foo = (*it);
|
||||
foo = m_dir.filePath(foo);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
@ -111,8 +106,7 @@ void IconList::directoryChanged(const QString &path)
|
||||
auto new_set = new_list.toSet();
|
||||
#endif
|
||||
QList<QString> current_list;
|
||||
for (auto &it : icons)
|
||||
{
|
||||
for (auto& it : icons) {
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||
@ -129,38 +123,33 @@ void IconList::directoryChanged(const QString &path)
|
||||
QSet<QString> to_add = new_set;
|
||||
to_add -= current_set;
|
||||
|
||||
for (auto remove : to_remove)
|
||||
{
|
||||
for (auto remove : to_remove) {
|
||||
qDebug() << "Removing " << remove;
|
||||
QFileInfo rmfile(remove);
|
||||
QString key = rmfile.completeBaseName();
|
||||
|
||||
QString suffix = rmfile.suffix();
|
||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
if (!IconUtils::isIconSuffix(suffix))
|
||||
key = rmfile.fileName();
|
||||
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(IconType::FileBased);
|
||||
if (icons[idx].type() == IconType::ToBeDeleted)
|
||||
{
|
||||
if (icons[idx].type() == IconType::ToBeDeleted) {
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
reindex();
|
||||
endRemoveRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
dataChanged(index(idx), index(idx));
|
||||
}
|
||||
m_watcher->removePath(remove);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
for (auto add : to_add)
|
||||
{
|
||||
for (auto add : to_add) {
|
||||
qDebug() << "Adding " << add;
|
||||
|
||||
QFileInfo addfile(add);
|
||||
@ -168,11 +157,10 @@ void IconList::directoryChanged(const QString &path)
|
||||
|
||||
QString suffix = addfile.suffix();
|
||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
if (!IconUtils::isIconSuffix(suffix))
|
||||
key = addfile.fileName();
|
||||
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
|
||||
{
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
|
||||
m_watcher->addPath(add);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
@ -181,7 +169,7 @@ void IconList::directoryChanged(const QString &path)
|
||||
sortIconList();
|
||||
}
|
||||
|
||||
void IconList::fileChanged(const QString &path)
|
||||
void IconList::fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
@ -200,9 +188,9 @@ void IconList::fileChanged(const QString &path)
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
void IconList::SettingChanged(const Setting &setting, QVariant value)
|
||||
void IconList::SettingChanged(const Setting& setting, QVariant value)
|
||||
{
|
||||
if(setting.id() != "IconsDir")
|
||||
if (setting.id() != "IconsDir")
|
||||
return;
|
||||
|
||||
directoryChanged(value.toString());
|
||||
@ -213,12 +201,9 @@ void IconList::startWatching()
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching)
|
||||
{
|
||||
if (is_watching) {
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
}
|
||||
}
|
||||
@ -241,7 +226,11 @@ Qt::DropActions IconList::supportedDropActions() const
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData* data,
|
||||
Qt::DropAction action,
|
||||
[[maybe_unused]] int row,
|
||||
[[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex& parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@ -250,12 +239,10 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
|
||||
return false;
|
||||
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
if (data->hasUrls()) {
|
||||
auto urls = data->urls();
|
||||
QStringList iconFiles;
|
||||
for (auto url : urls)
|
||||
{
|
||||
for (auto url : urls) {
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
@ -267,16 +254,13 @@ bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[mayb
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
|
||||
Qt::ItemFlags IconList::flags(const QModelIndex& index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
|
||||
QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
QVariant IconList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
@ -286,64 +270,49 @@ QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
if (row < 0 || row >= icons.size())
|
||||
return QVariant();
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
switch (role) {
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
int IconList::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
void IconList::installIcons(const QStringList& iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
continue;
|
||||
}
|
||||
installIcon(file, {});
|
||||
}
|
||||
|
||||
void IconList::installIcon(const QString &file, const QString &name)
|
||||
void IconList::installIcon(const QString& file, const QString& name)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if(!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
return;
|
||||
|
||||
QString target = FS::PathCombine(getDirectory(), name);
|
||||
if (!IconUtils::isIconSuffix(fileinfo.suffix()))
|
||||
return;
|
||||
|
||||
QString target = FS::PathCombine(getDirectory(), name.isEmpty() ? fileinfo.fileName() : name);
|
||||
QFile::copy(file, target);
|
||||
}
|
||||
|
||||
bool IconList::iconFileExists(const QString &key) const
|
||||
bool IconList::iconFileExists(const QString& key) const
|
||||
{
|
||||
auto iconEntry = icon(key);
|
||||
if(!iconEntry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return iconEntry->has(IconType::FileBased);
|
||||
return iconEntry && iconEntry->has(IconType::FileBased);
|
||||
}
|
||||
|
||||
const MMCIcon *IconList::icon(const QString &key) const
|
||||
const MMCIcon* IconList::icon(const QString& key) const
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
@ -351,100 +320,84 @@ const MMCIcon *IconList::icon(const QString &key) const
|
||||
return &icons[iconIdx];
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(const QString &key)
|
||||
bool IconList::deleteIcon(const QString& key)
|
||||
{
|
||||
if (!iconFileExists(key))
|
||||
return false;
|
||||
|
||||
return QFile::remove(icon(key)->getFilePath());
|
||||
return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
|
||||
}
|
||||
|
||||
bool IconList::trashIcon(const QString &key)
|
||||
bool IconList::trashIcon(const QString& key)
|
||||
{
|
||||
if (!iconFileExists(key))
|
||||
return false;
|
||||
|
||||
return FS::trash(icon(key)->getFilePath(), nullptr);
|
||||
return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
|
||||
}
|
||||
|
||||
bool IconList::addThemeIcon(const QString& key)
|
||||
{
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
if (iter != name_index.end()) {
|
||||
auto& oldOne = icons[*iter];
|
||||
oldOne.replace(Builtin, key);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
{
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = key;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(Builtin, key);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = key;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(Builtin, key);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
|
||||
bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
|
||||
{
|
||||
// replace the icon even? is the input valid?
|
||||
QIcon icon(path);
|
||||
if (icon.isNull())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
if (iter != name_index.end()) {
|
||||
auto& oldOne = icons[*iter];
|
||||
oldOne.replace(type, icon, path);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
{
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = name;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(type, icon, path);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = name;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(type, icon, path);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
void IconList::saveIcon(const QString &key, const QString &path, const char * format) const
|
||||
void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
|
||||
{
|
||||
auto icon = getIcon(key);
|
||||
auto pixmap = icon.pixmap(128, 128);
|
||||
pixmap.save(path, format);
|
||||
}
|
||||
|
||||
|
||||
void IconList::reindex()
|
||||
{
|
||||
name_index.clear();
|
||||
int i = 0;
|
||||
for (auto &iter : icons)
|
||||
{
|
||||
for (auto& iter : icons) {
|
||||
name_index[iter.m_key] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconList::getIcon(const QString &key) const
|
||||
QIcon IconList::getIcon(const QString& key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
@ -459,7 +412,7 @@ QIcon IconList::getIcon(const QString &key) const
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(const QString &key) const
|
||||
int IconList::getIconIndex(const QString& key) const
|
||||
{
|
||||
auto iter = name_index.find(key == "default" ? "grass" : key);
|
||||
if (iter != name_index.end())
|
||||
@ -472,5 +425,3 @@ QString IconList::getDirectory() const
|
||||
{
|
||||
return m_dir.absolutePath();
|
||||
}
|
||||
|
||||
//#include "IconList.moc"
|
||||
|
@ -1,24 +1,43 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMutex>
|
||||
#include <QAbstractListModel>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QtGui/QIcon>
|
||||
#include <memory>
|
||||
|
||||
@ -29,58 +48,58 @@
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class IconList : public QAbstractListModel
|
||||
{
|
||||
class IconList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
public:
|
||||
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
|
||||
virtual ~IconList(){};
|
||||
|
||||
QIcon getIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
QIcon getIcon(const QString& key) const;
|
||||
int getIconIndex(const QString& key) const;
|
||||
QString getDirectory() const;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
virtual QStringList mimeTypes() const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
bool addThemeIcon(const QString &key);
|
||||
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type);
|
||||
void saveIcon(const QString &key, const QString &path, const char * format) const;
|
||||
bool deleteIcon(const QString &key);
|
||||
bool trashIcon(const QString &key);
|
||||
bool iconFileExists(const QString &key) const;
|
||||
bool addThemeIcon(const QString& key);
|
||||
bool addIcon(const QString& key, const QString& name, const QString& path, const IconType type);
|
||||
void saveIcon(const QString& key, const QString& path, const char* format) const;
|
||||
bool deleteIcon(const QString& key);
|
||||
bool trashIcon(const QString& key);
|
||||
bool iconFileExists(const QString& key) const;
|
||||
|
||||
void installIcons(const QStringList &iconFiles);
|
||||
void installIcon(const QString &file, const QString &name);
|
||||
void installIcons(const QStringList& iconFiles);
|
||||
void installIcon(const QString& file, const QString& name);
|
||||
|
||||
const MMCIcon * icon(const QString &key) const;
|
||||
const MMCIcon* icon(const QString& key) const;
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void iconUpdated(QString key);
|
||||
|
||||
private:
|
||||
private:
|
||||
// hide copy constructor
|
||||
IconList(const IconList &) = delete;
|
||||
IconList(const IconList&) = delete;
|
||||
// hide assign op
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
IconList& operator=(const IconList&) = delete;
|
||||
void reindex();
|
||||
void sortIconList();
|
||||
|
||||
public slots:
|
||||
void directoryChanged(const QString &path);
|
||||
public slots:
|
||||
void directoryChanged(const QString& path);
|
||||
|
||||
protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
protected slots:
|
||||
void fileChanged(const QString& path);
|
||||
void SettingChanged(const Setting& setting, QVariant value);
|
||||
|
||||
private:
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
|
@ -1,25 +1,51 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "IconUtils.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QDirIterator>
|
||||
|
||||
#include <array>
|
||||
#include "FileSystem.h"
|
||||
|
||||
namespace {
|
||||
std::array<const char *, 6> validIconExtensions = {{
|
||||
"svg",
|
||||
"png",
|
||||
"ico",
|
||||
"gif",
|
||||
"jpg",
|
||||
"jpeg"
|
||||
}};
|
||||
static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
|
||||
}
|
||||
|
||||
namespace IconUtils{
|
||||
namespace IconUtils {
|
||||
|
||||
QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
||||
size_t best_found = validIconExtensions.size();
|
||||
QString findBestIconIn(const QString& folder, const QString& iconKey)
|
||||
{
|
||||
QString best_filename;
|
||||
|
||||
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
||||
@ -27,36 +53,20 @@ QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
||||
it.next();
|
||||
auto fileInfo = it.fileInfo();
|
||||
|
||||
if(fileInfo.completeBaseName() != iconKey)
|
||||
continue;
|
||||
|
||||
auto extension = fileInfo.suffix();
|
||||
|
||||
for(size_t i = 0; i < best_found; i++) {
|
||||
if(extension == validIconExtensions[i]) {
|
||||
best_found = i;
|
||||
qDebug() << i << " : " << fileInfo.fileName();
|
||||
best_filename = fileInfo.fileName();
|
||||
}
|
||||
}
|
||||
if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
|
||||
return fileInfo.absoluteFilePath();
|
||||
}
|
||||
return FS::PathCombine(folder, best_filename);
|
||||
return {};
|
||||
}
|
||||
|
||||
QString getIconFilter() {
|
||||
QString out;
|
||||
QTextStream stream(&out);
|
||||
stream << '(';
|
||||
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
|
||||
if(i > 0) {
|
||||
stream << " ";
|
||||
}
|
||||
stream << "*." << validIconExtensions[i];
|
||||
}
|
||||
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
|
||||
stream << ')';
|
||||
return out;
|
||||
QString getIconFilter()
|
||||
{
|
||||
return "(*." + validIconExtensions.join(" *.") + ")";
|
||||
}
|
||||
|
||||
bool isIconSuffix(QString suffix)
|
||||
{
|
||||
return validIconExtensions.contains(suffix);
|
||||
}
|
||||
|
||||
} // namespace IconUtils
|
||||
|
@ -1,3 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
@ -5,9 +40,10 @@
|
||||
namespace IconUtils {
|
||||
|
||||
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
|
||||
QString findBestIconIn(const QString &folder, const QString & iconKey);
|
||||
QString findBestIconIn(const QString& folder, const QString& iconKey);
|
||||
|
||||
// Get icon file type filter for file browser dialogs
|
||||
QString getIconFilter();
|
||||
|
||||
}
|
||||
bool isIconSuffix(QString suffix);
|
||||
} // namespace IconUtils
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -37,23 +38,21 @@
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
|
||||
IconType operator--(IconType &t, int)
|
||||
IconType operator--(IconType& t, int)
|
||||
{
|
||||
IconType temp = t;
|
||||
switch (t)
|
||||
{
|
||||
case IconType::Builtin:
|
||||
t = IconType::ToBeDeleted;
|
||||
break;
|
||||
case IconType::Transient:
|
||||
t = IconType::Builtin;
|
||||
break;
|
||||
case IconType::FileBased:
|
||||
t = IconType::Transient;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
switch (t) {
|
||||
case IconType::Builtin:
|
||||
t = IconType::ToBeDeleted;
|
||||
break;
|
||||
case IconType::Transient:
|
||||
t = IconType::Builtin;
|
||||
break;
|
||||
case IconType::FileBased:
|
||||
t = IconType::Transient;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
@ -79,8 +78,8 @@ QIcon MMCIcon::icon() const
|
||||
{
|
||||
if (m_current_type == IconType::ToBeDeleted)
|
||||
return QIcon();
|
||||
auto & icon = m_images[m_current_type].icon;
|
||||
if(!icon.isNull())
|
||||
auto& icon = m_images[m_current_type].icon;
|
||||
if (!icon.isNull())
|
||||
return icon;
|
||||
// FIXME: inject this.
|
||||
return QIcon::fromTheme(m_images[m_current_type].key);
|
||||
@ -90,10 +89,8 @@ void MMCIcon::remove(IconType rm_type)
|
||||
{
|
||||
m_images[rm_type].filename = QString();
|
||||
m_images[rm_type].icon = QIcon();
|
||||
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
|
||||
{
|
||||
if (m_images[iter].present())
|
||||
{
|
||||
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) {
|
||||
if (m_images[iter].present()) {
|
||||
m_current_type = iter;
|
||||
return;
|
||||
}
|
||||
@ -103,8 +100,7 @@ void MMCIcon::remove(IconType rm_type)
|
||||
|
||||
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = icon;
|
||||
@ -114,8 +110,7 @@ void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
||||
|
||||
void MMCIcon::replace(IconType new_type, const QString& key)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) {
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = QIcon();
|
||||
@ -125,13 +120,12 @@ void MMCIcon::replace(IconType new_type, const QString& key)
|
||||
|
||||
QString MMCIcon::getFilePath() const
|
||||
{
|
||||
if(m_current_type == IconType::ToBeDeleted){
|
||||
if (m_current_type == IconType::ToBeDeleted) {
|
||||
return QString();
|
||||
}
|
||||
return m_images[m_current_type].filename;
|
||||
}
|
||||
|
||||
|
||||
bool MMCIcon::isBuiltIn() const
|
||||
{
|
||||
return m_current_type == IconType::Builtin;
|
||||
|
@ -1,45 +1,52 @@
|
||||
/* Copyright 2013-2021 MultiMC Contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
|
||||
enum IconType : unsigned
|
||||
{
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
};
|
||||
enum IconType : unsigned { Builtin, Transient, FileBased, ICONS_TOTAL, ToBeDeleted };
|
||||
|
||||
struct MMCImage
|
||||
{
|
||||
struct MMCImage {
|
||||
QIcon icon;
|
||||
QString key;
|
||||
QString filename;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull() || !key.isEmpty();
|
||||
}
|
||||
bool present() const { return !icon.isNull() || !key.isEmpty(); }
|
||||
};
|
||||
|
||||
struct MMCIcon
|
||||
{
|
||||
struct MMCIcon {
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
@ -51,7 +58,7 @@ struct MMCIcon
|
||||
QIcon icon() const;
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
void replace(IconType new_type, const QString& key);
|
||||
bool isBuiltIn() const;
|
||||
QString getFilePath() const;
|
||||
};
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
#include "BaseEntity.h"
|
||||
|
||||
#include "net/Download.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "Json.h"
|
||||
@ -130,7 +130,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
|
||||
auto url = this->url();
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
|
||||
entry->setStale(true);
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
/*
|
||||
* The validator parses the file and loads it into the object.
|
||||
* If that fails, the file is not written to storage.
|
||||
|
@ -45,7 +45,7 @@
|
||||
|
||||
#include "AssetsUtils.h"
|
||||
#include "FileSystem.h"
|
||||
#include "net/Download.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
@ -311,7 +311,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
|
||||
QFileInfo objectFile(getLocalPath());
|
||||
if ((!objectFile.isFile()) || (objectFile.size() != size))
|
||||
{
|
||||
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
|
||||
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
|
||||
if(hash.size())
|
||||
{
|
||||
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
||||
|
@ -36,7 +36,7 @@
|
||||
#include "Library.h"
|
||||
#include "MinecraftInstance.h"
|
||||
|
||||
#include <net/Download.h>
|
||||
#include <net/ApiDownload.h>
|
||||
#include <net/ChecksumValidator.h>
|
||||
#include <FileSystem.h>
|
||||
#include <BuildConfig.h>
|
||||
@ -129,14 +129,14 @@ QList<NetAction::Ptr> Library::getDownloads(
|
||||
if(sha1.size())
|
||||
{
|
||||
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
|
||||
auto dl = Net::Download::makeCached(url, entry, options);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry, options);
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||
out.append(dl);
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append(Net::Download::makeCached(url, entry, options));
|
||||
out.append(Net::ApiDownload::makeCached(url, entry, options));
|
||||
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
|
||||
}
|
||||
return true;
|
||||
|
@ -61,7 +61,6 @@
|
||||
#include "launch/steps/QuitAfterGameStop.h"
|
||||
|
||||
#include "minecraft/launch/LauncherPartLaunch.h"
|
||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
||||
#include "minecraft/launch/ModMinecraftJar.h"
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/ReconstructAssets.h"
|
||||
@ -167,10 +166,6 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting);
|
||||
m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting);
|
||||
|
||||
// Minecraft launch method
|
||||
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride);
|
||||
|
||||
// Native library workarounds
|
||||
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
|
||||
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
|
||||
@ -990,15 +985,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
process->appendStep(makeShared<CheckJava>(pptr));
|
||||
}
|
||||
|
||||
// check launch method
|
||||
QStringList validMethods = {"LauncherPart", "DirectJava"};
|
||||
QString method = launchMethod();
|
||||
if(!validMethods.contains(method))
|
||||
{
|
||||
process->appendStep(makeShared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
||||
return process;
|
||||
}
|
||||
|
||||
// create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
|
||||
{
|
||||
process->appendStep(makeShared<CreateGameFolders>(pptr));
|
||||
@ -1072,23 +1058,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
|
||||
{
|
||||
// actually launch the game
|
||||
auto launch_method = launchMethod();
|
||||
if(launch_method == "LauncherPart")
|
||||
{
|
||||
auto step = makeShared<LauncherPartLaunch>(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
step->setServerToJoin(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
else if (launch_method == "DirectJava")
|
||||
{
|
||||
auto step = makeShared<DirectJavaLaunch>(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
step->setServerToJoin(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
auto step = makeShared<LauncherPartLaunch>(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
step->setServerToJoin(serverToJoin);
|
||||
process->appendStep(step);
|
||||
}
|
||||
|
||||
// run post-exit command if that's needed
|
||||
@ -1111,11 +1085,6 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
|
||||
return m_launchProcess;
|
||||
}
|
||||
|
||||
QString MinecraftInstance::launchMethod()
|
||||
{
|
||||
return settings()->get("MCLaunchMethod").toString();
|
||||
}
|
||||
|
||||
JavaVersion MinecraftInstance::getJavaVersion()
|
||||
{
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
|
@ -165,8 +165,6 @@ public:
|
||||
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
QStringList validLaunchMethods();
|
||||
QString launchMethod();
|
||||
|
||||
protected: // data
|
||||
std::shared_ptr<PackProfile> m_components;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -61,6 +61,7 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
|
||||
m_columnsHideable = { false, true, false, true, true, true };
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
|
@ -552,6 +552,8 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
|
||||
menu->addSeparator()->setText(tr("Show / Hide Columns"));
|
||||
|
||||
for (int col = 0; col < columnCount(); ++col) {
|
||||
// Skip creating actions for columns that should not be hidden
|
||||
if (!m_columnsHideable.at(col)) continue;
|
||||
auto act = new QAction(menu);
|
||||
setupHeaderAction(act, col);
|
||||
|
||||
|
@ -202,6 +202,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
QStringList m_column_names = {"Enable", "Name", "Last Modified"};
|
||||
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
|
||||
QList<bool> m_columnsHideable = { false, false, true };
|
||||
|
||||
QDir m_dir;
|
||||
BaseInstance* m_instance;
|
||||
|
@ -54,7 +54,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE};
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
|
||||
|
||||
m_columnsHideable = { false, true, false, true, true };
|
||||
}
|
||||
|
||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
|
@ -49,6 +49,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
|
||||
m_columnsHideable = { false, true, false, true };
|
||||
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
|
||||
{
|
||||
m_inst = inst;
|
||||
@ -34,7 +36,7 @@ void AssetUpdateTask::executeTask()
|
||||
entry->setStale(true);
|
||||
auto hexSha1 = assets->sha1.toLatin1();
|
||||
qDebug() << "Asset index SHA1:" << hexSha1;
|
||||
auto dl = Net::Download::makeCached(indexUrl, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
|
||||
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
job->addNetAction(dl);
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
|
||||
{
|
||||
m_inst = inst;
|
||||
@ -68,7 +70,7 @@ void FMLLibrariesTask::executeTask()
|
||||
{
|
||||
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
|
||||
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
|
||||
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
|
||||
dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options));
|
||||
}
|
||||
|
||||
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);
|
||||
|
@ -53,6 +53,8 @@
|
||||
#include "meta/Version.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include "BuildConfig.h"
|
||||
#include "Application.h"
|
||||
|
||||
@ -82,9 +84,9 @@ void PackInstallTask::executeTask()
|
||||
{
|
||||
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
|
||||
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
|
||||
auto searchUrl =
|
||||
auto searchUrl =
|
||||
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
@ -659,7 +661,7 @@ void PackInstallTask::installConfigs()
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
|
||||
entry->setStale(true);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
if (!m_version.configs.sha1.isEmpty()) {
|
||||
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
|
||||
@ -782,7 +784,7 @@ void PackInstallTask::downloadMods()
|
||||
entry->setStale(true);
|
||||
modsToExtract.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
@ -794,7 +796,7 @@ void PackInstallTask::downloadMods()
|
||||
entry->setStale(true);
|
||||
modsToDecomp.insert(entry->getFullPath(), mod);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
@ -808,7 +810,7 @@ void PackInstallTask::downloadMods()
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
|
||||
entry->setStale(true);
|
||||
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
auto dl = Net::ApiDownload::makeCached(url, entry);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
|
@ -1,7 +1,9 @@
|
||||
#include "FileResolvingTask.h"
|
||||
|
||||
#include "Json.h"
|
||||
#include "net/ApiUpload.h"
|
||||
#include "net/Upload.h"
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
||||
|
||||
@ -38,7 +40,7 @@ void Flame::FileResolvingTask::executeTask()
|
||||
return l;
|
||||
}));
|
||||
QByteArray data = Json::toText(object);
|
||||
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
|
||||
auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
|
||||
m_dljob->addNetAction(dl);
|
||||
|
||||
auto step_progress = std::make_shared<TaskStepProgress>();
|
||||
@ -99,7 +101,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
if (!hash.isEmpty()) {
|
||||
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
|
||||
auto output = std::make_shared<QByteArray>();
|
||||
auto dl = Net::Download::makeByteArray(QUrl(url), output);
|
||||
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
|
||||
|
||||
m_checkJob->addNetAction(dl);
|
||||
@ -171,7 +173,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
|
||||
auto projectId = mod->projectId;
|
||||
auto output = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
||||
auto dl = Net::Download::makeByteArray(url, output);
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, output);
|
||||
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
|
||||
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
|
||||
|
@ -8,7 +8,9 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
#include "net/ApiUpload.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/Upload.h"
|
||||
|
||||
Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response)
|
||||
@ -26,7 +28,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
|
||||
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
|
||||
|
||||
return netJob;
|
||||
}
|
||||
@ -38,7 +40,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
netJob->addNetAction(Net::Download::makeByteArray(
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
|
||||
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
|
||||
response));
|
||||
@ -74,7 +76,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
|
||||
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
netJob->addNetAction(
|
||||
Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
|
||||
Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -113,7 +115,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
ModPlatform::IndexedVersion ver;
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -173,7 +175,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
|
||||
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
||||
|
||||
@ -195,7 +197,7 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
|
||||
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
|
||||
|
||||
|
@ -13,6 +13,8 @@
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
static FlameAPI api;
|
||||
|
||||
bool FlameCheckUpdate::abort()
|
||||
@ -33,7 +35,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
|
||||
auto dl = Net::Download::makeByteArray(url, response);
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||
get_project_job->addNetAction(dl);
|
||||
|
||||
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
|
||||
@ -77,7 +79,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
|
||||
auto dl = Net::Download::makeByteArray(url, response);
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||
get_file_info_job->addNetAction(dl);
|
||||
|
||||
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include "meta/VersionList.h"
|
||||
#include "minecraft/World.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
static const FlameAPI api;
|
||||
|
||||
@ -523,7 +524,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
case Flame::File::Type::Mod: {
|
||||
if (!result.url.isEmpty()) {
|
||||
qDebug() << "Will download" << result.url << "to" << path;
|
||||
auto dl = Net::Download::makeFile(result.url, path);
|
||||
auto dl = Net::ApiDownload::makeFile(result.url, path);
|
||||
m_files_job->addNetAction(dl);
|
||||
}
|
||||
break;
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <QMessageBox>
|
||||
#include <QtConcurrentRun>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include "Json.h"
|
||||
#include "MMCZip.h"
|
||||
@ -64,20 +65,11 @@ void FlamePackExportTask::executeTask()
|
||||
|
||||
bool FlamePackExportTask::abort()
|
||||
{
|
||||
if (task != nullptr) {
|
||||
if (task) {
|
||||
task->abort();
|
||||
task = nullptr;
|
||||
emitAborted();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (buildZipFuture.isRunning()) {
|
||||
buildZipFuture.cancel();
|
||||
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
|
||||
// immediately.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -336,89 +328,40 @@ void FlamePackExportTask::buildZip()
|
||||
setStatus(tr("Adding files..."));
|
||||
setProgress(4, 5);
|
||||
|
||||
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
||||
QuaZip zip(output);
|
||||
if (!zip.open(QuaZip::mdCreate)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not create file"));
|
||||
}
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("manifest.json", generateIndex());
|
||||
zipTask->addExtraFile("modlist.html", generateHTML());
|
||||
|
||||
if (buildZipFuture.isCanceled())
|
||||
return BuildZipResult();
|
||||
QStringList exclude;
|
||||
std::transform(resolvedFiles.keyBegin(), resolvedFiles.keyEnd(), std::back_insert_iterator(exclude),
|
||||
[this](QString file) { return gameRoot.relativeFilePath(file); });
|
||||
zipTask->setExcludeFiles(exclude);
|
||||
|
||||
QuaZipFile indexFile(&zip);
|
||||
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>();
|
||||
|
||||
size_t progress = 0;
|
||||
for (const QFileInfo& file : files) {
|
||||
if (buildZipFuture.isCanceled()) {
|
||||
QFile::remove(output);
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
return BuildZipResult();
|
||||
}
|
||||
progressStep->update(progress, files.length());
|
||||
stepProgress(*progressStep);
|
||||
|
||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||
if (!resolvedFiles.contains(file.absoluteFilePath()) &&
|
||||
!JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
||||
}
|
||||
progress++;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
if (zip.getZipError() != 0) {
|
||||
QFile::remove(output);
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
return BuildZipResult(tr("A zip error occurred"));
|
||||
}
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||
progressStep->state = TaskStepState::Succeeded;
|
||||
stepProgress(*progressStep);
|
||||
return BuildZipResult();
|
||||
});
|
||||
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &FlamePackExportTask::finish);
|
||||
buildZipWatcher.setFuture(buildZipFuture);
|
||||
}
|
||||
|
||||
void FlamePackExportTask::finish()
|
||||
{
|
||||
if (buildZipFuture.isCanceled())
|
||||
emitAborted();
|
||||
else {
|
||||
const BuildZipResult result = buildZipFuture.result();
|
||||
if (result.has_value())
|
||||
emitFailed(result.value());
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
connect(zipTask.get(), &Task::succeeded, this, &FlamePackExportTask::emitSucceeded);
|
||||
connect(zipTask.get(), &Task::aborted, this, &FlamePackExportTask::emitAborted);
|
||||
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(zipTask.get(), &Task::stepProgress, this, &FlamePackExportTask::propagateStepProgress);
|
||||
|
||||
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||
progressStep->update(current, total);
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
task.reset(zipTask);
|
||||
zipTask->start();
|
||||
}
|
||||
|
||||
QByteArray FlamePackExportTask::generateIndex()
|
||||
@ -471,3 +414,18 @@ QByteArray FlamePackExportTask::generateIndex()
|
||||
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QByteArray FlamePackExportTask::generateHTML()
|
||||
{
|
||||
QString content = "";
|
||||
for (auto mod : resolvedFiles) {
|
||||
if (mod.isMod) {
|
||||
content += QString(TEMPLATE)
|
||||
.replace("{name}", mod.name.toHtmlEscaped())
|
||||
.replace("{url}", ModPlatform::getMetaURL(ModPlatform::ResourceProvider::FLAME, mod.addonId).toHtmlEscaped())
|
||||
.replace("{authors}", !mod.authors.isEmpty() ? QString(" (by %1)").arg(mod.authors).toHtmlEscaped() : "");
|
||||
}
|
||||
}
|
||||
content = "<ul>" + content + "</ul>";
|
||||
return content.toUtf8();
|
||||
}
|
@ -19,8 +19,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "BaseInstance.h"
|
||||
#include "MMCZip.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
@ -52,7 +50,6 @@ class FlamePackExportTask : public Task {
|
||||
const QString output;
|
||||
const MMCZip::FilterFunction filter;
|
||||
|
||||
typedef std::optional<QString> BuildZipResult;
|
||||
struct ResolvedFile {
|
||||
int addonId;
|
||||
int version;
|
||||
@ -76,15 +73,13 @@ class FlamePackExportTask : public Task {
|
||||
QMap<QString, HashInfo> pendingHashes{};
|
||||
QMap<QString, ResolvedFile> resolvedFiles{};
|
||||
Task::Ptr task;
|
||||
QFuture<BuildZipResult> buildZipFuture;
|
||||
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
||||
|
||||
void collectFiles();
|
||||
void collectHashes();
|
||||
void makeApiRequest();
|
||||
void getProjectsInfo();
|
||||
void buildZip();
|
||||
void finish();
|
||||
|
||||
QByteArray generateIndex();
|
||||
QByteArray generateHTML();
|
||||
};
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&& callbacks) const
|
||||
{
|
||||
auto search_url_optional = getSearchURL(args);
|
||||
@ -23,7 +25,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto netJob = makeShared<NetJob>(QString("%1::Search").arg(debugName()), APPLICATION->network());
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(search_url), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -85,7 +87,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
||||
auto netJob = makeShared<NetJob>(QString("%1::Versions").arg(args.pack.name), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -113,7 +115,7 @@ Task::Ptr NetworkResourceAPI::getProject(QString addonId, std::shared_ptr<QByteA
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(project_url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(project_url), response));
|
||||
|
||||
return netJob;
|
||||
}
|
||||
|
87
launcher/modplatform/import_ftb/PackHelpers.cpp
Normal file
87
launcher/modplatform/import_ftb/PackHelpers.cpp
Normal 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
|
55
launcher/modplatform/import_ftb/PackHelpers.h
Normal file
55
launcher/modplatform/import_ftb/PackHelpers.h
Normal 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)
|
99
launcher/modplatform/import_ftb/PackInstallTask.cpp
Normal file
99
launcher/modplatform/import_ftb/PackInstallTask.cpp
Normal 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
|
49
launcher/modplatform/import_ftb/PackInstallTask.h
Normal file
49
launcher/modplatform/import_ftb/PackInstallTask.h
Normal 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
|
@ -40,6 +40,8 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
void PackFetchTask::fetch()
|
||||
@ -51,7 +53,7 @@ void PackFetchTask::fetch()
|
||||
|
||||
QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
|
||||
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
|
||||
jobPtr->addNetAction(Net::Download::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
|
||||
jobPtr->addNetAction(Net::ApiDownload::makeByteArray(publicPacksUrl, publicModpacksXmlFileData));
|
||||
|
||||
QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
|
||||
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
|
||||
@ -71,7 +73,7 @@ void PackFetchTask::fetchPrivate(const QStringList& toFetch)
|
||||
for (auto& packCode : toFetch) {
|
||||
auto data = std::make_shared<QByteArray>();
|
||||
NetJob* job = new NetJob("Fetching private pack", m_network);
|
||||
job->addNetAction(Net::Download::makeByteArray(privatePackBaseUrl.arg(packCode), data));
|
||||
job->addNetAction(Net::ApiDownload::makeByteArray(privatePackBaseUrl.arg(packCode), data));
|
||||
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode] {
|
||||
ModpackList packs;
|
||||
|
@ -48,6 +48,8 @@
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version)
|
||||
@ -77,7 +79,7 @@ void PackInstallTask::downloadPack()
|
||||
} else {
|
||||
url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(archivePath);
|
||||
}
|
||||
netJobContainer->addNetAction(Net::Download::makeFile(url, archivePath));
|
||||
netJobContainer->addNetAction(Net::ApiDownload::makeFile(url, archivePath));
|
||||
|
||||
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::unzip);
|
||||
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::emitFailed);
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include "Application.h"
|
||||
#include "Json.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/ApiUpload.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/Upload.h"
|
||||
|
||||
@ -13,7 +15,7 @@ Task::Ptr ModrinthAPI::currentVersion(QString hash, QString hash_format, std::sh
|
||||
{
|
||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetCurrentVersion"), APPLICATION->network());
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1?algorithm=%2").arg(hash, hash_format), response));
|
||||
|
||||
return netJob;
|
||||
@ -31,7 +33,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
|
||||
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files"), response, body_raw));
|
||||
|
||||
return netJob;
|
||||
}
|
||||
@ -60,7 +62,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(
|
||||
netJob->addNetAction(Net::ApiUpload::makeByteArray(
|
||||
QString(BuildConfig.MODRINTH_PROD_URL + "/version_file/%1/update?algorithm=%2").arg(hash, hash_format), response, body_raw));
|
||||
|
||||
return netJob;
|
||||
@ -93,7 +95,8 @@ Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
|
||||
QJsonDocument body(body_obj);
|
||||
auto body_raw = body.toJson();
|
||||
|
||||
netJob->addNetAction(Net::Upload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
|
||||
netJob->addNetAction(
|
||||
Net::ApiUpload::makeByteArray(QString(BuildConfig.MODRINTH_PROD_URL + "/version_files/update"), response, body_raw));
|
||||
|
||||
return netJob;
|
||||
}
|
||||
@ -103,7 +106,7 @@ Task::Ptr ModrinthAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteAr
|
||||
auto netJob = makeShared<NetJob>(QString("Modrinth::GetProjects"), APPLICATION->network());
|
||||
auto searchUrl = getMultipleModInfoURL(addonIds);
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
|
||||
return netJob;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "net/ChecksumValidator.h"
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
@ -238,7 +239,7 @@ bool ModrinthCreationTask::createInstance()
|
||||
}
|
||||
|
||||
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
|
||||
auto dl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
||||
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||
m_files_job->addNetAction(dl);
|
||||
|
||||
@ -247,7 +248,7 @@ bool ModrinthCreationTask::createInstance()
|
||||
// MultipleOptionsTask's , once those exist :)
|
||||
auto param = dl.toWeakRef();
|
||||
connect(dl.get(), &NetAction::failed, [this, &file, file_path, param] {
|
||||
auto ndl = Net::Download::makeFile(file.downloads.dequeue(), file_path);
|
||||
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
|
||||
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
|
||||
m_files_job->addNetAction(ndl);
|
||||
if (auto shared = param.lock()) shared->succeeded();
|
||||
|
@ -55,20 +55,11 @@ void ModrinthPackExportTask::executeTask()
|
||||
|
||||
bool ModrinthPackExportTask::abort()
|
||||
{
|
||||
if (task != nullptr) {
|
||||
if (task) {
|
||||
task->abort();
|
||||
task = nullptr;
|
||||
emitAborted();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (buildZipFuture.isRunning()) {
|
||||
buildZipFuture.cancel();
|
||||
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur
|
||||
// immediately.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -205,63 +196,36 @@ void ModrinthPackExportTask::buildZip()
|
||||
{
|
||||
setStatus(tr("Adding files..."));
|
||||
|
||||
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
||||
QuaZip zip(output);
|
||||
if (!zip.open(QuaZip::mdCreate)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not create file"));
|
||||
}
|
||||
auto zipTask = makeShared<MMCZip::ExportToZipTask>(output, gameRoot, files, "overrides/", true);
|
||||
zipTask->addExtraFile("modrinth.index.json", generateIndex());
|
||||
|
||||
if (buildZipFuture.isCanceled())
|
||||
return BuildZipResult();
|
||||
zipTask->setExcludeFiles(resolvedFiles.keys());
|
||||
|
||||
QuaZipFile indexFile(&zip);
|
||||
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not create index"));
|
||||
}
|
||||
indexFile.write(generateIndex());
|
||||
|
||||
size_t progress = 0;
|
||||
for (const QFileInfo& file : files) {
|
||||
if (buildZipFuture.isCanceled()) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult();
|
||||
}
|
||||
|
||||
setProgress(progress, files.length());
|
||||
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
||||
}
|
||||
progress++;
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
if (zip.getZipError() != 0) {
|
||||
QFile::remove(output);
|
||||
return BuildZipResult(tr("A zip error occurred"));
|
||||
}
|
||||
|
||||
return BuildZipResult();
|
||||
auto progressStep = std::make_shared<TaskStepProgress>();
|
||||
connect(zipTask.get(), &Task::finished, this, [this, progressStep] {
|
||||
progressStep->state = TaskStepState::Succeeded;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
|
||||
buildZipWatcher.setFuture(buildZipFuture);
|
||||
}
|
||||
|
||||
void ModrinthPackExportTask::finish()
|
||||
{
|
||||
if (buildZipFuture.isCanceled())
|
||||
emitAborted();
|
||||
else {
|
||||
const BuildZipResult result = buildZipFuture.result();
|
||||
if (result.has_value())
|
||||
emitFailed(result.value());
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
connect(zipTask.get(), &Task::succeeded, this, &ModrinthPackExportTask::emitSucceeded);
|
||||
connect(zipTask.get(), &Task::aborted, this, &ModrinthPackExportTask::emitAborted);
|
||||
connect(zipTask.get(), &Task::failed, this, [this, progressStep](QString reason) {
|
||||
progressStep->state = TaskStepState::Failed;
|
||||
stepProgress(*progressStep);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(zipTask.get(), &Task::stepProgress, this, &ModrinthPackExportTask::propagateStepProgress);
|
||||
|
||||
connect(zipTask.get(), &Task::progress, this, [this, progressStep](qint64 current, qint64 total) {
|
||||
progressStep->update(current, total);
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
connect(zipTask.get(), &Task::status, this, [this, progressStep](QString status) {
|
||||
progressStep->status = status;
|
||||
stepProgress(*progressStep);
|
||||
});
|
||||
task.reset(zipTask);
|
||||
zipTask->start();
|
||||
}
|
||||
|
||||
QByteArray ModrinthPackExportTask::generateIndex()
|
||||
|
@ -56,22 +56,17 @@ class ModrinthPackExportTask : public Task {
|
||||
const QString output;
|
||||
const MMCZip::FilterFunction filter;
|
||||
|
||||
typedef std::optional<QString> BuildZipResult;
|
||||
|
||||
ModrinthAPI api;
|
||||
QFileInfoList files;
|
||||
QMap<QString, QString> pendingHashes;
|
||||
QMap<QString, ResolvedFile> resolvedFiles;
|
||||
Task::Ptr task;
|
||||
QFuture<BuildZipResult> buildZipFuture;
|
||||
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
||||
|
||||
void collectFiles();
|
||||
void collectHashes();
|
||||
void makeApiRequest();
|
||||
void parseApiResponse(const std::shared_ptr<QByteArray> response);
|
||||
void buildZip();
|
||||
void finish();
|
||||
|
||||
QByteArray generateIndex();
|
||||
};
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
|
||||
{
|
||||
m_sourceUrl = sourceUrl;
|
||||
@ -45,7 +47,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
|
||||
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
|
||||
entry->setStale(true);
|
||||
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
|
||||
m_archivePath = entry->getFullPath();
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "SolderPackManifest.h"
|
||||
#include "TechnicPackProcessor.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
Technic::SolderPackInstallTask::SolderPackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network,
|
||||
const QUrl& solderUrl,
|
||||
@ -71,7 +72,7 @@ void Technic::SolderPackInstallTask::executeTask()
|
||||
|
||||
m_filesNetJob.reset(new NetJob(tr("Resolving modpack files"), m_network));
|
||||
auto sourceUrl = QString("%1/modpack/%2/%3").arg(m_solderUrl.toString(), m_pack, m_version);
|
||||
m_filesNetJob->addNetAction(Net::Download::makeByteArray(sourceUrl, m_response));
|
||||
m_filesNetJob->addNetAction(Net::ApiDownload::makeByteArray(sourceUrl, m_response));
|
||||
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
|
||||
@ -112,7 +113,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
|
||||
for (const auto& mod : build.mods) {
|
||||
auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
|
||||
|
||||
auto dl = Net::Download::makeFile(mod.url, path);
|
||||
auto dl = Net::ApiDownload::makeFile(mod.url, path);
|
||||
if (!mod.md5.isEmpty()) {
|
||||
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
|
||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
|
||||
|
66
launcher/net/ApiDownload.cpp
Normal file
66
launcher/net/ApiDownload.cpp
Normal 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
|
38
launcher/net/ApiDownload.h
Normal file
38
launcher/net/ApiDownload.h
Normal 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
|
49
launcher/net/ApiHeaderProxy.h
Normal file
49
launcher/net/ApiHeaderProxy.h
Normal 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
|
43
launcher/net/ApiUpload.cpp
Normal file
43
launcher/net/ApiUpload.cpp
Normal 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
36
launcher/net/ApiUpload.h
Normal 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
|
@ -42,8 +42,6 @@ namespace Net {
|
||||
|
||||
/*
|
||||
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
||||
* FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
|
||||
* causing a segmentation fault.
|
||||
*/
|
||||
class ByteArraySink : public Sink {
|
||||
public:
|
||||
|
@ -47,17 +47,11 @@
|
||||
#include "ChecksumValidator.h"
|
||||
#include "MetaCacheSink.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include "net/Logging.h"
|
||||
#include "net/NetAction.h"
|
||||
|
||||
#include "MMCTime.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
namespace Net {
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
||||
{
|
||||
auto dl = makeShared<Download>();
|
||||
@ -69,6 +63,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
|
||||
dl->m_sink.reset(cachedNode);
|
||||
return dl;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto Download::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options) -> Download::Ptr
|
||||
{
|
||||
@ -90,260 +85,8 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
|
||||
return dl;
|
||||
}
|
||||
|
||||
void Download::addValidator(Validator* v)
|
||||
QNetworkReply* Download::getReply(QNetworkRequest& request)
|
||||
{
|
||||
m_sink->addValidator(v);
|
||||
}
|
||||
|
||||
void Download::executeTask()
|
||||
{
|
||||
setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
|
||||
|
||||
if (getState() == Task::State::AbortedByUser) {
|
||||
qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
|
||||
emitAborted();
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request(m_url);
|
||||
m_state = m_sink->init(request);
|
||||
switch (m_state) {
|
||||
case State::Succeeded:
|
||||
emit succeeded();
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
|
||||
return;
|
||||
case State::Running:
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
|
||||
break;
|
||||
case State::Inactive:
|
||||
case State::Failed:
|
||||
emitFailed();
|
||||
return;
|
||||
case State::AbortedByUser:
|
||||
emitAborted();
|
||||
return;
|
||||
}
|
||||
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8());
|
||||
// TODO remove duplication
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
|
||||
request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8());
|
||||
} else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
|
||||
request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
|
||||
QString token = APPLICATION->getModrinthAPIToken();
|
||||
if (!token.isNull())
|
||||
request.setRawHeader("Authorization", token.toUtf8());
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
request.setTransferTimeout();
|
||||
#endif
|
||||
|
||||
m_last_progress_time = m_clock.now();
|
||||
m_last_progress_bytes = 0;
|
||||
|
||||
QNetworkReply* rep = m_network->get(request);
|
||||
m_reply.reset(rep);
|
||||
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
|
||||
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Download::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
||||
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
||||
}
|
||||
|
||||
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
auto now = m_clock.now();
|
||||
auto elapsed = now - m_last_progress_time;
|
||||
|
||||
// use milliseconds for speed precision
|
||||
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
||||
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
|
||||
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
|
||||
auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
|
||||
|
||||
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
|
||||
QString dl_progress =
|
||||
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
|
||||
|
||||
QString dl_speed_str;
|
||||
if (elapsed_ms.count() > 0) {
|
||||
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
|
||||
//: Download speed, in bytes per second (remaining download time in parenthesis)
|
||||
dl_speed_str =
|
||||
tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
|
||||
} else {
|
||||
//: Download speed at 0 bytes per second
|
||||
dl_speed_str = tr("0 B/s");
|
||||
}
|
||||
|
||||
setDetails(dl_progress + "\n" + dl_speed_str);
|
||||
|
||||
setProgress(bytesReceived, bytesTotal);
|
||||
}
|
||||
|
||||
void Download::downloadError(QNetworkReply::NetworkError error)
|
||||
{
|
||||
if (error == QNetworkReply::OperationCanceledError) {
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
|
||||
m_state = State::AbortedByUser;
|
||||
} else {
|
||||
if (m_options & Option::AcceptLocalFiles) {
|
||||
if (m_sink->hasLocalData()) {
|
||||
m_state = State::Succeeded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// error happened during download.
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
||||
m_state = State::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
void Download::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
|
||||
<< error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
auto Download::handleRedirect() -> bool
|
||||
{
|
||||
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||
if (!redirect.isValid()) {
|
||||
if (!m_reply->hasRawHeader("Location")) {
|
||||
// no redirect -> it's fine to continue
|
||||
return false;
|
||||
}
|
||||
// there is a Location header, but it's not correct. we need to apply some workarounds...
|
||||
QByteArray redirectBA = m_reply->rawHeader("Location");
|
||||
if (redirectBA.size() == 0) {
|
||||
// empty, yet present redirect header? WTF?
|
||||
return false;
|
||||
}
|
||||
QString redirectStr = QString::fromUtf8(redirectBA);
|
||||
|
||||
if (redirectStr.startsWith("//")) {
|
||||
/*
|
||||
* IF the URL begins with //, we need to insert the URL scheme.
|
||||
* See: https://bugreports.qt.io/browse/QTBUG-41061
|
||||
* See: http://tools.ietf.org/html/rfc3986#section-4.2
|
||||
*/
|
||||
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
|
||||
} else if (redirectStr.startsWith("/")) {
|
||||
/*
|
||||
* IF the URL begins with /, we need to process it as a relative URL
|
||||
*/
|
||||
auto url = m_reply->url();
|
||||
url.setPath(redirectStr, QUrl::TolerantMode);
|
||||
redirectStr = url.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
|
||||
* FIXME: report Qt bug for this
|
||||
*/
|
||||
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
||||
if (!redirect.isValid()) {
|
||||
qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
|
||||
downloadError(QNetworkReply::ProtocolFailure);
|
||||
return false;
|
||||
}
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
|
||||
} else {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
|
||||
}
|
||||
|
||||
m_url = QUrl(redirect.toString());
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||
startAction(m_network);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Download::downloadFinished()
|
||||
{
|
||||
// handle HTTP redirection first
|
||||
if (handleRedirect()) {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the download failed before this point ...
|
||||
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
||||
{
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
m_reply.reset();
|
||||
emit succeeded();
|
||||
return;
|
||||
} else if (m_state == State::Failed) {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
m_reply.reset();
|
||||
emit failed("");
|
||||
return;
|
||||
} else if (m_state == State::AbortedByUser) {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
m_reply.reset();
|
||||
emit aborted();
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we got all the remaining data, if any
|
||||
auto data = m_reply->readAll();
|
||||
if (data.size()) {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
|
||||
m_state = m_sink->write(data);
|
||||
}
|
||||
|
||||
// otherwise, finalize the whole graph
|
||||
m_state = m_sink->finalize(*m_reply.get());
|
||||
if (m_state != State::Succeeded) {
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
|
||||
m_sink->abort();
|
||||
m_reply.reset();
|
||||
emit failed("");
|
||||
return;
|
||||
}
|
||||
|
||||
m_reply.reset();
|
||||
qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
|
||||
emit succeeded();
|
||||
}
|
||||
|
||||
void Download::downloadReadyRead()
|
||||
{
|
||||
if (m_state == State::Running) {
|
||||
auto data = m_reply->readAll();
|
||||
m_state = m_sink->write(data);
|
||||
if (m_state == State::Failed) {
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
|
||||
}
|
||||
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||
} else {
|
||||
qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Net
|
||||
|
||||
auto Net::Download::abort() -> bool
|
||||
{
|
||||
if (m_reply) {
|
||||
m_reply->abort();
|
||||
} else {
|
||||
m_state = State::AbortedByUser;
|
||||
}
|
||||
return true;
|
||||
return m_network->get(request);
|
||||
}
|
||||
} // namespace Net
|
@ -38,57 +38,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "HttpMetaCache.h"
|
||||
#include "NetAction.h"
|
||||
#include "Sink.h"
|
||||
#include "Validator.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
namespace Net {
|
||||
class Download : public NetAction {
|
||||
class Download : public NetRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<class Download>;
|
||||
enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 };
|
||||
Q_DECLARE_FLAGS(Options, Option)
|
||||
|
||||
public:
|
||||
~Download() override = default;
|
||||
explicit Download() : NetRequest() { logCat = taskDownloadLogC; }
|
||||
|
||||
#if defined(LAUNCHER_APPLICATION)
|
||||
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
#endif
|
||||
|
||||
static auto makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
||||
|
||||
public:
|
||||
void addValidator(Validator* v);
|
||||
auto abort() -> bool override;
|
||||
auto canAbort() const -> bool override { return true; }
|
||||
|
||||
private:
|
||||
auto handleRedirect() -> bool;
|
||||
|
||||
protected slots:
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||
void downloadError(QNetworkReply::NetworkError error) override;
|
||||
void sslErrors(const QList<QSslError>& errors) override;
|
||||
void downloadFinished() override;
|
||||
void downloadReadyRead() override;
|
||||
|
||||
public slots:
|
||||
void executeTask() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Sink> m_sink;
|
||||
Options m_options;
|
||||
|
||||
std::chrono::steady_clock m_clock;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
||||
qint64 m_last_progress_bytes;
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
};
|
||||
} // namespace Net
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
|
||||
|
49
launcher/net/HeaderProxy.h
Normal file
49
launcher/net/HeaderProxy.h
Normal 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
|
@ -42,6 +42,8 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "HeaderProxy.h"
|
||||
|
||||
class NetAction : public Task {
|
||||
Q_OBJECT
|
||||
protected:
|
||||
@ -56,13 +58,17 @@ class NetAction : public Task {
|
||||
|
||||
void setNetwork(shared_qobject_ptr<QNetworkAccessManager> network) { m_network = network; }
|
||||
|
||||
void addHeaderProxy(Net::HeaderProxy* proxy) { m_headerProxies.push_back(std::shared_ptr<Net::HeaderProxy>(proxy)); }
|
||||
virtual void init() = 0;
|
||||
|
||||
protected slots:
|
||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
||||
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
||||
virtual void downloadFinished() = 0;
|
||||
virtual void downloadReadyRead() = 0;
|
||||
|
||||
virtual void sslErrors(const QList<QSslError>& errors) {
|
||||
virtual void sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
||||
@ -70,7 +76,6 @@ class NetAction : public Task {
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public slots:
|
||||
@ -91,4 +96,5 @@ class NetAction : public Task {
|
||||
|
||||
/// source URL
|
||||
QUrl m_url;
|
||||
std::vector<std::shared_ptr<Net::HeaderProxy>> m_headerProxies;
|
||||
};
|
||||
|
331
launcher/net/NetRequest.cpp
Normal file
331
launcher/net/NetRequest.cpp
Normal 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
99
launcher/net/NetRequest.h
Normal 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)
|
44
launcher/net/RawHeaderProxy.h
Normal file
44
launcher/net/RawHeaderProxy.h
Normal 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
|
@ -38,219 +38,16 @@
|
||||
|
||||
#include "Upload.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "Application.h"
|
||||
#include "BuildConfig.h"
|
||||
#include "ByteArraySink.h"
|
||||
|
||||
#include "net/Logging.h"
|
||||
|
||||
namespace Net {
|
||||
|
||||
bool Upload::abort()
|
||||
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");
|
||||
QNetworkReply* rep = m_network->post(request, m_post_data);
|
||||
|
||||
m_reply.reset(rep);
|
||||
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
|
||||
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
|
||||
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
|
||||
return m_network->post(request, m_post_data);
|
||||
}
|
||||
|
||||
Upload::Ptr Upload::makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data)
|
||||
|
@ -37,36 +37,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "NetAction.h"
|
||||
#include "Sink.h"
|
||||
#include "net/NetRequest.h"
|
||||
|
||||
namespace Net {
|
||||
|
||||
class Upload : public NetAction {
|
||||
class Upload : public NetRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<Upload>;
|
||||
explicit Upload() : NetRequest() { logCat = taskUploadLogC; };
|
||||
|
||||
static Upload::Ptr makeByteArray(QUrl url, std::shared_ptr<QByteArray> output, QByteArray m_post_data);
|
||||
auto abort() -> bool override;
|
||||
auto canAbort() const -> bool override { return true; };
|
||||
|
||||
protected slots:
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||
void downloadError(QNetworkReply::NetworkError error) override;
|
||||
void sslErrors(const QList<QSslError>& errors) override;
|
||||
void downloadFinished() override;
|
||||
void downloadReadyRead() override;
|
||||
|
||||
public slots:
|
||||
void executeTask() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Sink> m_sink;
|
||||
protected:
|
||||
virtual QNetworkReply* getReply(QNetworkRequest&) override;
|
||||
QByteArray m_post_data;
|
||||
|
||||
bool handleRedirect();
|
||||
};
|
||||
|
||||
} // namespace Net
|
||||
|
@ -57,6 +57,8 @@ public:
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void init() override {};
|
||||
|
||||
protected
|
||||
slots:
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||
|
@ -46,6 +46,7 @@ public:
|
||||
static Ptr make(ScreenShot::Ptr shot) {
|
||||
return Ptr(new ImgurUpload(shot));
|
||||
}
|
||||
void init() override {};
|
||||
|
||||
protected
|
||||
slots:
|
||||
|
@ -1345,16 +1345,10 @@ void MainWindow::on_actionExportInstanceFlamePack_triggered()
|
||||
if (m_selectedInstance) {
|
||||
auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
|
||||
if (instance) {
|
||||
QString errorMsg;
|
||||
if (instance->getPackProfile()->getComponent("org.quiltmc.quilt-loader")) {
|
||||
errorMsg = tr("Quilt is currently not supported by CurseForge modpacks.");
|
||||
} else if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
|
||||
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
|
||||
errorMsg = tr("Snapshots are currently not supported by CurseForge modpacks.");
|
||||
}
|
||||
if (!errorMsg.isEmpty()) {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(errorMsg);
|
||||
if (auto cmp = instance->getPackProfile()->getComponent("net.minecraft");
|
||||
cmp && cmp->getVersionFile() && cmp->getVersionFile()->type == "snapshot") {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setText("Snapshots are currently not supported by CurseForge modpacks.");
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
|
||||
QString urlText("<html><head/><body><p><a href=\"%1\">%1</a></p></body></html>");
|
||||
ui->urlLabel->setText(urlText.arg(BuildConfig.LAUNCHER_GIT));
|
||||
|
||||
QString copyText("© 2022 %1");
|
||||
QString copyText("© 2022-2023 %1");
|
||||
ui->copyLabel->setText(copyText.arg(BuildConfig.LAUNCHER_COPYRIGHT));
|
||||
|
||||
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
|
||||
|
@ -33,38 +33,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Application.h"
|
||||
#include "NewInstanceDialog.h"
|
||||
#include "Application.h"
|
||||
#include "ui/pages/modplatform/import_ftb/ImportFTBPage.h"
|
||||
#include "ui_NewInstanceDialog.h"
|
||||
|
||||
#include <BaseVersion.h>
|
||||
#include <InstanceList.h>
|
||||
#include <icons/IconList.h>
|
||||
#include <tasks/Task.h>
|
||||
#include <InstanceList.h>
|
||||
|
||||
#include "VersionSelectDialog.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "IconPickerDialog.h"
|
||||
#include "ProgressDialog.h"
|
||||
#include "VersionSelectDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QValidator>
|
||||
#include <QDialogButtonBox>
|
||||
#include <utility>
|
||||
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
#include "ui/pages/modplatform/CustomPage.h"
|
||||
#include "ui/pages/modplatform/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/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/technic/TechnicPage.h"
|
||||
#include "ui/widgets/PageContainer.h"
|
||||
|
||||
|
||||
|
||||
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
|
||||
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
|
||||
: QDialog(parent), ui(new Ui::NewInstanceDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@ -168,6 +167,7 @@ QList<BasePage *> NewInstanceDialog::getPages()
|
||||
if (APPLICATION->capabilities() & Application::SupportsFlame)
|
||||
pages.append(new FlamePage(this));
|
||||
pages.append(new LegacyFTB::Page(this));
|
||||
pages.append(new FTBImportAPP::ImportFTBPage(this));
|
||||
pages.append(new ModrinthPage(this));
|
||||
pages.append(new TechnicPage(this));
|
||||
|
||||
@ -284,28 +284,27 @@ QString NewInstanceDialog::iconKey() const
|
||||
|
||||
void NewInstanceDialog::on_iconButton_clicked()
|
||||
{
|
||||
importIconNow(); //so the user can switch back
|
||||
importIconNow(); // so the user can switch back
|
||||
IconPickerDialog dlg(this);
|
||||
dlg.execWithSelection(InstIconKey);
|
||||
|
||||
if (dlg.result() == QDialog::Accepted)
|
||||
{
|
||||
if (dlg.result() == QDialog::Accepted) {
|
||||
InstIconKey = dlg.selectedIconKey;
|
||||
ui->iconButton->setIcon(APPLICATION->icons()->getIcon(InstIconKey));
|
||||
importIcon = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString &arg1)
|
||||
void NewInstanceDialog::on_instNameTextBox_textChanged([[maybe_unused]] const QString& arg1)
|
||||
{
|
||||
updateDialogState();
|
||||
}
|
||||
|
||||
void NewInstanceDialog::importIconNow()
|
||||
{
|
||||
if(importIcon) {
|
||||
if (importIcon) {
|
||||
APPLICATION->icons()->installIcon(importIconPath, importIconName);
|
||||
InstIconKey = importIconName;
|
||||
InstIconKey = importIconName.mid(0, importIconName.lastIndexOf('.'));
|
||||
importIcon = false;
|
||||
}
|
||||
APPLICATION->settings()->set("NewInstanceGeometry", saveGeometry().toBase64());
|
||||
|
@ -42,7 +42,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -47,7 +47,7 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="jprofilerPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -84,7 +84,7 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="jvisualvmPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -121,7 +121,7 @@
|
||||
<item>
|
||||
<widget class="QPushButton" name="mceditPathBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -166,7 +166,7 @@
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="jsonEditorBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -160,117 +160,7 @@
|
||||
<string>Java Runtime</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="1">
|
||||
<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>&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>&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>&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>&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 &Wizard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="2">
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="QPlainTextEdit" name="jvmArgsTextBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
@ -289,6 +179,114 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>&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>&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>&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>&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 &Wizard</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -317,8 +315,6 @@
|
||||
<tabstop>permGenSpinBox</tabstop>
|
||||
<tabstop>javaBrowseBtn</tabstop>
|
||||
<tabstop>javaPathTextBox</tabstop>
|
||||
<tabstop>javaDetectBtn</tabstop>
|
||||
<tabstop>javaTestBtn</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
@ -99,7 +99,7 @@
|
||||
<item row="3" column="2">
|
||||
<widget class="QToolButton" name="downloadsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -109,7 +109,7 @@
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="modsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -126,14 +126,14 @@
|
||||
<item row="0" column="2">
|
||||
<widget class="QToolButton" name="instDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QToolButton" name="iconsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string notr="true">...</string>
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -61,31 +61,7 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QLineEdit" name="javaPathTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="javaDetectBtn">
|
||||
<property name="text">
|
||||
<string>Auto-detect...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="javaBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="javaTestBtn">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="skipCompatibilityCheckbox">
|
||||
<property name="toolTip">
|
||||
<string>If enabled, the launcher will not check if an instance is compatible with the selected Java version.</string>
|
||||
@ -95,6 +71,38 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="javaPathTextBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="javaBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="javaDetectBtn">
|
||||
<property name="text">
|
||||
<string>Auto-detect...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="javaTestBtn">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -699,10 +707,6 @@
|
||||
<tabstop>openGlobalJavaSettingsButton</tabstop>
|
||||
<tabstop>settingsTabs</tabstop>
|
||||
<tabstop>javaSettingsGroupBox</tabstop>
|
||||
<tabstop>javaPathTextBox</tabstop>
|
||||
<tabstop>javaDetectBtn</tabstop>
|
||||
<tabstop>javaBrowseBtn</tabstop>
|
||||
<tabstop>javaTestBtn</tabstop>
|
||||
<tabstop>memoryGroupBox</tabstop>
|
||||
<tabstop>minMemSpinBox</tabstop>
|
||||
<tabstop>maxMemSpinBox</tabstop>
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
/** This is just to override the combo box popup behavior so that the combo box doesn't take the whole screen.
|
||||
* ... thanks Qt.
|
||||
*/
|
||||
@ -226,7 +228,7 @@ void ModrinthManagedPackPage::parseManagedPack()
|
||||
QString id = m_inst->getManagedPackID();
|
||||
|
||||
m_fetch_job->addNetAction(
|
||||
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -376,7 +378,7 @@ void FlameManagedPackPage::parseManagedPack()
|
||||
|
||||
QString id = m_inst->getManagedPackID();
|
||||
|
||||
m_fetch_job->addNetAction(Net::Download::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
|
||||
m_fetch_job->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/mods/%2/files").arg(BuildConfig.FLAME_BASE_URL, id), response));
|
||||
|
||||
QObject::connect(m_fetch_job.get(), &NetJob::succeeded, this, [this, response, id] {
|
||||
QJsonParseError parse_error{};
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "net/Download.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
@ -281,7 +281,7 @@ std::optional<QIcon> ResourceModel::getIcon(QModelIndex& index, const QUrl& url)
|
||||
auto cache_entry = APPLICATION->metacache()->resolveEntry(
|
||||
metaEntryBase(),
|
||||
QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
||||
auto icon_fetch_action = Net::Download::makeCached(url, cache_entry);
|
||||
auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry);
|
||||
|
||||
auto full_file_path = cache_entry->getFullPath();
|
||||
connect(icon_fetch_action.get(), &NetAction::succeeded, this, [=] {
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include <BuildConfig.h>
|
||||
#include <Json.h>
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
namespace Atl {
|
||||
|
||||
ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
@ -75,7 +77,7 @@ void ListModel::request()
|
||||
|
||||
auto netJob = makeShared<NetJob>("Atl::Request", APPLICATION->network());
|
||||
auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
|
||||
@ -137,8 +139,7 @@ void ListModel::requestFailed(QString reason)
|
||||
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(
|
||||
APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -168,9 +169,9 @@ void ListModel::requestLogo(QString file, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file));
|
||||
auto job = new NetJob(QString("ATLauncher Icon Download %1").arg(file), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath, job] {
|
||||
|
@ -43,6 +43,8 @@
|
||||
#include "modplatform/atlauncher/ATLShareCode.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||
: QAbstractListModel(parent)
|
||||
, m_version(version)
|
||||
@ -152,7 +154,7 @@ Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
|
||||
void AtlOptionalModListModel::useShareCode(const QString& code) {
|
||||
m_jobPtr.reset(new NetJob("Atl::Request", APPLICATION->network()));
|
||||
auto url = QString(BuildConfig.ATL_API_BASE_URL + "share-codes/" + code);
|
||||
m_jobPtr->addNetAction(Net::Download::makeByteArray(QUrl(url), m_response));
|
||||
m_jobPtr->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), m_response));
|
||||
|
||||
connect(m_jobPtr.get(), &NetJob::succeeded,
|
||||
this, &AtlOptionalModListModel::shareCodeSuccess);
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "Application.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <Version.h>
|
||||
|
||||
#include <QtMath>
|
||||
@ -40,14 +42,16 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
return edit;
|
||||
}
|
||||
return pack.description;
|
||||
} case Qt::DecorationRole: {
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if (m_logoMap.contains(pack.logoName)) {
|
||||
return (m_logoMap.value(pack.logoName));
|
||||
}
|
||||
QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||
((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
|
||||
return icon;
|
||||
} case Qt::UserRole: {
|
||||
}
|
||||
case Qt::UserRole: {
|
||||
QVariant v;
|
||||
v.setValue(pack);
|
||||
return v;
|
||||
@ -68,7 +72,7 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool ListModel::setData(const QModelIndex &index, const QVariant &value, [[maybe_unused]] int role)
|
||||
bool ListModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role)
|
||||
{
|
||||
int pos = index.row();
|
||||
if (pos >= modpacks.size() || pos < 0 || !index.isValid())
|
||||
@ -102,9 +106,9 @@ void ListModel::requestLogo(QString logo, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo));
|
||||
auto job = new NetJob(QString("Flame Icon Download %1").arg(logo), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
|
||||
@ -128,7 +132,7 @@ void ListModel::requestLogo(QString logo, QString url)
|
||||
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -171,7 +175,7 @@ void ListModel::performPaginatedSearch()
|
||||
.arg(currentSearchTerm)
|
||||
.arg(currentSort + 1);
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
|
@ -42,9 +42,11 @@
|
||||
#include "FlameModel.h"
|
||||
#include "InstanceImportTask.h"
|
||||
#include "Json.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "ui/dialogs/NewInstanceDialog.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
static FlameAPI api;
|
||||
|
||||
@ -132,7 +134,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
|
||||
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
int addonId = current.addonId;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/files").arg(addonId), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, addonId, curr] {
|
||||
if (addonId != current.addonId) {
|
||||
@ -207,7 +209,7 @@ void FlamePage::suggestCurrent()
|
||||
|
||||
dialog->setSuggestedPack(current.name, new InstanceImportTask(version.downloadUrl, this, std::move(extra_info)));
|
||||
QString editedLogoName;
|
||||
editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
|
||||
editedLogoName = "curseforge_" + current.logoName;
|
||||
listModel->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
}
|
||||
@ -252,10 +254,8 @@ void FlamePage::updateUi()
|
||||
text += "<br>" + tr(" by ") + authorStrs.join(", ");
|
||||
}
|
||||
|
||||
if(current.extraInfoLoaded) {
|
||||
if (!current.extra.issuesUrl.isEmpty()
|
||||
|| !current.extra.sourceUrl.isEmpty()
|
||||
|| !current.extra.wikiUrl.isEmpty()) {
|
||||
if (current.extraInfoLoaded) {
|
||||
if (!current.extra.issuesUrl.isEmpty() || !current.extra.sourceUrl.isEmpty() || !current.extra.wikiUrl.isEmpty()) {
|
||||
text += "<br><br>" + tr("External links:") + "<br>";
|
||||
}
|
||||
|
||||
@ -267,7 +267,6 @@ void FlamePage::updateUi()
|
||||
text += "- " + tr("Source code: <a href=%1>%1</a>").arg(current.extra.sourceUrl) + "<br>";
|
||||
}
|
||||
|
||||
|
||||
text += "<hr>";
|
||||
text += api.getModDescription(current.addonId).toUtf8();
|
||||
|
||||
|
104
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
Normal file
104
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
Normal 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
|
67
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
Normal file
67
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
Normal 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
|
28
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
Normal file
28
launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
Normal 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>
|
88
launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
Normal file
88
launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
Normal 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
|
46
launcher/ui/pages/modplatform/import_ftb/ListModel.h
Normal file
46
launcher/ui/pages/modplatform/import_ftb/ListModel.h
Normal 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
|
@ -37,6 +37,7 @@
|
||||
#include "Application.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <Version.h>
|
||||
#include "StringUtils.h"
|
||||
@ -229,9 +230,9 @@ void ListModel::requestLogo(QString file)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file));
|
||||
NetJob* job = new NetJob(QString("FTB Icon Download for %1").arg(file), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath, job] {
|
||||
@ -255,7 +256,7 @@ void ListModel::requestLogo(QString file)
|
||||
void ListModel::getLogo(const QString& logo, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo);
|
||||
}
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace Modrinth {
|
||||
@ -142,7 +144,7 @@ void ModpackListModel::performPaginatedSearch()
|
||||
.arg(currentSearchTerm)
|
||||
.arg(currentSort);
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), m_all_response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
|
||||
QJsonParseError parse_error_all{};
|
||||
@ -216,9 +218,7 @@ void ModpackListModel::searchWithTerm(const QString& term, const int sort)
|
||||
void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
|
||||
{
|
||||
if (m_logoMap.contains(logo)) {
|
||||
callback(APPLICATION->metacache()
|
||||
->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)))
|
||||
->getFullPath());
|
||||
callback(APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo))->getFullPath());
|
||||
} else {
|
||||
requestLogo(logo, logoUrl);
|
||||
}
|
||||
@ -230,10 +230,9 @@ void ModpackListModel::requestLogo(QString logo, QString url)
|
||||
return;
|
||||
}
|
||||
|
||||
MetaEntryPtr entry =
|
||||
APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo.section(".", 0, 0)));
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry(m_parent->metaEntryBase(), QString("logos/%1").arg(logo));
|
||||
auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] {
|
||||
|
@ -46,6 +46,8 @@
|
||||
|
||||
#include "ui/widgets/ProjectItem.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QKeyEvent>
|
||||
#include <QPushButton>
|
||||
@ -127,7 +129,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
|
||||
|
||||
QString id = current.id;
|
||||
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
|
||||
if (id != current.id) {
|
||||
@ -176,7 +178,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
|
||||
QString id = current.id;
|
||||
|
||||
netJob->addNetAction(
|
||||
Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
Net::ApiDownload::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response));
|
||||
|
||||
QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id, curr] {
|
||||
if (id != current.id) {
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include "BuildConfig.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
Technic::ListModel::ListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||
@ -116,7 +118,7 @@ void Technic::ListModel::performSearch()
|
||||
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
|
||||
searchMode = List;
|
||||
}
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
||||
jobPtr = netJob;
|
||||
jobPtr->start();
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||
@ -157,7 +159,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
pack.logoName = "null";
|
||||
} else {
|
||||
pack.logoUrl = rawURL;
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||
pack.logoName = rawURL.section(QLatin1Char('/'), -1);
|
||||
}
|
||||
pack.broken = false;
|
||||
newList.append(pack);
|
||||
@ -179,7 +181,7 @@ void Technic::ListModel::searchRequestFinished()
|
||||
auto iconUrl = Json::requireString(iconObj, "url");
|
||||
|
||||
pack.logoUrl = iconUrl;
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
|
||||
pack.logoName = iconUrl.section(QLatin1Char('/'), -1);
|
||||
} else {
|
||||
pack.logoUrl = "null";
|
||||
pack.logoName = "null";
|
||||
@ -254,7 +256,7 @@ void Technic::ListModel::requestLogo(QString logo, QString url)
|
||||
|
||||
MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
|
||||
auto job = new NetJob(QString("Technic Icon Download %1").arg(logo), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(QUrl(url), entry));
|
||||
|
||||
auto fullPath = entry->getFullPath();
|
||||
|
||||
|
@ -49,6 +49,8 @@
|
||||
#include "Application.h"
|
||||
#include "modplatform/technic/SolderPackManifest.h"
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
|
||||
: QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
|
||||
{
|
||||
@ -129,21 +131,18 @@ void TechnicPage::suggestCurrent()
|
||||
return;
|
||||
}
|
||||
|
||||
QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
|
||||
model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
|
||||
{
|
||||
dialog->setSuggestedIconFromFile(logo, editedLogoName);
|
||||
});
|
||||
QString editedLogoName = "technic_" + current.logoName;
|
||||
model->getLogo(current.logoName, current.logoUrl,
|
||||
[this, editedLogoName](QString logo) { dialog->setSuggestedIconFromFile(logo, editedLogoName); });
|
||||
|
||||
if (current.metadataLoaded)
|
||||
{
|
||||
if (current.metadataLoaded) {
|
||||
metadataLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Technic::PackMeta(%1)").arg(current.name), APPLICATION->network());
|
||||
QString slug = current.slug;
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QString("%1modpack/%2?build=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, slug, BuildConfig.TECHNIC_API_BUILD), response));
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this, slug]
|
||||
{
|
||||
jobPtr.reset();
|
||||
@ -249,7 +248,7 @@ void TechnicPage::metadataLoaded()
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Technic::SolderMeta(%1)").arg(current.name), APPLICATION->network());
|
||||
auto url = QString("%1/modpack/%2").arg(current.url, current.slug);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), response));
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(url), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &TechnicPage::onSolderLoaded);
|
||||
|
||||
|
@ -103,6 +103,8 @@ public:
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
||||
setItemDelegate(new PageViewDelegate(this));
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
// Adjust margins when using Breeze theme
|
||||
setProperty("_kde_side_panel_view", true);
|
||||
}
|
||||
|
||||
virtual QSize sizeHint() const
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "Application.h"
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
enum FormatProperties { ImageData = QTextFormat::UserProperty + 1 };
|
||||
|
||||
@ -97,7 +98,7 @@ void VariableSizedImageObject::loadImage(QTextDocument* doc, const QUrl& source,
|
||||
QString("images/%1").arg(QString(QCryptographicHash::hash(source.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex())));
|
||||
|
||||
auto job = new NetJob(QString("Load Image: %1").arg(source.fileName()), APPLICATION->network());
|
||||
job->addNetAction(Net::Download::makeCached(source, entry));
|
||||
job->addNetAction(Net::ApiDownload::makeCached(source, entry));
|
||||
|
||||
auto full_entry_path = entry->getFullPath();
|
||||
auto source_url = source;
|
||||
|
@ -204,8 +204,10 @@ static void copyAction(QAction* from, QAction* to)
|
||||
|
||||
void WideBar::showVisibilityMenu(QPoint const& position)
|
||||
{
|
||||
if (!m_bar_menu)
|
||||
if (!m_bar_menu) {
|
||||
m_bar_menu = std::make_unique<QMenu>(this);
|
||||
m_bar_menu->setTearOffEnabled(true);
|
||||
}
|
||||
|
||||
if (m_menu_state == MenuState::Dirty) {
|
||||
for (auto* old_action : m_bar_menu->actions())
|
||||
|
Loading…
Reference in New Issue
Block a user