Merge branch 'develop' into global-jvm-args
This commit is contained in:
commit
2be8100e7c
33
.github/workflows/build.yml
vendored
33
.github/workflows/build.yml
vendored
@ -39,6 +39,7 @@ jobs:
|
|||||||
INSTALL_PORTABLE_DIR: "install-portable"
|
INSTALL_PORTABLE_DIR: "install-portable"
|
||||||
INSTALL_APPIMAGE_DIR: "install-appdir"
|
INSTALL_APPIMAGE_DIR: "install-appdir"
|
||||||
BUILD_DIR: "build"
|
BUILD_DIR: "build"
|
||||||
|
CCACHE_VAR: ""
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
##
|
##
|
||||||
@ -63,6 +64,7 @@ jobs:
|
|||||||
ninja:p
|
ninja:p
|
||||||
qt5:p
|
qt5:p
|
||||||
ccache:p
|
ccache:p
|
||||||
|
nsis:p
|
||||||
|
|
||||||
- name: Setup ccache
|
- name: Setup ccache
|
||||||
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os != 'Windows' && inputs.build_type == 'Debug'
|
||||||
@ -80,6 +82,12 @@ jobs:
|
|||||||
ccache -p # Show config
|
ccache -p # Show config
|
||||||
ccache -z # Zero stats
|
ccache -z # Zero stats
|
||||||
|
|
||||||
|
- name: Use ccache on Debug builds only
|
||||||
|
if: inputs.build_type == 'Debug'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Retrieve ccache cache (Windows)
|
- name: Retrieve ccache cache (Windows)
|
||||||
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v3.0.2
|
uses: actions/cache@v3.0.2
|
||||||
@ -105,12 +113,15 @@ jobs:
|
|||||||
if: runner.os == 'Linux' && matrix.appimage == true
|
if: runner.os == 'Linux' && matrix.appimage == true
|
||||||
run: |
|
run: |
|
||||||
sudo add-apt-repository ppa:savoury1/qt-5-15
|
sudo add-apt-repository ppa:savoury1/qt-5-15
|
||||||
|
sudo add-apt-repository ppa:savoury1/kde-5-80
|
||||||
|
sudo add-apt-repository ppa:savoury1/gpg
|
||||||
|
sudo add-apt-repository ppa:savoury1/ffmpeg4
|
||||||
|
|
||||||
- name: Install Qt (Linux)
|
- name: Install Qt (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -y update
|
sudo apt-get -y update
|
||||||
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build
|
sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 ninja-build qt5-image-formats-plugins
|
||||||
|
|
||||||
- name: Prepare AppImage (Linux)
|
- name: Prepare AppImage (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage == true
|
if: runner.os == 'Linux' && matrix.appimage == true
|
||||||
@ -128,18 +139,18 @@ jobs:
|
|||||||
- name: Configure CMake (macOS)
|
- name: Configure CMake (macOS)
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Windows)
|
- name: Configure CMake (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
|
||||||
|
|
||||||
- name: Configure CMake (Linux)
|
- name: Configure CMake (Linux)
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -G Ninja
|
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=Linux -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -G Ninja
|
||||||
|
|
||||||
##
|
##
|
||||||
# BUILD
|
# BUILD
|
||||||
@ -190,6 +201,13 @@ jobs:
|
|||||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
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
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||||
|
|
||||||
|
- name: Package (Windows, installer)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: msys2 {0}
|
||||||
|
run: |
|
||||||
|
cd ${{ env.INSTALL_DIR }}
|
||||||
|
makensis -NOCD "-DVERSION=${{ env.VERSION }}" "-DMUI_ICON=${{ github.workspace }}/program_info/polymc.ico" "-XOutFile ${{ github.workspace }}/PolyMC-Setup.exe" "${{ github.workspace }}/program_info/win_install.nsi"
|
||||||
|
|
||||||
- name: Package (Linux)
|
- name: Package (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux' && matrix.appimage != true
|
||||||
run: |
|
run: |
|
||||||
@ -257,6 +275,13 @@ jobs:
|
|||||||
name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
path: ${{ env.INSTALL_PORTABLE_DIR }}/**
|
||||||
|
|
||||||
|
- name: Upload installer (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: PolyMC-${{ matrix.name }}-Setup-${{ env.VERSION }}-${{ inputs.build_type }}
|
||||||
|
path: PolyMC-Setup.exe
|
||||||
|
|
||||||
- name: Upload binary tarball (Linux)
|
- name: Upload binary tarball (Linux)
|
||||||
if: runner.os == 'Linux' && matrix.appimage != true
|
if: runner.os == 'Linux' && matrix.appimage != true
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
6
.github/workflows/trigger_release.yml
vendored
6
.github/workflows/trigger_release.yml
vendored
@ -43,10 +43,12 @@ jobs:
|
|||||||
for d in PolyMC-Windows-*; do
|
for d in PolyMC-Windows-*; do
|
||||||
cd "${d}" || continue
|
cd "${d}" || continue
|
||||||
ARCH="$(echo -n ${d} | cut -d '-' -f 3)"
|
ARCH="$(echo -n ${d} | cut -d '-' -f 3)"
|
||||||
|
INST="$(echo -n ${d} | grep -o Setup || true)"
|
||||||
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
PORT="$(echo -n ${d} | grep -o Portable || true)"
|
||||||
NAME="PolyMC-Windows-${ARCH}"
|
NAME="PolyMC-Windows-${ARCH}"
|
||||||
test -z "${PORT}" || NAME="${NAME}-Portable"
|
test -z "${PORT}" || NAME="${NAME}-Portable"
|
||||||
zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
test -z "${INST}" || mv PolyMC-*.exe ../${NAME}-Setup-${{ env.VERSION }}.exe
|
||||||
|
test -n "${INST}" || zip -r -9 "../${NAME}-${{ env.VERSION }}.zip" *
|
||||||
cd ..
|
cd ..
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -66,7 +68,9 @@ jobs:
|
|||||||
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage
|
||||||
PolyMC-Windows-i686-${{ env.VERSION }}.zip
|
PolyMC-Windows-i686-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-i686-Portable-${{ env.VERSION }}.zip
|
PolyMC-Windows-i686-Portable-${{ env.VERSION }}.zip
|
||||||
|
PolyMC-Windows-i686-Setup-${{ env.VERSION }}.exe
|
||||||
PolyMC-Windows-x86_64-${{ env.VERSION }}.zip
|
PolyMC-Windows-x86_64-${{ env.VERSION }}.zip
|
||||||
PolyMC-Windows-x86_64-Portable-${{ env.VERSION }}.zip
|
PolyMC-Windows-x86_64-Portable-${{ env.VERSION }}.zip
|
||||||
|
PolyMC-Windows-x86_64-Setup-${{ env.VERSION }}.exe
|
||||||
PolyMC-macOS-${{ env.VERSION }}.tar.gz
|
PolyMC-macOS-${{ env.VERSION }}.tar.gz
|
||||||
PolyMC-${{ env.VERSION }}.tar.gz
|
PolyMC-${{ env.VERSION }}.tar.gz
|
||||||
|
@ -34,17 +34,20 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
|||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
include(GenerateExportHeader)
|
include(GenerateExportHeader)
|
||||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "-Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}")
|
||||||
if(UNIX AND APPLE)
|
if(UNIX AND APPLE)
|
||||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
|
|
||||||
|
|
||||||
# Fix build with Qt 5.13
|
# Fix build with Qt 5.13
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y")
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_DISABLE_DEPRECATED_BEFORE=0x050C00")
|
||||||
|
|
||||||
|
# set CXXFLAGS for build targets
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||||
|
|
||||||
option(ENABLE_LTO "Enable Link Time Optimization" off)
|
option(ENABLE_LTO "Enable Link Time Optimization" off)
|
||||||
|
|
||||||
if(ENABLE_LTO)
|
if(ENABLE_LTO)
|
||||||
@ -70,8 +73,8 @@ set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL
|
|||||||
|
|
||||||
######## Set version numbers ########
|
######## Set version numbers ########
|
||||||
set(Launcher_VERSION_MAJOR 1)
|
set(Launcher_VERSION_MAJOR 1)
|
||||||
set(Launcher_VERSION_MINOR 2)
|
set(Launcher_VERSION_MINOR 3)
|
||||||
set(Launcher_VERSION_HOTFIX 2)
|
set(Launcher_VERSION_HOTFIX 0)
|
||||||
|
|
||||||
# Build number
|
# Build number
|
||||||
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||||
@ -140,7 +143,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
|||||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml)
|
||||||
|
|
||||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||||
find_package(QuaZip-Qt5 1.3)
|
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||||
endif()
|
endif()
|
||||||
if (NOT QuaZip-Qt5_FOUND)
|
if (NOT QuaZip-Qt5_FOUND)
|
||||||
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)
|
set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -36,6 +37,7 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
|
#include "net/PasteUpload.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
|
|
||||||
@ -61,6 +63,7 @@
|
|||||||
#include "ui/setupwizard/SetupWizard.h"
|
#include "ui/setupwizard/SetupWizard.h"
|
||||||
#include "ui/setupwizard/LanguageWizardPage.h"
|
#include "ui/setupwizard/LanguageWizardPage.h"
|
||||||
#include "ui/setupwizard/JavaWizardPage.h"
|
#include "ui/setupwizard/JavaWizardPage.h"
|
||||||
|
#include "ui/setupwizard/PasteWizardPage.h"
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
|
||||||
@ -671,8 +674,33 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
|
|
||||||
m_settings->registerSetting("UpdateDialogGeometry", "");
|
m_settings->registerSetting("UpdateDialogGeometry", "");
|
||||||
|
|
||||||
// pastebin URL
|
// HACK: This code feels so stupid is there a less stupid way of doing this?
|
||||||
m_settings->registerSetting("PastebinURL", "https://0x0.st");
|
{
|
||||||
|
m_settings->registerSetting("PastebinURL", "");
|
||||||
|
m_settings->registerSetting("PastebinType", PasteUpload::PasteType::Mclogs);
|
||||||
|
m_settings->registerSetting("PastebinCustomAPIBase", "");
|
||||||
|
|
||||||
|
QString pastebinURL = m_settings->get("PastebinURL").toString();
|
||||||
|
|
||||||
|
bool userHadDefaultPastebin = pastebinURL == "https://0x0.st";
|
||||||
|
if (!pastebinURL.isEmpty() && !userHadDefaultPastebin)
|
||||||
|
{
|
||||||
|
m_settings->set("PastebinType", PasteUpload::PasteType::NullPointer);
|
||||||
|
m_settings->set("PastebinCustomAPIBase", pastebinURL);
|
||||||
|
m_settings->reset("PastebinURL");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int pasteType = m_settings->get("PastebinType").toInt(&ok);
|
||||||
|
// If PastebinType is invalid then reset the related settings.
|
||||||
|
if (!ok || !(PasteUpload::PasteType::First <= pasteType && pasteType <= PasteUpload::PasteType::Last))
|
||||||
|
{
|
||||||
|
m_settings->reset("PastebinType");
|
||||||
|
m_settings->reset("PastebinCustomAPIBase");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// meta URL
|
||||||
|
m_settings->registerSetting("MetaURLOverride", "");
|
||||||
|
|
||||||
m_settings->registerSetting("CloseAfterLaunch", false);
|
m_settings->registerSetting("CloseAfterLaunch", false);
|
||||||
m_settings->registerSetting("QuitAfterGameStop", false);
|
m_settings->registerSetting("QuitAfterGameStop", false);
|
||||||
@ -899,7 +927,8 @@ bool Application::createSetupWizard()
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}();
|
}();
|
||||||
bool wizardRequired = javaRequired || languageRequired;
|
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
||||||
|
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired;
|
||||||
|
|
||||||
if(wizardRequired)
|
if(wizardRequired)
|
||||||
{
|
{
|
||||||
@ -913,6 +942,11 @@ bool Application::createSetupWizard()
|
|||||||
{
|
{
|
||||||
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
|
m_setupWizard->addPage(new JavaWizardPage(m_setupWizard));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pasteInterventionRequired)
|
||||||
|
{
|
||||||
|
m_setupWizard->addPage(new PasteWizardPage(m_setupWizard));
|
||||||
|
}
|
||||||
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
|
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
|
||||||
m_setupWizard->show();
|
m_setupWizard->show();
|
||||||
return true;
|
return true;
|
||||||
|
@ -661,6 +661,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/setupwizard/JavaWizardPage.h
|
ui/setupwizard/JavaWizardPage.h
|
||||||
ui/setupwizard/LanguageWizardPage.cpp
|
ui/setupwizard/LanguageWizardPage.cpp
|
||||||
ui/setupwizard/LanguageWizardPage.h
|
ui/setupwizard/LanguageWizardPage.h
|
||||||
|
ui/setupwizard/PasteWizardPage.cpp
|
||||||
|
ui/setupwizard/PasteWizardPage.h
|
||||||
|
|
||||||
# GUI - themes
|
# GUI - themes
|
||||||
ui/themes/FusionTheme.cpp
|
ui/themes/FusionTheme.cpp
|
||||||
@ -890,6 +892,7 @@ SET(LAUNCHER_SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
qt5_wrap_ui(LAUNCHER_UI
|
qt5_wrap_ui(LAUNCHER_UI
|
||||||
|
ui/setupwizard/PasteWizardPage.ui
|
||||||
ui/pages/global/AccountListPage.ui
|
ui/pages/global/AccountListPage.ui
|
||||||
ui/pages/global/JavaPage.ui
|
ui/pages/global/JavaPage.ui
|
||||||
ui/pages/global/LauncherPage.ui
|
ui/pages/global/LauncherPage.ui
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "NullInstance.h"
|
#include "NullInstance.h"
|
||||||
|
#include "icons/IconList.h"
|
||||||
#include "icons/IconUtils.h"
|
#include "icons/IconUtils.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
|
@ -75,7 +75,16 @@ Meta::BaseEntity::~BaseEntity()
|
|||||||
|
|
||||||
QUrl Meta::BaseEntity::url() const
|
QUrl Meta::BaseEntity::url() const
|
||||||
{
|
{
|
||||||
|
auto s = APPLICATION->settings();
|
||||||
|
QString metaOverride = s->get("MetaURLOverride").toString();
|
||||||
|
if(metaOverride.isEmpty())
|
||||||
|
{
|
||||||
return QUrl(BuildConfig.META_URL).resolved(localFilename());
|
return QUrl(BuildConfig.META_URL).resolved(localFilename());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return QUrl(metaOverride).resolved(localFilename());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Meta::BaseEntity::loadLocalFile()
|
bool Meta::BaseEntity::loadLocalFile()
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -297,7 +317,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
|
|||||||
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
auto rawHash = QByteArray::fromHex(hash.toLatin1());
|
||||||
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
||||||
}
|
}
|
||||||
objectDL->m_total_progress = size;
|
objectDL->setProgress(objectDL->getProgress(), size);
|
||||||
return objectDL;
|
return objectDL;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -487,9 +488,8 @@ QStringList MinecraftInstance::processMinecraftArgs(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// blatant self-promotion.
|
token_mapping["profile_name"] = name();
|
||||||
token_mapping["profile_name"] = token_mapping["version_name"] = BuildConfig.LAUNCHER_NAME;
|
token_mapping["version_name"] = profile->getMinecraftVersion();
|
||||||
|
|
||||||
token_mapping["version_type"] = profile->getMinecraftVersionType();
|
token_mapping["version_type"] = profile->getMinecraftVersionType();
|
||||||
|
|
||||||
QString absRootDir = QDir(gameRoot()).absolutePath();
|
QString absRootDir = QDir(gameRoot()).absolutePath();
|
||||||
|
@ -36,6 +36,13 @@
|
|||||||
#include "ComponentUpdateTask.h"
|
#include "ComponentUpdateTask.h"
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "modplatform/ModAPI.h"
|
||||||
|
|
||||||
|
static const QMap<QString, ModAPI::ModLoaderType> modloaderMapping{
|
||||||
|
{"net.minecraftforge", ModAPI::Forge},
|
||||||
|
{"net.fabricmc.fabric-loader", ModAPI::Fabric},
|
||||||
|
{"org.quiltmc.quilt-loader", ModAPI::Quilt}
|
||||||
|
};
|
||||||
|
|
||||||
PackProfile::PackProfile(MinecraftInstance * instance)
|
PackProfile::PackProfile(MinecraftInstance * instance)
|
||||||
: QAbstractListModel()
|
: QAbstractListModel()
|
||||||
@ -971,19 +978,19 @@ void PackProfile::disableInteraction(bool disable)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ModAPI::ModLoaderType PackProfile::getModLoader()
|
ModAPI::ModLoaderTypes PackProfile::getModLoaders()
|
||||||
{
|
{
|
||||||
if (!getComponentVersion("net.minecraftforge").isEmpty())
|
ModAPI::ModLoaderTypes result = ModAPI::Unspecified;
|
||||||
|
|
||||||
|
QMapIterator<QString, ModAPI::ModLoaderType> i(modloaderMapping);
|
||||||
|
|
||||||
|
while (i.hasNext())
|
||||||
{
|
{
|
||||||
return ModAPI::Forge;
|
i.next();
|
||||||
|
Component* c = getComponent(i.key());
|
||||||
|
if (c != nullptr && c->isEnabled()) {
|
||||||
|
result |= i.value();
|
||||||
}
|
}
|
||||||
else if (!getComponentVersion("net.fabricmc.fabric-loader").isEmpty())
|
|
||||||
{
|
|
||||||
return ModAPI::Fabric;
|
|
||||||
}
|
}
|
||||||
else if (!getComponentVersion("org.quiltmc.quilt-loader").isEmpty())
|
return result;
|
||||||
{
|
|
||||||
return ModAPI::Quilt;
|
|
||||||
}
|
|
||||||
return ModAPI::Unspecified;
|
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ public:
|
|||||||
// todo(merged): is this the best approach
|
// todo(merged): is this the best approach
|
||||||
void appendComponent(ComponentPtr component);
|
void appendComponent(ComponentPtr component);
|
||||||
|
|
||||||
ModAPI::ModLoaderType getModLoader();
|
ModAPI::ModLoaderTypes getModLoaders();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void scheduleSave();
|
void scheduleSave();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -55,7 +56,7 @@ void VersionFile::applyTo(LaunchProfile *profile)
|
|||||||
// Only real Minecraft can set those. Don't let anything override them.
|
// Only real Minecraft can set those. Don't let anything override them.
|
||||||
if (isMinecraftVersion(uid))
|
if (isMinecraftVersion(uid))
|
||||||
{
|
{
|
||||||
profile->applyMinecraftVersion(minecraftVersion);
|
profile->applyMinecraftVersion(version);
|
||||||
profile->applyMinecraftVersionType(type);
|
profile->applyMinecraftVersionType(type);
|
||||||
// HACK: ignore assets from other version files than Minecraft
|
// HACK: ignore assets from other version files than Minecraft
|
||||||
// workaround for stupid assets issue caused by amazon:
|
// workaround for stupid assets issue caused by amazon:
|
||||||
|
@ -16,14 +16,21 @@ class ModAPI {
|
|||||||
public:
|
public:
|
||||||
virtual ~ModAPI() = default;
|
virtual ~ModAPI() = default;
|
||||||
|
|
||||||
// https://docs.curseforge.com/?http#tocS_ModLoaderType
|
enum ModLoaderType {
|
||||||
enum ModLoaderType { Unspecified = 0, Forge = 1, Cauldron = 2, LiteLoader = 3, Fabric = 4, Quilt = 5 };
|
Unspecified = 0,
|
||||||
|
Forge = 1 << 0,
|
||||||
|
Cauldron = 1 << 1,
|
||||||
|
LiteLoader = 1 << 2,
|
||||||
|
Fabric = 1 << 3,
|
||||||
|
Quilt = 1 << 4
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
||||||
|
|
||||||
struct SearchArgs {
|
struct SearchArgs {
|
||||||
int offset;
|
int offset;
|
||||||
QString search;
|
QString search;
|
||||||
QString sorting;
|
QString sorting;
|
||||||
ModLoaderType mod_loader;
|
ModLoaderTypes loaders;
|
||||||
std::list<Version> versions;
|
std::list<Version> versions;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,7 +40,7 @@ class ModAPI {
|
|||||||
struct VersionSearchArgs {
|
struct VersionSearchArgs {
|
||||||
QString addonId;
|
QString addonId;
|
||||||
std::list<Version> mcVersions;
|
std::list<Version> mcVersions;
|
||||||
ModLoaderType loader;
|
ModLoaderTypes loaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
|
virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0;
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -39,6 +58,8 @@
|
|||||||
|
|
||||||
namespace ATLauncher {
|
namespace ATLauncher {
|
||||||
|
|
||||||
|
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version);
|
||||||
|
|
||||||
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
|
PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
|
||||||
{
|
{
|
||||||
m_support = support;
|
m_support = support;
|
||||||
@ -74,14 +95,13 @@ void PackInstallTask::onDownloadSucceeded()
|
|||||||
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
|
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
|
||||||
jobPtr.reset();
|
jobPtr.reset();
|
||||||
|
|
||||||
QJsonParseError parse_error;
|
QJsonParseError parse_error {};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||||
if(parse_error.error != QJsonParseError::NoError) {
|
if(parse_error.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
|
qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||||
qWarning() << response;
|
qWarning() << response;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto obj = doc.object();
|
auto obj = doc.object();
|
||||||
|
|
||||||
ATLauncher::PackVersion version;
|
ATLauncher::PackVersion version;
|
||||||
@ -96,19 +116,15 @@ void PackInstallTask::onDownloadSucceeded()
|
|||||||
}
|
}
|
||||||
m_version = version;
|
m_version = version;
|
||||||
|
|
||||||
auto vlist = APPLICATION->metadataIndex()->get("net.minecraft");
|
// Display install message if one exists
|
||||||
if(!vlist)
|
if (!m_version.messages.install.isEmpty())
|
||||||
{
|
m_support->displayMessage(m_version.messages.install);
|
||||||
emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ver = vlist->getVersion(m_version.minecraft);
|
auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
|
||||||
if (!ver) {
|
if (!ver) {
|
||||||
emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft));
|
emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft", m_version.minecraft));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ver->load(Net::Mode::Online);
|
|
||||||
minecraftVersion = ver;
|
minecraftVersion = ver;
|
||||||
|
|
||||||
if(m_version.noConfigs) {
|
if(m_version.noConfigs) {
|
||||||
@ -305,7 +321,48 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
|
|||||||
auto f = std::make_shared<VersionFile>();
|
auto f = std::make_shared<VersionFile>();
|
||||||
f->name = m_pack + " " + m_version_name + " (libraries)";
|
f->name = m_pack + " " + m_version_name + " (libraries)";
|
||||||
|
|
||||||
|
const static QMap<QString, QString> liteLoaderMap = {
|
||||||
|
{ "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" },
|
||||||
|
{ "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" },
|
||||||
|
{ "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" },
|
||||||
|
{ "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" },
|
||||||
|
{ "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" },
|
||||||
|
{ "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" },
|
||||||
|
{ "55785ccc82c07ff0ba038fe24be63ea2", "1.7.10_01" },
|
||||||
|
{ "63ada46e033d0cb6782bada09ad5ca4e", "1.7.10_04" },
|
||||||
|
{ "7983e4b28217c9ae8569074388409c86", "1.7.10_03" },
|
||||||
|
{ "c09882458d74fe0697c7681b8993097e", "1.7.10_02" },
|
||||||
|
{ "db7235aefd407ac1fde09a7baba50839", "1.7.10_00" },
|
||||||
|
{ "6e9028816027f53957bd8fcdfabae064", "1.8" },
|
||||||
|
{ "5e732dc446f9fe2abe5f9decaec40cde", "1.10-SNAPSHOT" },
|
||||||
|
{ "3a98b5ed95810bf164e71c1a53be568d", "1.11.2-SNAPSHOT" },
|
||||||
|
{ "ba8e6285966d7d988a96496f48cbddaa", "1.8.9-SNAPSHOT" },
|
||||||
|
{ "8524af3ac3325a82444cc75ae6e9112f", "1.11-SNAPSHOT" },
|
||||||
|
{ "53639d52340479ccf206a04f5e16606f", "1.5.2_01" },
|
||||||
|
{ "1fcdcf66ce0a0806b7ad8686afdce3f7", "1.6.4_00" },
|
||||||
|
{ "531c116f71ae2b11033f9a11a0f8e668", "1.6.4_01" },
|
||||||
|
{ "4009eeb99c9068f608d3483a6439af88", "1.7.2_03" },
|
||||||
|
{ "66f343354b8417abce1a10d557d2c6e9", "1.7.2_04" },
|
||||||
|
{ "ab554c21f28fbc4ae9b098bcb5f4cceb", "1.7.2_05" },
|
||||||
|
{ "e1d76a05a3723920e2f80a5e66c45f16", "1.7.2_02" },
|
||||||
|
{ "00318cb0c787934d523f63cdfe8ddde4", "1.9-SNAPSHOT" },
|
||||||
|
{ "986fd1ee9525cb0dcab7609401cef754", "1.9.4-SNAPSHOT" },
|
||||||
|
{ "571ad5e6edd5ff40259570c9be588bb5", "1.9.4" },
|
||||||
|
{ "1cdd72f7232e45551f16cc8ffd27ccf3", "1.10.2-SNAPSHOT" },
|
||||||
|
{ "8a7c21f32d77ee08b393dd3921ced8eb", "1.10.2" },
|
||||||
|
{ "b9bef8abc8dc309069aeba6fbbe58980", "1.12.1-SNAPSHOT" }
|
||||||
|
};
|
||||||
|
|
||||||
for(const auto & lib : m_version.libraries) {
|
for(const auto & lib : m_version.libraries) {
|
||||||
|
// If the library is LiteLoader, we need to ignore it and handle it separately.
|
||||||
|
if (liteLoaderMap.contains(lib.md5)) {
|
||||||
|
auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5));
|
||||||
|
if (ver) {
|
||||||
|
componentsToInstall.insert("com.mumfrey.liteloader", ver);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto libName = detectLibrary(lib);
|
auto libName = detectLibrary(lib);
|
||||||
GradleSpecifier libSpecifier(libName);
|
GradleSpecifier libSpecifier(libName);
|
||||||
|
|
||||||
@ -357,7 +414,31 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared
|
|||||||
|
|
||||||
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
|
||||||
{
|
{
|
||||||
if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
|
if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mainClass = m_version.mainClass.mainClass;
|
||||||
|
auto extraArguments = m_version.extraArguments.arguments;
|
||||||
|
|
||||||
|
auto hasMainClassDepends = !m_version.mainClass.depends.isEmpty();
|
||||||
|
auto hasExtraArgumentsDepends = !m_version.extraArguments.depends.isEmpty();
|
||||||
|
if (hasMainClassDepends || hasExtraArgumentsDepends) {
|
||||||
|
QSet<QString> mods;
|
||||||
|
for (const auto& item : m_version.mods) {
|
||||||
|
mods.insert(item.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMainClassDepends && !mods.contains(m_version.mainClass.depends)) {
|
||||||
|
mainClass = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasExtraArgumentsDepends && !mods.contains(m_version.extraArguments.depends)) {
|
||||||
|
extraArguments = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainClass.isEmpty() && extraArguments.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,12 +466,12 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<
|
|||||||
|
|
||||||
auto f = std::make_shared<VersionFile>();
|
auto f = std::make_shared<VersionFile>();
|
||||||
f->name = m_pack + " " + m_version_name;
|
f->name = m_pack + " " + m_version_name;
|
||||||
if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
|
if (!mainClass.isEmpty() && !mainClasses.contains(mainClass)) {
|
||||||
f->mainClass = m_version.mainClass;
|
f->mainClass = mainClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse out tweakers
|
// Parse out tweakers
|
||||||
auto args = m_version.extraArguments.split(" ");
|
auto args = extraArguments.split(" ");
|
||||||
QString previous;
|
QString previous;
|
||||||
for(auto arg : args) {
|
for(auto arg : args) {
|
||||||
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
|
if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
|
||||||
@ -502,7 +583,7 @@ void PackInstallTask::downloadMods()
|
|||||||
QVector<QString> selectedMods;
|
QVector<QString> selectedMods;
|
||||||
if (!optionalMods.isEmpty()) {
|
if (!optionalMods.isEmpty()) {
|
||||||
setStatus(tr("Selecting optional mods..."));
|
setStatus(tr("Selecting optional mods..."));
|
||||||
selectedMods = m_support->chooseOptionalMods(optionalMods);
|
selectedMods = m_support->chooseOptionalMods(m_version, optionalMods);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
@ -574,20 +655,13 @@ void PackInstallTask::downloadMods()
|
|||||||
jobPtr->addNetAction(dl);
|
jobPtr->addNetAction(dl);
|
||||||
|
|
||||||
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
|
||||||
qDebug() << "Will download" << url << "to" << path;
|
|
||||||
modsToCopy[entry->getFullPath()] = path;
|
|
||||||
|
|
||||||
if(mod.type == ModType::Forge) {
|
if(mod.type == ModType::Forge) {
|
||||||
auto vlist = APPLICATION->metadataIndex()->get("net.minecraftforge");
|
auto ver = getComponentVersion("net.minecraftforge", mod.version);
|
||||||
if(vlist)
|
if (ver) {
|
||||||
{
|
|
||||||
auto ver = vlist->getVersion(mod.version);
|
|
||||||
if(ver) {
|
|
||||||
ver->load(Net::Mode::Online);
|
|
||||||
componentsToInstall.insert("net.minecraftforge", ver);
|
componentsToInstall.insert("net.minecraftforge", ver);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Jarmod: " + path;
|
qDebug() << "Jarmod: " + path;
|
||||||
jarmods.push_back(path);
|
jarmods.push_back(path);
|
||||||
@ -597,6 +671,10 @@ void PackInstallTask::downloadMods()
|
|||||||
qDebug() << "Jarmod: " + path;
|
qDebug() << "Jarmod: " + path;
|
||||||
jarmods.push_back(path);
|
jarmods.push_back(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download after Forge handling, to avoid downloading Forge twice.
|
||||||
|
qDebug() << "Will download" << url << "to" << path;
|
||||||
|
modsToCopy[entry->getFullPath()] = path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,6 +781,17 @@ bool PackInstallTask::extractMods(
|
|||||||
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
|
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
|
||||||
auto &from = iter.key();
|
auto &from = iter.key();
|
||||||
auto &to = iter.value();
|
auto &to = iter.value();
|
||||||
|
|
||||||
|
// If the file already exists, assume the mod is the correct copy - and remove
|
||||||
|
// the copy from the Configs.zip
|
||||||
|
QFileInfo fileInfo(to);
|
||||||
|
if (fileInfo.exists()) {
|
||||||
|
if (!QFile::remove(to)) {
|
||||||
|
qWarning() << "Failed to delete" << to;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FS::copy fileCopyOperation(from, to);
|
FS::copy fileCopyOperation(from, to);
|
||||||
if(!fileCopyOperation()) {
|
if(!fileCopyOperation()) {
|
||||||
qWarning() << "Failed to copy" << from << "to" << to;
|
qWarning() << "Failed to copy" << from << "to" << to;
|
||||||
@ -779,4 +868,23 @@ void PackInstallTask::install()
|
|||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version)
|
||||||
|
{
|
||||||
|
auto vlist = APPLICATION->metadataIndex()->get(uid);
|
||||||
|
if (!vlist)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!vlist->isLoaded())
|
||||||
|
vlist->load(Net::Mode::Online);
|
||||||
|
|
||||||
|
auto ver = vlist->getVersion(version);
|
||||||
|
if (!ver)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!ver->isLoaded())
|
||||||
|
ver->load(Net::Mode::Online);
|
||||||
|
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -37,7 +56,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Requests a user interaction to select which optional mods should be installed.
|
* Requests a user interaction to select which optional mods should be installed.
|
||||||
*/
|
*/
|
||||||
virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
|
virtual QVector<QString> chooseOptionalMods(PackVersion version, QVector<ATLauncher::VersionMod> mods) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a user interaction to select a component version from a given version list
|
* Requests a user interaction to select a component version from a given version list
|
||||||
@ -45,6 +64,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
|
virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a user interaction to display a message.
|
||||||
|
*/
|
||||||
|
virtual void displayMessage(QString message) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PackInstallTask : public InstanceTask
|
class PackInstallTask : public InstanceTask
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* 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 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020-2021 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
* Copyright 2021 Petr Mrazek <peterix@gmail.com>
|
||||||
*
|
*
|
||||||
@ -178,6 +197,8 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
|||||||
p.depends.append(Json::requireString(depends));
|
p.depends.append(Json::requireString(depends));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
p.colour = Json::ensureString(obj, QString("colour"), "");
|
||||||
|
p.warning = Json::ensureString(obj, QString("warning"), "");
|
||||||
|
|
||||||
p.client = Json::ensureBoolean(obj, QString("client"), false);
|
p.client = Json::ensureBoolean(obj, QString("client"), false);
|
||||||
|
|
||||||
@ -185,6 +206,24 @@ static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
|
|||||||
p.effectively_hidden = p.hidden || p.library;
|
p.effectively_hidden = p.hidden || p.library;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void loadVersionMessages(ATLauncher::VersionMessages& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
m.install = Json::ensureString(obj, "install", "");
|
||||||
|
m.update = Json::ensureString(obj, "update", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadVersionMainClass(ATLauncher::PackVersionMainClass& m, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
m.mainClass = Json::ensureString(obj, "mainClass", "");
|
||||||
|
m.depends = Json::ensureString(obj, "depends", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, QJsonObject& obj)
|
||||||
|
{
|
||||||
|
a.arguments = Json::ensureString(obj, "arguments", "");
|
||||||
|
a.depends = Json::ensureString(obj, "depends", "");
|
||||||
|
}
|
||||||
|
|
||||||
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
||||||
{
|
{
|
||||||
v.version = Json::requireString(obj, "version");
|
v.version = Json::requireString(obj, "version");
|
||||||
@ -193,12 +232,12 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
|||||||
|
|
||||||
if(obj.contains("mainClass")) {
|
if(obj.contains("mainClass")) {
|
||||||
auto main = Json::requireObject(obj, "mainClass");
|
auto main = Json::requireObject(obj, "mainClass");
|
||||||
v.mainClass = Json::ensureString(main, "mainClass", "");
|
loadVersionMainClass(v.mainClass, main);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj.contains("extraArguments")) {
|
if(obj.contains("extraArguments")) {
|
||||||
auto arguments = Json::requireObject(obj, "extraArguments");
|
auto arguments = Json::requireObject(obj, "extraArguments");
|
||||||
v.extraArguments = Json::ensureString(arguments, "arguments", "");
|
loadVersionExtraArguments(v.extraArguments, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(obj.contains("loader")) {
|
if(obj.contains("loader")) {
|
||||||
@ -232,4 +271,17 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
|
|||||||
auto configsObj = Json::requireObject(obj, "configs");
|
auto configsObj = Json::requireObject(obj, "configs");
|
||||||
loadVersionConfigs(v.configs, configsObj);
|
loadVersionConfigs(v.configs, configsObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto colourObj = Json::ensureObject(obj, "colours");
|
||||||
|
for (const auto &key : colourObj.keys()) {
|
||||||
|
v.colours[key] = Json::requireString(colourObj.value(key), "colour");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto warningsObj = Json::ensureObject(obj, "warnings");
|
||||||
|
for (const auto &key : warningsObj.keys()) {
|
||||||
|
v.warnings[key] = Json::requireString(warningsObj.value(key), "warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto messages = Json::ensureObject(obj, "messages");
|
||||||
|
loadVersionMessages(v.messages, messages);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* 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 2020 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright 2020 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -16,9 +35,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
namespace ATLauncher
|
namespace ATLauncher
|
||||||
{
|
{
|
||||||
@ -109,6 +129,8 @@ struct VersionMod
|
|||||||
bool library;
|
bool library;
|
||||||
QString group;
|
QString group;
|
||||||
QVector<QString> depends;
|
QVector<QString> depends;
|
||||||
|
QString colour;
|
||||||
|
QString warning;
|
||||||
|
|
||||||
bool client;
|
bool client;
|
||||||
|
|
||||||
@ -122,18 +144,40 @@ struct VersionConfigs
|
|||||||
QString sha1;
|
QString sha1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VersionMessages
|
||||||
|
{
|
||||||
|
QString install;
|
||||||
|
QString update;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackVersionMainClass
|
||||||
|
{
|
||||||
|
QString mainClass;
|
||||||
|
QString depends;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackVersionExtraArguments
|
||||||
|
{
|
||||||
|
QString arguments;
|
||||||
|
QString depends;
|
||||||
|
};
|
||||||
|
|
||||||
struct PackVersion
|
struct PackVersion
|
||||||
{
|
{
|
||||||
QString version;
|
QString version;
|
||||||
QString minecraft;
|
QString minecraft;
|
||||||
bool noConfigs;
|
bool noConfigs;
|
||||||
QString mainClass;
|
PackVersionMainClass mainClass;
|
||||||
QString extraArguments;
|
PackVersionExtraArguments extraArguments;
|
||||||
|
|
||||||
VersionLoader loader;
|
VersionLoader loader;
|
||||||
QVector<VersionLibrary> libraries;
|
QVector<VersionLibrary> libraries;
|
||||||
QVector<VersionMod> mods;
|
QVector<VersionMod> mods;
|
||||||
VersionConfigs configs;
|
VersionConfigs configs;
|
||||||
|
|
||||||
|
QMap<QString, QString> colours;
|
||||||
|
QMap<QString, QString> warnings;
|
||||||
|
VersionMessages messages;
|
||||||
};
|
};
|
||||||
|
|
||||||
void loadVersion(PackVersion & v, QJsonObject & obj);
|
void loadVersion(PackVersion & v, QJsonObject & obj);
|
||||||
|
@ -31,21 +31,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
for (auto& bytes : results) {
|
for (auto& bytes : results) {
|
||||||
auto& out = m_toProcess.files[index];
|
auto& out = m_toProcess.files[index];
|
||||||
try {
|
try {
|
||||||
bool fail = (!out.parseFromBytes(bytes));
|
failed &= (!out.parseFromBytes(bytes));
|
||||||
if(fail){
|
|
||||||
//failed :( probably disabled mod, try to add to the list
|
|
||||||
auto doc = Json::requireDocument(bytes);
|
|
||||||
if (!doc.isObject()) {
|
|
||||||
throw JSONValidationError(QString("data is not an object? that's not supposed to happen"));
|
|
||||||
}
|
|
||||||
auto obj = Json::ensureObject(doc.object(), "data");
|
|
||||||
//FIXME : HACK, MAY NOT WORK FOR LONG
|
|
||||||
out.url = QUrl(QString("https://media.forgecdn.net/files/%1/%2/%3")
|
|
||||||
.arg(QString::number(QString::number(out.fileId).leftRef(4).toInt())
|
|
||||||
,QString::number(QString::number(out.fileId).rightRef(3).toInt())
|
|
||||||
,QUrl::toPercentEncoding(out.fileName)), QUrl::TolerantMode);
|
|
||||||
}
|
|
||||||
failed &= fail;
|
|
||||||
} catch (const JSONValidationError& e) {
|
} catch (const JSONValidationError& e) {
|
||||||
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
|
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
|
||||||
qCritical() << e.cause();
|
qCritical() << e.cause();
|
||||||
|
@ -37,14 +37,14 @@ class FlameAPI : public NetworkModAPI {
|
|||||||
.arg(args.offset)
|
.arg(args.offset)
|
||||||
.arg(args.search)
|
.arg(args.search)
|
||||||
.arg(getSortFieldInt(args.sorting))
|
.arg(getSortFieldInt(args.sorting))
|
||||||
.arg(getMappedModLoader(args.mod_loader))
|
.arg(getMappedModLoader(args.loaders))
|
||||||
.arg(gameVersionStr);
|
.arg(gameVersionStr);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
|
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
|
||||||
{
|
{
|
||||||
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";
|
QString gameVersionQuery = args.mcVersions.size() == 1 ? QString("gameVersion=%1&").arg(args.mcVersions.front().toString()) : "";
|
||||||
QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loader));
|
QString modLoaderQuery = QString("modLoaderType=%1&").arg(getMappedModLoader(args.loaders));
|
||||||
|
|
||||||
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
|
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&%2%3")
|
||||||
.arg(args.addonId)
|
.arg(args.addonId)
|
||||||
@ -53,11 +53,16 @@ class FlameAPI : public NetworkModAPI {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static auto getMappedModLoader(const ModLoaderType type) -> const ModLoaderType
|
static auto getMappedModLoader(const ModLoaderTypes loaders) -> const int
|
||||||
{
|
{
|
||||||
|
// https://docs.curseforge.com/?http#tocS_ModLoaderType
|
||||||
|
if (loaders & Forge)
|
||||||
|
return 1;
|
||||||
|
if (loaders & Fabric)
|
||||||
|
return 4;
|
||||||
// TODO: remove this once Quilt drops official Fabric support
|
// TODO: remove this once Quilt drops official Fabric support
|
||||||
if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently*
|
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
|
||||||
return Fabric;
|
return 4; // Quilt would probably be 5
|
||||||
return type;
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -56,15 +56,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
|||||||
file.fileId = Json::requireInteger(obj, "id");
|
file.fileId = Json::requireInteger(obj, "id");
|
||||||
file.date = Json::requireString(obj, "fileDate");
|
file.date = Json::requireString(obj, "fileDate");
|
||||||
file.version = Json::requireString(obj, "displayName");
|
file.version = Json::requireString(obj, "displayName");
|
||||||
|
file.downloadUrl = Json::requireString(obj, "downloadUrl");
|
||||||
file.fileName = Json::requireString(obj, "fileName");
|
file.fileName = Json::requireString(obj, "fileName");
|
||||||
file.downloadUrl = Json::ensureString(obj, "downloadUrl", "");
|
|
||||||
if(file.downloadUrl.isEmpty()){
|
|
||||||
//FIXME : HACK, MAY NOT WORK FOR LONG
|
|
||||||
file.downloadUrl = QString("https://media.forgecdn.net/files/%1/%2/%3")
|
|
||||||
.arg(QString::number(QString::number(file.fileId.toInt()).leftRef(4).toInt())
|
|
||||||
,QString::number(QString::number(file.fileId.toInt()).rightRef(3).toInt())
|
|
||||||
,QUrl::toPercentEncoding(file.fileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
unsortedVersions.append(file);
|
unsortedVersions.append(file);
|
||||||
}
|
}
|
||||||
|
@ -65,9 +65,13 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
|
|||||||
// pick the latest version supported
|
// pick the latest version supported
|
||||||
file.mcVersion = versionArray[0].toString();
|
file.mcVersion = versionArray[0].toString();
|
||||||
file.version = Json::requireString(version, "displayName");
|
file.version = Json::requireString(version, "displayName");
|
||||||
file.downloadUrl = Json::requireString(version, "downloadUrl");
|
file.downloadUrl = Json::ensureString(version, "downloadUrl");
|
||||||
|
|
||||||
|
// only add if we have a download URL (third party distribution is enabled)
|
||||||
|
if (!file.downloadUrl.isEmpty()) {
|
||||||
unsortedVersions.append(file);
|
unsortedVersions.append(file);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };
|
auto orderSortPredicate = [](const IndexedVersion& a, const IndexedVersion& b) -> bool { return a.fileId > b.fileId; };
|
||||||
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
|
||||||
|
@ -71,6 +71,11 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
|
|||||||
|
|
||||||
fileName = Json::requireString(obj, "fileName");
|
fileName = Json::requireString(obj, "fileName");
|
||||||
|
|
||||||
|
QString rawUrl = Json::requireString(obj, "downloadUrl");
|
||||||
|
url = QUrl(rawUrl, QUrl::TolerantMode);
|
||||||
|
if (!url.isValid()) {
|
||||||
|
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
||||||
|
}
|
||||||
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
// This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
|
||||||
// It is also optional
|
// It is also optional
|
||||||
type = File::Type::SingleFile;
|
type = File::Type::SingleFile;
|
||||||
@ -82,17 +87,7 @@ bool Flame::File::parseFromBytes(const QByteArray& bytes)
|
|||||||
// this is probably a mod, dunno what else could modpacks download
|
// this is probably a mod, dunno what else could modpacks download
|
||||||
targetFolder = "mods";
|
targetFolder = "mods";
|
||||||
}
|
}
|
||||||
QString rawUrl = Json::ensureString(obj, "downloadUrl");
|
|
||||||
|
|
||||||
if(rawUrl.isEmpty()){
|
|
||||||
//either there somehow is an emtpy string as a link, or it's null either way it's invalid
|
|
||||||
//soft failing
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
url = QUrl(rawUrl, QUrl::TolerantMode);
|
|
||||||
if (!url.isValid()) {
|
|
||||||
throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
|
|
||||||
}
|
|
||||||
resolved = true;
|
resolved = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -28,30 +28,25 @@ class ModrinthAPI : public NetworkModAPI {
|
|||||||
public:
|
public:
|
||||||
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
|
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
|
||||||
|
|
||||||
static auto getModLoaderStrings(ModLoaderType type) -> const QStringList
|
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
|
||||||
{
|
{
|
||||||
QStringList l;
|
QStringList l;
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case Unspecified:
|
|
||||||
for (auto loader : {Forge, Fabric, Quilt})
|
for (auto loader : {Forge, Fabric, Quilt})
|
||||||
|
{
|
||||||
|
if ((types & loader) || types == Unspecified)
|
||||||
{
|
{
|
||||||
l << ModAPI::getModLoaderString(loader);
|
l << ModAPI::getModLoaderString(loader);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
case Quilt:
|
|
||||||
l << ModAPI::getModLoaderString(Fabric);
|
|
||||||
default:
|
|
||||||
l << ModAPI::getModLoaderString(type);
|
|
||||||
}
|
}
|
||||||
|
if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
|
||||||
|
l << ModAPI::getModLoaderString(Fabric);
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto getModLoaderFilters(ModLoaderType type) -> const QString
|
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
|
||||||
{
|
{
|
||||||
QStringList l;
|
QStringList l;
|
||||||
for (auto loader : getModLoaderStrings(type))
|
for (auto loader : getModLoaderStrings(types))
|
||||||
{
|
{
|
||||||
l << QString("\"categories:%1\"").arg(loader);
|
l << QString("\"categories:%1\"").arg(loader);
|
||||||
}
|
}
|
||||||
@ -61,7 +56,7 @@ class ModrinthAPI : public NetworkModAPI {
|
|||||||
private:
|
private:
|
||||||
inline auto getModSearchURL(SearchArgs& args) const -> QString override
|
inline auto getModSearchURL(SearchArgs& args) const -> QString override
|
||||||
{
|
{
|
||||||
if (!validateModLoader(args.mod_loader)) {
|
if (!validateModLoaders(args.loaders)) {
|
||||||
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
|
qWarning() << "Modrinth only have Forge and Fabric-compatible mods!";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -76,7 +71,7 @@ class ModrinthAPI : public NetworkModAPI {
|
|||||||
.arg(args.offset)
|
.arg(args.offset)
|
||||||
.arg(args.search)
|
.arg(args.search)
|
||||||
.arg(args.sorting)
|
.arg(args.sorting)
|
||||||
.arg(getModLoaderFilters(args.mod_loader))
|
.arg(getModLoaderFilters(args.loaders))
|
||||||
.arg(getGameVersionsArray(args.versions));
|
.arg(getGameVersionsArray(args.versions));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,7 +83,7 @@ class ModrinthAPI : public NetworkModAPI {
|
|||||||
"loaders=[\"%3\"]")
|
"loaders=[\"%3\"]")
|
||||||
.arg(args.addonId)
|
.arg(args.addonId)
|
||||||
.arg(getGameVersionsString(args.mcVersions))
|
.arg(getGameVersionsString(args.mcVersions))
|
||||||
.arg(getModLoaderStrings(args.loader).join("\",\""));
|
.arg(getModLoaderStrings(args.loaders).join("\",\""));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
|
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
|
||||||
@ -101,9 +96,9 @@ class ModrinthAPI : public NetworkModAPI {
|
|||||||
return s.isEmpty() ? QString() : QString("[%1],").arg(s);
|
return s.isEmpty() ? QString() : QString("[%1],").arg(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto validateModLoader(ModLoaderType modLoader) const -> bool
|
inline auto validateModLoaders(ModLoaderTypes loaders) const -> bool
|
||||||
{
|
{
|
||||||
return modLoader == Unspecified || modLoader == Forge || modLoader == Fabric || modLoader == Quilt;
|
return (loaders == Unspecified) || (loaders & (Forge | Fabric | Quilt));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1,62 +1,89 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
* Sink object for downloads that uses an external QByteArray it doesn't own as a target.
|
||||||
|
* FIXME: It is possible that the QByteArray is freed while we're doing some operation on it,
|
||||||
|
* causing a segmentation fault.
|
||||||
*/
|
*/
|
||||||
class ByteArraySink : public Sink
|
class ByteArraySink : public Sink {
|
||||||
{
|
public:
|
||||||
public:
|
ByteArraySink(QByteArray* output) : m_output(output){};
|
||||||
ByteArraySink(QByteArray *output)
|
|
||||||
:m_output(output)
|
|
||||||
{
|
|
||||||
// nil
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual ~ByteArraySink()
|
virtual ~ByteArraySink() = default;
|
||||||
{
|
|
||||||
// nil
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JobStatus init(QNetworkRequest & request) override
|
auto init(QNetworkRequest& request) -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->clear();
|
m_output->clear();
|
||||||
if(initAllValidators(request))
|
if (initAllValidators(request))
|
||||||
return Job_InProgress;
|
return Task::State::Running;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
};
|
};
|
||||||
|
|
||||||
JobStatus write(QByteArray & data) override
|
auto write(QByteArray& data) -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->append(data);
|
m_output->append(data);
|
||||||
if(writeAllValidators(data))
|
if (writeAllValidators(data))
|
||||||
return Job_InProgress;
|
return Task::State::Running;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus abort() override
|
auto abort() -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->clear();
|
m_output->clear();
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus finalize(QNetworkReply &reply) override
|
auto finalize(QNetworkReply& reply) -> Task::State override
|
||||||
{
|
{
|
||||||
if(finalizeAllValidators(reply))
|
if (finalizeAllValidators(reply))
|
||||||
return Job_Finished;
|
return Task::State::Succeeded;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasLocalData() override
|
auto hasLocalData() -> bool override { return false; }
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray * m_output;
|
QByteArray* m_output;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,55 +1,82 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <memory>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class ChecksumValidator: public Validator
|
class ChecksumValidator : public Validator {
|
||||||
{
|
public:
|
||||||
public: /* con/des */
|
|
||||||
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
|
ChecksumValidator(QCryptographicHash::Algorithm algorithm, QByteArray expected = QByteArray())
|
||||||
:m_checksum(algorithm), m_expected(expected)
|
: m_checksum(algorithm), m_expected(expected){};
|
||||||
{
|
virtual ~ChecksumValidator() = default;
|
||||||
};
|
|
||||||
virtual ~ChecksumValidator() {};
|
|
||||||
|
|
||||||
public: /* methods */
|
public:
|
||||||
bool init(QNetworkRequest &) override
|
auto init(QNetworkRequest&) -> bool override
|
||||||
{
|
{
|
||||||
m_checksum.reset();
|
m_checksum.reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool write(QByteArray & data) override
|
|
||||||
|
auto write(QByteArray& data) -> bool override
|
||||||
{
|
{
|
||||||
m_checksum.addData(data);
|
m_checksum.addData(data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
bool abort() override
|
|
||||||
{
|
auto abort() -> bool override { return true; }
|
||||||
return true;
|
|
||||||
}
|
auto validate(QNetworkReply&) -> bool override
|
||||||
bool validate(QNetworkReply &) override
|
|
||||||
{
|
|
||||||
if(m_expected.size() && m_expected != hash())
|
|
||||||
{
|
{
|
||||||
|
if (m_expected.size() && m_expected != hash()) {
|
||||||
qWarning() << "Checksum mismatch, download is bad.";
|
qWarning() << "Checksum mismatch, download is bad.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
QByteArray hash()
|
|
||||||
{
|
|
||||||
return m_checksum.result();
|
|
||||||
}
|
|
||||||
void setExpected(QByteArray expected)
|
|
||||||
{
|
|
||||||
m_expected = expected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private: /* data */
|
auto hash() -> QByteArray { return m_checksum.result(); }
|
||||||
|
|
||||||
|
void setExpected(QByteArray expected) { m_expected = expected; }
|
||||||
|
|
||||||
|
private:
|
||||||
QCryptographicHash m_checksum;
|
QCryptographicHash m_checksum;
|
||||||
QByteArray m_expected;
|
QByteArray m_expected;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,7 +36,6 @@
|
|||||||
#include "Download.h"
|
#include "Download.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
@ -31,33 +50,32 @@ namespace Net {
|
|||||||
|
|
||||||
Download::Download() : NetAction()
|
Download::Download() : NetAction()
|
||||||
{
|
{
|
||||||
m_status = Job_NotStarted;
|
m_state = State::Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::Ptr Download::makeCached(QUrl url, MetaEntryPtr entry, Options options)
|
auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Download::Ptr
|
||||||
{
|
{
|
||||||
Download* dl = new Download();
|
auto* dl = new Download();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
||||||
auto cachedNode = new MetaCacheSink(entry, md5Node);
|
auto cachedNode = new MetaCacheSink(entry, md5Node);
|
||||||
dl->m_sink.reset(cachedNode);
|
dl->m_sink.reset(cachedNode);
|
||||||
dl->m_target_path = entry->getFullPath();
|
|
||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::Ptr Download::makeByteArray(QUrl url, QByteArray* output, Options options)
|
auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> Download::Ptr
|
||||||
{
|
{
|
||||||
Download* dl = new Download();
|
auto* dl = new Download();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
dl->m_sink.reset(new ByteArraySink(output));
|
dl->m_sink.reset(new ByteArraySink(output));
|
||||||
return dl;
|
return dl;
|
||||||
}
|
}
|
||||||
|
|
||||||
Download::Ptr Download::makeFile(QUrl url, QString path, Options options)
|
auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Ptr
|
||||||
{
|
{
|
||||||
Download* dl = new Download();
|
auto* dl = new Download();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
dl->m_sink.reset(new FileSink(path));
|
dl->m_sink.reset(new FileSink(path));
|
||||||
@ -69,29 +87,32 @@ void Download::addValidator(Validator* v)
|
|||||||
m_sink->addValidator(v);
|
m_sink->addValidator(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::startImpl()
|
void Download::executeTask()
|
||||||
{
|
{
|
||||||
if (m_status == Job_Aborted) {
|
setStatus(tr("Downloading %1").arg(m_url.toString()));
|
||||||
|
|
||||||
|
if (getState() == Task::State::AbortedByUser) {
|
||||||
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
|
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
|
||||||
emit aborted(m_index_within_job);
|
emitAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkRequest request(m_url);
|
QNetworkRequest request(m_url);
|
||||||
m_status = m_sink->init(request);
|
m_state = m_sink->init(request);
|
||||||
switch (m_status) {
|
switch (m_state) {
|
||||||
case Job_Finished:
|
case State::Succeeded:
|
||||||
emit succeeded(m_index_within_job);
|
emit succeeded();
|
||||||
qDebug() << "Download cache hit " << m_url.toString();
|
qDebug() << "Download cache hit " << m_url.toString();
|
||||||
return;
|
return;
|
||||||
case Job_InProgress:
|
case State::Running:
|
||||||
qDebug() << "Downloading " << m_url.toString();
|
qDebug() << "Downloading " << m_url.toString();
|
||||||
break;
|
break;
|
||||||
case Job_Failed_Proceed: // this is meaningless in this context. We do need a sink.
|
case State::Inactive:
|
||||||
case Job_NotStarted:
|
case State::Failed:
|
||||||
case Job_Failed:
|
emitFailed();
|
||||||
emit failed(m_index_within_job);
|
|
||||||
return;
|
return;
|
||||||
case Job_Aborted:
|
case State::AbortedByUser:
|
||||||
|
emitAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,8 +124,8 @@ void Download::startImpl()
|
|||||||
QNetworkReply* rep = m_network->get(request);
|
QNetworkReply* rep = m_network->get(request);
|
||||||
|
|
||||||
m_reply.reset(rep);
|
m_reply.reset(rep);
|
||||||
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
|
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
|
||||||
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
|
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
||||||
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
||||||
@ -112,26 +133,24 @@ void Download::startImpl()
|
|||||||
|
|
||||||
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
m_total_progress = bytesTotal;
|
setProgress(bytesReceived, bytesTotal);
|
||||||
m_progress = bytesReceived;
|
|
||||||
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::downloadError(QNetworkReply::NetworkError error)
|
void Download::downloadError(QNetworkReply::NetworkError error)
|
||||||
{
|
{
|
||||||
if (error == QNetworkReply::OperationCanceledError) {
|
if (error == QNetworkReply::OperationCanceledError) {
|
||||||
qCritical() << "Aborted " << m_url.toString();
|
qCritical() << "Aborted " << m_url.toString();
|
||||||
m_status = Job_Aborted;
|
m_state = State::AbortedByUser;
|
||||||
} else {
|
} else {
|
||||||
if (m_options & Option::AcceptLocalFiles) {
|
if (m_options & Option::AcceptLocalFiles) {
|
||||||
if (m_sink->hasLocalData()) {
|
if (m_sink->hasLocalData()) {
|
||||||
m_status = Job_Failed_Proceed;
|
m_state = State::Succeeded;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
|
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
|
||||||
m_status = Job_Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +165,7 @@ void Download::sslErrors(const QList<QSslError>& errors)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Download::handleRedirect()
|
auto Download::handleRedirect() -> bool
|
||||||
{
|
{
|
||||||
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||||
if (!redirect.isValid()) {
|
if (!redirect.isValid()) {
|
||||||
@ -195,7 +214,8 @@ bool Download::handleRedirect()
|
|||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
m_url = QUrl(redirect.toString());
|
||||||
qDebug() << "Following redirect to " << m_url.toString();
|
qDebug() << "Following redirect to " << m_url.toString();
|
||||||
start(m_network);
|
startAction(m_network);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,74 +228,71 @@ void Download::downloadFinished()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the download failed before this point ...
|
// if the download failed before this point ...
|
||||||
if (m_status == Job_Failed_Proceed) {
|
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
||||||
|
{
|
||||||
qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit succeeded(m_index_within_job);
|
emit succeeded();
|
||||||
return;
|
return;
|
||||||
} else if (m_status == Job_Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qDebug() << "Download failed in previous step:" << m_url.toString();
|
qDebug() << "Download failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emit failed("");
|
||||||
return;
|
return;
|
||||||
} else if (m_status == Job_Aborted) {
|
} else if (m_state == State::AbortedByUser) {
|
||||||
qDebug() << "Download aborted in previous step:" << m_url.toString();
|
qDebug() << "Download aborted in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit aborted(m_index_within_job);
|
emit aborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we got all the remaining data, if any
|
// make sure we got all the remaining data, if any
|
||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
if (data.size()) {
|
if (data.size()) {
|
||||||
qDebug() << "Writing extra" << data.size() << "bytes to" << m_target_path;
|
qDebug() << "Writing extra" << data.size() << "bytes";
|
||||||
m_status = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, finalize the whole graph
|
// otherwise, finalize the whole graph
|
||||||
m_status = m_sink->finalize(*m_reply.get());
|
m_state = m_sink->finalize(*m_reply.get());
|
||||||
if (m_status != Job_Finished) {
|
if (m_state != State::Succeeded) {
|
||||||
qDebug() << "Download failed to finalize:" << m_url.toString();
|
qDebug() << "Download failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emit failed("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
qDebug() << "Download succeeded:" << m_url.toString();
|
qDebug() << "Download succeeded:" << m_url.toString();
|
||||||
emit succeeded(m_index_within_job);
|
emit succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::downloadReadyRead()
|
void Download::downloadReadyRead()
|
||||||
{
|
{
|
||||||
if (m_status == Job_InProgress) {
|
if (m_state == State::Running) {
|
||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
m_status = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
if (m_status == Job_Failed) {
|
if (m_state == State::Failed) {
|
||||||
qCritical() << "Failed to process response chunk for " << m_target_path;
|
qCritical() << "Failed to process response chunk";
|
||||||
}
|
}
|
||||||
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "Cannot write to " << m_target_path << ", illegal status" << m_status;
|
qCritical() << "Cannot write download data! illegal status " << m_status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
bool Net::Download::abort()
|
auto Net::Download::abort() -> bool
|
||||||
{
|
{
|
||||||
if (m_reply) {
|
if (m_reply) {
|
||||||
m_reply->abort();
|
m_reply->abort();
|
||||||
} else {
|
} else {
|
||||||
m_status = Job_Aborted;
|
m_state = State::AbortedByUser;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Net::Download::canAbort()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,63 +35,54 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "NetAction.h"
|
|
||||||
#include "HttpMetaCache.h"
|
#include "HttpMetaCache.h"
|
||||||
#include "Validator.h"
|
#include "NetAction.h"
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
|
#include "Validator.h"
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Download : public NetAction
|
class Download : public NetAction {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public: /* types */
|
public:
|
||||||
typedef shared_qobject_ptr<class Download> Ptr;
|
using Ptr = shared_qobject_ptr<class Download>;
|
||||||
enum class Option
|
enum class Option { NoOptions = 0, AcceptLocalFiles = 1 };
|
||||||
{
|
|
||||||
NoOptions = 0,
|
|
||||||
AcceptLocalFiles = 1
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(Options, Option)
|
Q_DECLARE_FLAGS(Options, Option)
|
||||||
|
|
||||||
protected: /* con/des */
|
protected:
|
||||||
explicit Download();
|
explicit Download();
|
||||||
public:
|
|
||||||
virtual ~Download(){};
|
|
||||||
static Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions);
|
|
||||||
static Download::Ptr makeByteArray(QUrl url, QByteArray *output, Options options = Option::NoOptions);
|
|
||||||
static Download::Ptr makeFile(QUrl url, QString path, Options options = Option::NoOptions);
|
|
||||||
|
|
||||||
public: /* methods */
|
public:
|
||||||
QString getTargetFilepath()
|
~Download() override = default;
|
||||||
{
|
|
||||||
return m_target_path;
|
|
||||||
}
|
|
||||||
void addValidator(Validator * v);
|
|
||||||
bool abort() override;
|
|
||||||
bool canAbort() override;
|
|
||||||
|
|
||||||
private: /* methods */
|
static auto makeCached(QUrl url, MetaEntryPtr entry, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
bool handleRedirect();
|
static auto makeByteArray(QUrl url, QByteArray* output, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
static auto makeFile(QUrl url, QString path, Options options = Option::NoOptions) -> Download::Ptr;
|
||||||
|
|
||||||
protected slots:
|
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 downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
void downloadError(QNetworkReply::NetworkError error) override;
|
||||||
void sslErrors(const QList<QSslError> & errors);
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
void downloadFinished() override;
|
void downloadFinished() override;
|
||||||
void downloadReadyRead() override;
|
void downloadReadyRead() override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void startImpl() override;
|
void executeTask() override;
|
||||||
|
|
||||||
private: /* data */
|
private:
|
||||||
// FIXME: remove this, it has no business being here.
|
|
||||||
QString m_target_path;
|
|
||||||
std::unique_ptr<Sink> m_sink;
|
std::unique_ptr<Sink> m_sink;
|
||||||
Options m_options;
|
Options m_options;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(Net::Download::Options)
|
||||||
|
@ -1,109 +1,131 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "FileSink.h"
|
#include "FileSink.h"
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
FileSink::FileSink(QString filename)
|
Task::State FileSink::init(QNetworkRequest& request)
|
||||||
:m_filename(filename)
|
|
||||||
{
|
|
||||||
// nil
|
|
||||||
}
|
|
||||||
|
|
||||||
FileSink::~FileSink()
|
|
||||||
{
|
|
||||||
// nil
|
|
||||||
}
|
|
||||||
|
|
||||||
JobStatus FileSink::init(QNetworkRequest& request)
|
|
||||||
{
|
{
|
||||||
auto result = initCache(request);
|
auto result = initCache(request);
|
||||||
if(result != Job_InProgress)
|
if (result != Task::State::Running) {
|
||||||
{
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new save file and open it for writing
|
// create a new save file and open it for writing
|
||||||
if (!FS::ensureFilePathExists(m_filename))
|
if (!FS::ensureFilePathExists(m_filename)) {
|
||||||
{
|
|
||||||
qCritical() << "Could not create folder for " + m_filename;
|
qCritical() << "Could not create folder for " + m_filename;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
wroteAnyData = false;
|
wroteAnyData = false;
|
||||||
m_output_file.reset(new QSaveFile(m_filename));
|
m_output_file.reset(new QSaveFile(m_filename));
|
||||||
if (!m_output_file->open(QIODevice::WriteOnly))
|
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||||
{
|
|
||||||
qCritical() << "Could not open " + m_filename + " for writing";
|
qCritical() << "Could not open " + m_filename + " for writing";
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(initAllValidators(request))
|
if (initAllValidators(request))
|
||||||
return Job_InProgress;
|
return Task::State::Running;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus FileSink::initCache(QNetworkRequest &)
|
Task::State FileSink::write(QByteArray& data)
|
||||||
{
|
{
|
||||||
return Job_InProgress;
|
if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
|
||||||
}
|
|
||||||
|
|
||||||
JobStatus FileSink::write(QByteArray& data)
|
|
||||||
{
|
|
||||||
if (!writeAllValidators(data) || m_output_file->write(data) != data.size())
|
|
||||||
{
|
|
||||||
qCritical() << "Failed writing into " + m_filename;
|
qCritical() << "Failed writing into " + m_filename;
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
m_output_file.reset();
|
m_output_file.reset();
|
||||||
wroteAnyData = false;
|
wroteAnyData = false;
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
wroteAnyData = true;
|
wroteAnyData = true;
|
||||||
return Job_InProgress;
|
return Task::State::Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus FileSink::abort()
|
Task::State FileSink::abort()
|
||||||
{
|
{
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus FileSink::finalize(QNetworkReply& reply)
|
Task::State FileSink::finalize(QNetworkReply& reply)
|
||||||
{
|
{
|
||||||
bool gotFile = false;
|
bool gotFile = false;
|
||||||
QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
QVariant statusCodeV = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
bool validStatus = false;
|
bool validStatus = false;
|
||||||
int statusCode = statusCodeV.toInt(&validStatus);
|
int statusCode = statusCodeV.toInt(&validStatus);
|
||||||
if(validStatus)
|
if (validStatus) {
|
||||||
{
|
|
||||||
// this leaves out 304 Not Modified
|
// this leaves out 304 Not Modified
|
||||||
gotFile = statusCode == 200 || statusCode == 203;
|
gotFile = statusCode == 200 || statusCode == 203;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we wrote any data to the save file, we try to commit the data to the real file.
|
// if we wrote any data to the save file, we try to commit the data to the real file.
|
||||||
// if it actually got a proper file, we write it even if it was empty
|
// if it actually got a proper file, we write it even if it was empty
|
||||||
if (gotFile || wroteAnyData)
|
if (gotFile || wroteAnyData) {
|
||||||
{
|
|
||||||
// ask validators for data consistency
|
// ask validators for data consistency
|
||||||
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
||||||
if(!finalizeAllValidators(reply))
|
if (!finalizeAllValidators(reply))
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
|
|
||||||
// nothing went wrong...
|
// nothing went wrong...
|
||||||
if (!m_output_file->commit())
|
if (!m_output_file->commit()) {
|
||||||
{
|
|
||||||
qCritical() << "Failed to commit changes to " << m_filename;
|
qCritical() << "Failed to commit changes to " << m_filename;
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
return Job_Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// then get rid of the save file
|
// then get rid of the save file
|
||||||
m_output_file.reset();
|
m_output_file.reset();
|
||||||
|
|
||||||
return finalizeCache(reply);
|
return finalizeCache(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus FileSink::finalizeCache(QNetworkReply &)
|
Task::State FileSink::initCache(QNetworkRequest&)
|
||||||
{
|
{
|
||||||
return Job_Finished;
|
return Task::State::Running;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task::State FileSink::finalizeCache(QNetworkReply&)
|
||||||
|
{
|
||||||
|
return Task::State::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSink::hasLocalData()
|
bool FileSink::hasLocalData()
|
||||||
@ -111,4 +133,4 @@ bool FileSink::hasLocalData()
|
|||||||
QFileInfo info(m_filename);
|
QFileInfo info(m_filename);
|
||||||
return info.exists() && info.size() != 0;
|
return info.exists() && info.size() != 0;
|
||||||
}
|
}
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,28 +1,65 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Sink.h"
|
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
|
|
||||||
|
#include "Sink.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class FileSink : public Sink
|
class FileSink : public Sink {
|
||||||
{
|
public:
|
||||||
public: /* con/des */
|
FileSink(QString filename) : m_filename(filename){};
|
||||||
FileSink(QString filename);
|
virtual ~FileSink() = default;
|
||||||
virtual ~FileSink();
|
|
||||||
|
|
||||||
public: /* methods */
|
public:
|
||||||
JobStatus init(QNetworkRequest & request) override;
|
auto init(QNetworkRequest& request) -> Task::State override;
|
||||||
JobStatus write(QByteArray & data) override;
|
auto write(QByteArray& data) -> Task::State override;
|
||||||
JobStatus abort() override;
|
auto abort() -> Task::State override;
|
||||||
JobStatus finalize(QNetworkReply & reply) override;
|
auto finalize(QNetworkReply& reply) -> Task::State override;
|
||||||
bool hasLocalData() override;
|
|
||||||
|
|
||||||
protected: /* methods */
|
auto hasLocalData() -> bool override;
|
||||||
virtual JobStatus initCache(QNetworkRequest &);
|
|
||||||
virtual JobStatus finalizeCache(QNetworkReply &reply);
|
|
||||||
|
|
||||||
protected: /* data */
|
protected:
|
||||||
|
virtual auto initCache(QNetworkRequest&) -> Task::State;
|
||||||
|
virtual auto finalizeCache(QNetworkReply& reply) -> Task::State;
|
||||||
|
|
||||||
|
protected:
|
||||||
QString m_filename;
|
QString m_filename;
|
||||||
bool wroteAnyData = false;
|
bool wroteAnyData = false;
|
||||||
std::unique_ptr<QSaveFile> m_output_file;
|
std::unique_ptr<QSaveFile> m_output_file;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,29 +35,26 @@
|
|||||||
|
|
||||||
#include "HttpMetaCache.h"
|
#include "HttpMetaCache.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "Json.h"
|
||||||
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include <QJsonDocument>
|
auto MetaEntry::getFullPath() -> QString
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
QString MetaEntry::getFullPath()
|
|
||||||
{
|
{
|
||||||
// FIXME: make local?
|
// FIXME: make local?
|
||||||
return FS::PathCombine(basePath, relativePath);
|
return FS::PathCombine(basePath, relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpMetaCache::HttpMetaCache(QString path) : QObject()
|
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
||||||
{
|
{
|
||||||
m_index_file = path;
|
|
||||||
saveBatchingTimer.setSingleShot(true);
|
saveBatchingTimer.setSingleShot(true);
|
||||||
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
|
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
|
||||||
|
|
||||||
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
|
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,45 +64,42 @@ HttpMetaCache::~HttpMetaCache()
|
|||||||
SaveNow();
|
SaveNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaEntryPtr HttpMetaCache::getEntry(QString base, QString resource_path)
|
auto HttpMetaCache::getEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
// no base. no base path. can't store
|
// no base. no base path. can't store
|
||||||
if (!m_entries.contains(base))
|
if (!m_entries.contains(base)) {
|
||||||
{
|
|
||||||
// TODO: log problem
|
// TODO: log problem
|
||||||
return MetaEntryPtr();
|
return {};
|
||||||
}
|
}
|
||||||
EntryMap &map = m_entries[base];
|
|
||||||
if (map.entry_list.contains(resource_path))
|
EntryMap& map = m_entries[base];
|
||||||
{
|
if (map.entry_list.contains(resource_path)) {
|
||||||
return map.entry_list[resource_path];
|
return map.entry_list[resource_path];
|
||||||
}
|
}
|
||||||
return MetaEntryPtr();
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag)
|
auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString expected_etag) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
auto entry = getEntry(base, resource_path);
|
auto entry = getEntry(base, resource_path);
|
||||||
// it's not present? generate a default stale entry
|
// it's not present? generate a default stale entry
|
||||||
if (!entry)
|
if (!entry) {
|
||||||
{
|
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &selected_base = m_entries[base];
|
auto& selected_base = m_entries[base];
|
||||||
QString real_path = FS::PathCombine(selected_base.base_path, resource_path);
|
QString real_path = FS::PathCombine(selected_base.base_path, resource_path);
|
||||||
QFileInfo finfo(real_path);
|
QFileInfo finfo(real_path);
|
||||||
|
|
||||||
// is the file really there? if not -> stale
|
// is the file really there? if not -> stale
|
||||||
if (!finfo.isFile() || !finfo.isReadable())
|
if (!finfo.isFile() || !finfo.isReadable()) {
|
||||||
{
|
|
||||||
// if the file doesn't exist, we disown the entry
|
// if the file doesn't exist, we disown the entry
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expected_etag.isEmpty() && expected_etag != entry->etag)
|
if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
|
||||||
{
|
|
||||||
// if the etag doesn't match expected, we disown the entry
|
// if the etag doesn't match expected, we disown the entry
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
@ -93,18 +107,15 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
|
|||||||
|
|
||||||
// if the file changed, check md5sum
|
// if the file changed, check md5sum
|
||||||
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
||||||
if (file_last_changed != entry->local_changed_timestamp)
|
if (file_last_changed != entry->local_changed_timestamp) {
|
||||||
{
|
|
||||||
QFile input(real_path);
|
QFile input(real_path);
|
||||||
input.open(QIODevice::ReadOnly);
|
input.open(QIODevice::ReadOnly);
|
||||||
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5)
|
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
|
||||||
.toHex()
|
if (entry->md5sum != md5sum) {
|
||||||
.constData();
|
|
||||||
if (entry->md5sum != md5sum)
|
|
||||||
{
|
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// md5sums matched... keep entry and save the new state to file
|
// md5sums matched... keep entry and save the new state to file
|
||||||
entry->local_changed_timestamp = file_last_changed;
|
entry->local_changed_timestamp = file_last_changed;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
@ -115,42 +126,42 @@ MetaEntryPtr HttpMetaCache::resolveEntry(QString base, QString resource_path, QS
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpMetaCache::updateEntry(MetaEntryPtr stale_entry)
|
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||||
{
|
{
|
||||||
if (!m_entries.contains(stale_entry->baseId))
|
if (!m_entries.contains(stale_entry->baseId)) {
|
||||||
{
|
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
|
||||||
qCritical() << "Cannot add entry with unknown base: "
|
|
||||||
<< stale_entry->baseId.toLocal8Bit();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (stale_entry->stale)
|
|
||||||
{
|
if (stale_entry->stale) {
|
||||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
|
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpMetaCache::evictEntry(MetaEntryPtr entry)
|
auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
||||||
{
|
{
|
||||||
if(entry)
|
if (!entry)
|
||||||
{
|
return false;
|
||||||
|
|
||||||
entry->stale = true;
|
entry->stale = true;
|
||||||
SaveEventually();
|
SaveEventually();
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaEntryPtr HttpMetaCache::staleEntry(QString base, QString resource_path)
|
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||||
{
|
{
|
||||||
auto foo = new MetaEntry();
|
auto foo = new MetaEntry();
|
||||||
foo->baseId = base;
|
foo->baseId = base;
|
||||||
foo->basePath = getBasePath(base);
|
foo->basePath = getBasePath(base);
|
||||||
foo->relativePath = resource_path;
|
foo->relativePath = resource_path;
|
||||||
foo->stale = true;
|
foo->stale = true;
|
||||||
|
|
||||||
return MetaEntryPtr(foo);
|
return MetaEntryPtr(foo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,24 +170,25 @@ void HttpMetaCache::addBase(QString base, QString base_root)
|
|||||||
// TODO: report error
|
// TODO: report error
|
||||||
if (m_entries.contains(base))
|
if (m_entries.contains(base))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// TODO: check if the base path is valid
|
// TODO: check if the base path is valid
|
||||||
EntryMap foo;
|
EntryMap foo;
|
||||||
foo.base_path = base_root;
|
foo.base_path = base_root;
|
||||||
m_entries[base] = foo;
|
m_entries[base] = foo;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HttpMetaCache::getBasePath(QString base)
|
auto HttpMetaCache::getBasePath(QString base) -> QString
|
||||||
{
|
{
|
||||||
if (m_entries.contains(base))
|
if (m_entries.contains(base)) {
|
||||||
{
|
|
||||||
return m_entries[base].base_path;
|
return m_entries[base].base_path;
|
||||||
}
|
}
|
||||||
return QString();
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpMetaCache::Load()
|
void HttpMetaCache::Load()
|
||||||
{
|
{
|
||||||
if(m_index_file.isNull())
|
if (m_index_file.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QFile index(m_index_file);
|
QFile index(m_index_file);
|
||||||
@ -184,41 +196,35 @@ void HttpMetaCache::Load()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
|
QJsonDocument json = QJsonDocument::fromJson(index.readAll());
|
||||||
if (!json.isObject())
|
|
||||||
return;
|
auto root = Json::requireObject(json, "HttpMetaCache root");
|
||||||
auto root = json.object();
|
|
||||||
// check file version first
|
// check file version first
|
||||||
auto version_val = root.value("version");
|
auto version_val = Json::ensureString(root, "version");
|
||||||
if (!version_val.isString())
|
if (version_val != "1")
|
||||||
return;
|
|
||||||
if (version_val.toString() != "1")
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// read the entry array
|
// read the entry array
|
||||||
auto entries_val = root.value("entries");
|
auto array = Json::ensureArray(root, "entries");
|
||||||
if (!entries_val.isArray())
|
for (auto element : array) {
|
||||||
return;
|
auto element_obj = Json::ensureObject(element);
|
||||||
QJsonArray array = entries_val.toArray();
|
auto base = Json::ensureString(element_obj, "base");
|
||||||
for (auto element : array)
|
|
||||||
{
|
|
||||||
if (!element.isObject())
|
|
||||||
return;
|
|
||||||
auto element_obj = element.toObject();
|
|
||||||
QString base = element_obj.value("base").toString();
|
|
||||||
if (!m_entries.contains(base))
|
if (!m_entries.contains(base))
|
||||||
continue;
|
continue;
|
||||||
auto &entrymap = m_entries[base];
|
|
||||||
|
auto& entrymap = m_entries[base];
|
||||||
|
|
||||||
auto foo = new MetaEntry();
|
auto foo = new MetaEntry();
|
||||||
foo->baseId = base;
|
foo->baseId = base;
|
||||||
QString path = foo->relativePath = element_obj.value("path").toString();
|
foo->relativePath = Json::ensureString(element_obj, "path");
|
||||||
foo->md5sum = element_obj.value("md5sum").toString();
|
foo->md5sum = Json::ensureString(element_obj, "md5sum");
|
||||||
foo->etag = element_obj.value("etag").toString();
|
foo->etag = Json::ensureString(element_obj, "etag");
|
||||||
foo->local_changed_timestamp = element_obj.value("last_changed_timestamp").toDouble();
|
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||||
foo->remote_changed_timestamp =
|
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||||
element_obj.value("remote_changed_timestamp").toString();
|
|
||||||
// presumed innocent until closer examination
|
// presumed innocent until closer examination
|
||||||
foo->stale = false;
|
foo->stale = false;
|
||||||
entrymap.entry_list[path] = MetaEntryPtr(foo);
|
|
||||||
|
entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,42 +237,36 @@ void HttpMetaCache::SaveEventually()
|
|||||||
|
|
||||||
void HttpMetaCache::SaveNow()
|
void HttpMetaCache::SaveNow()
|
||||||
{
|
{
|
||||||
if(m_index_file.isNull())
|
if (m_index_file.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QJsonObject toplevel;
|
QJsonObject toplevel;
|
||||||
toplevel.insert("version", QJsonValue(QString("1")));
|
Json::writeString(toplevel, "version", "1");
|
||||||
|
|
||||||
QJsonArray entriesArr;
|
QJsonArray entriesArr;
|
||||||
for (auto group : m_entries)
|
for (auto group : m_entries) {
|
||||||
{
|
for (auto entry : group.entry_list) {
|
||||||
for (auto entry : group.entry_list)
|
|
||||||
{
|
|
||||||
// do not save stale entries. they are dead.
|
// do not save stale entries. they are dead.
|
||||||
if(entry->stale)
|
if (entry->stale) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject entryObj;
|
QJsonObject entryObj;
|
||||||
entryObj.insert("base", QJsonValue(entry->baseId));
|
Json::writeString(entryObj, "base", entry->baseId);
|
||||||
entryObj.insert("path", QJsonValue(entry->relativePath));
|
Json::writeString(entryObj, "path", entry->relativePath);
|
||||||
entryObj.insert("md5sum", QJsonValue(entry->md5sum));
|
Json::writeString(entryObj, "md5sum", entry->md5sum);
|
||||||
entryObj.insert("etag", QJsonValue(entry->etag));
|
Json::writeString(entryObj, "etag", entry->etag);
|
||||||
entryObj.insert("last_changed_timestamp",
|
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
||||||
QJsonValue(double(entry->local_changed_timestamp)));
|
|
||||||
if (!entry->remote_changed_timestamp.isEmpty())
|
if (!entry->remote_changed_timestamp.isEmpty())
|
||||||
entryObj.insert("remote_changed_timestamp",
|
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
|
||||||
QJsonValue(entry->remote_changed_timestamp));
|
|
||||||
entriesArr.append(entryObj);
|
entriesArr.append(entryObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toplevel.insert("entries", entriesArr);
|
toplevel.insert("entries", entriesArr);
|
||||||
|
|
||||||
QJsonDocument doc(toplevel);
|
try {
|
||||||
try
|
Json::write(toplevel, m_index_file);
|
||||||
{
|
} catch (const Exception& e) {
|
||||||
FS::write(m_index_file, doc.toJson());
|
|
||||||
}
|
|
||||||
catch (const Exception &e)
|
|
||||||
{
|
|
||||||
qWarning() << e.what();
|
qWarning() << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,57 +34,37 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QString>
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <qtimer.h>
|
#include <QString>
|
||||||
|
#include <QTimer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class HttpMetaCache;
|
class HttpMetaCache;
|
||||||
|
|
||||||
class MetaEntry
|
class MetaEntry {
|
||||||
{
|
friend class HttpMetaCache;
|
||||||
friend class HttpMetaCache;
|
|
||||||
protected:
|
protected:
|
||||||
MetaEntry() {}
|
MetaEntry() = default;
|
||||||
public:
|
|
||||||
bool isStale()
|
public:
|
||||||
{
|
auto isStale() -> bool { return stale; }
|
||||||
return stale;
|
void setStale(bool stale) { this->stale = stale; }
|
||||||
}
|
|
||||||
void setStale(bool stale)
|
auto getFullPath() -> QString;
|
||||||
{
|
|
||||||
this->stale = stale;
|
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
|
||||||
}
|
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
|
||||||
QString getFullPath();
|
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
|
||||||
QString getRemoteChangedTimestamp()
|
|
||||||
{
|
auto getETag() -> QString { return etag; }
|
||||||
return remote_changed_timestamp;
|
void setETag(QString etag) { this->etag = etag; }
|
||||||
}
|
|
||||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp)
|
auto getMD5Sum() -> QString { return md5sum; }
|
||||||
{
|
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
|
||||||
this->remote_changed_timestamp = remote_changed_timestamp;
|
|
||||||
}
|
protected:
|
||||||
void setLocalChangedTimestamp(qint64 timestamp)
|
|
||||||
{
|
|
||||||
local_changed_timestamp = timestamp;
|
|
||||||
}
|
|
||||||
QString getETag()
|
|
||||||
{
|
|
||||||
return etag;
|
|
||||||
}
|
|
||||||
void setETag(QString etag)
|
|
||||||
{
|
|
||||||
this->etag = etag;
|
|
||||||
}
|
|
||||||
QString getMD5Sum()
|
|
||||||
{
|
|
||||||
return md5sum;
|
|
||||||
}
|
|
||||||
void setMD5Sum(QString md5sum)
|
|
||||||
{
|
|
||||||
this->md5sum = md5sum;
|
|
||||||
}
|
|
||||||
protected:
|
|
||||||
QString baseId;
|
QString baseId;
|
||||||
QString basePath;
|
QString basePath;
|
||||||
QString relativePath;
|
QString relativePath;
|
||||||
@ -75,48 +75,48 @@ protected:
|
|||||||
bool stale = true;
|
bool stale = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<MetaEntry> MetaEntryPtr;
|
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
||||||
|
|
||||||
class HttpMetaCache : public QObject
|
class HttpMetaCache : public QObject {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
// supply path to the cache index file
|
// supply path to the cache index file
|
||||||
HttpMetaCache(QString path = QString());
|
HttpMetaCache(QString path = QString());
|
||||||
~HttpMetaCache();
|
~HttpMetaCache() override;
|
||||||
|
|
||||||
// get the entry solely from the cache
|
// get the entry solely from the cache
|
||||||
// you probably don't want this, unless you have some specific caching needs.
|
// you probably don't want this, unless you have some specific caching needs.
|
||||||
MetaEntryPtr getEntry(QString base, QString resource_path);
|
auto getEntry(QString base, QString resource_path) -> MetaEntryPtr;
|
||||||
|
|
||||||
// get the entry from cache and verify that it isn't stale (within reason)
|
// get the entry from cache and verify that it isn't stale (within reason)
|
||||||
MetaEntryPtr resolveEntry(QString base, QString resource_path,
|
auto resolveEntry(QString base, QString resource_path, QString expected_etag = QString()) -> MetaEntryPtr;
|
||||||
QString expected_etag = QString());
|
|
||||||
|
|
||||||
// add a previously resolved stale entry
|
// add a previously resolved stale entry
|
||||||
bool updateEntry(MetaEntryPtr stale_entry);
|
auto updateEntry(MetaEntryPtr stale_entry) -> bool;
|
||||||
|
|
||||||
// evict selected entry from cache
|
// evict selected entry from cache
|
||||||
bool evictEntry(MetaEntryPtr entry);
|
auto evictEntry(MetaEntryPtr entry) -> bool;
|
||||||
|
|
||||||
void addBase(QString base, QString base_root);
|
void addBase(QString base, QString base_root);
|
||||||
|
|
||||||
// (re)start a timer that calls SaveNow later.
|
// (re)start a timer that calls SaveNow later.
|
||||||
void SaveEventually();
|
void SaveEventually();
|
||||||
void Load();
|
void Load();
|
||||||
QString getBasePath(QString base);
|
|
||||||
public
|
auto getBasePath(QString base) -> QString;
|
||||||
slots:
|
|
||||||
|
public slots:
|
||||||
void SaveNow();
|
void SaveNow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// create a new stale entry, given the parameters
|
// create a new stale entry, given the parameters
|
||||||
MetaEntryPtr staleEntry(QString base, QString resource_path);
|
auto staleEntry(QString base, QString resource_path) -> MetaEntryPtr;
|
||||||
struct EntryMap
|
|
||||||
{
|
struct EntryMap {
|
||||||
QString base_path;
|
QString base_path;
|
||||||
QMap<QString, MetaEntryPtr> entry_list;
|
QMap<QString, MetaEntryPtr> entry_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
QMap<QString, EntryMap> m_entries;
|
QMap<QString, EntryMap> m_entries;
|
||||||
QString m_index_file;
|
QString m_index_file;
|
||||||
QTimer saveBatchingTimer;
|
QTimer saveBatchingTimer;
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@ -12,17 +47,13 @@ MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum)
|
|||||||
addValidator(md5sum);
|
addValidator(md5sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
MetaCacheSink::~MetaCacheSink()
|
Task::State MetaCacheSink::initCache(QNetworkRequest& request)
|
||||||
{
|
|
||||||
// nil
|
|
||||||
}
|
|
||||||
|
|
||||||
JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
|
|
||||||
{
|
{
|
||||||
if (!m_entry->isStale())
|
if (!m_entry->isStale())
|
||||||
{
|
{
|
||||||
return Job_Finished;
|
return Task::State::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if file exists, if it does, use its information for the request
|
// check if file exists, if it does, use its information for the request
|
||||||
QFile current(m_filename);
|
QFile current(m_filename);
|
||||||
if(current.exists() && current.size() != 0)
|
if(current.exists() && current.size() != 0)
|
||||||
@ -36,25 +67,31 @@ JobStatus MetaCacheSink::initCache(QNetworkRequest& request)
|
|||||||
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
|
request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Job_InProgress;
|
|
||||||
|
return Task::State::Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobStatus MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
||||||
{
|
{
|
||||||
QFileInfo output_file_info(m_filename);
|
QFileInfo output_file_info(m_filename);
|
||||||
|
|
||||||
if(wroteAnyData)
|
if(wroteAnyData)
|
||||||
{
|
{
|
||||||
m_entry->setMD5Sum(m_md5Node->hash().toHex().constData());
|
m_entry->setMD5Sum(m_md5Node->hash().toHex().constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_entry->setETag(reply.rawHeader("ETag").constData());
|
m_entry->setETag(reply.rawHeader("ETag").constData());
|
||||||
|
|
||||||
if (reply.hasRawHeader("Last-Modified"))
|
if (reply.hasRawHeader("Last-Modified"))
|
||||||
{
|
{
|
||||||
m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData());
|
m_entry->setRemoteChangedTimestamp(reply.rawHeader("Last-Modified").constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
|
m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
|
||||||
m_entry->setStale(false);
|
m_entry->setStale(false);
|
||||||
APPLICATION->metacache()->updateEntry(m_entry);
|
APPLICATION->metacache()->updateEntry(m_entry);
|
||||||
return Job_Finished;
|
|
||||||
|
return Task::State::Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MetaCacheSink::hasLocalData()
|
bool MetaCacheSink::hasLocalData()
|
||||||
|
@ -1,22 +1,58 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "FileSink.h"
|
|
||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
|
#include "FileSink.h"
|
||||||
#include "net/HttpMetaCache.h"
|
#include "net/HttpMetaCache.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class MetaCacheSink : public FileSink
|
class MetaCacheSink : public FileSink {
|
||||||
{
|
public:
|
||||||
public: /* con/des */
|
MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum);
|
||||||
MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum);
|
virtual ~MetaCacheSink() = default;
|
||||||
virtual ~MetaCacheSink();
|
|
||||||
bool hasLocalData() override;
|
|
||||||
|
|
||||||
protected: /* methods */
|
auto hasLocalData() -> bool override;
|
||||||
JobStatus initCache(QNetworkRequest & request) override;
|
|
||||||
JobStatus finalizeCache(QNetworkReply & reply) override;
|
|
||||||
|
|
||||||
private: /* data */
|
protected:
|
||||||
|
auto initCache(QNetworkRequest& request) -> Task::State override;
|
||||||
|
auto finalizeCache(QNetworkReply& reply) -> Task::State override;
|
||||||
|
|
||||||
|
private:
|
||||||
MetaEntryPtr m_entry;
|
MetaEntryPtr m_entry;
|
||||||
ChecksumValidator * m_md5Node;
|
ChecksumValidator* m_md5Node;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace Net
|
namespace Net {
|
||||||
{
|
enum class Mode { Offline, Online };
|
||||||
enum class Mode
|
|
||||||
{
|
|
||||||
Offline,
|
|
||||||
Online
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,94 +35,42 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QUrl>
|
|
||||||
#include <memory>
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObjectPtr.h>
|
#include <QUrl>
|
||||||
|
|
||||||
enum JobStatus
|
#include "QObjectPtr.h"
|
||||||
{
|
#include "tasks/Task.h"
|
||||||
Job_NotStarted,
|
|
||||||
Job_InProgress,
|
|
||||||
Job_Finished,
|
|
||||||
Job_Failed,
|
|
||||||
Job_Aborted,
|
|
||||||
/*
|
|
||||||
* FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion.
|
|
||||||
* Same could be true for aborted task - the presence of pre-existing result is a separate concern
|
|
||||||
*/
|
|
||||||
Job_Failed_Proceed
|
|
||||||
};
|
|
||||||
|
|
||||||
class NetAction : public QObject
|
class NetAction : public Task {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit NetAction() : QObject(nullptr) {};
|
explicit NetAction() : Task() {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<NetAction>;
|
using Ptr = shared_qobject_ptr<NetAction>;
|
||||||
|
|
||||||
virtual ~NetAction() {};
|
virtual ~NetAction() = default;
|
||||||
|
|
||||||
bool isRunning() const
|
QUrl url() { return m_url; }
|
||||||
{
|
auto index() -> int { return m_index_within_job; }
|
||||||
return m_status == Job_InProgress;
|
|
||||||
}
|
|
||||||
bool isFinished() const
|
|
||||||
{
|
|
||||||
return m_status >= Job_Finished;
|
|
||||||
}
|
|
||||||
bool wasSuccessful() const
|
|
||||||
{
|
|
||||||
return m_status == Job_Finished || m_status == Job_Failed_Proceed;
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 totalProgress() const
|
protected slots:
|
||||||
{
|
|
||||||
return m_total_progress;
|
|
||||||
}
|
|
||||||
qint64 currentProgress() const
|
|
||||||
{
|
|
||||||
return m_progress;
|
|
||||||
}
|
|
||||||
virtual bool abort()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
virtual bool canAbort()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QUrl url()
|
|
||||||
{
|
|
||||||
return m_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void started(int index);
|
|
||||||
void netActionProgress(int index, qint64 current, qint64 total);
|
|
||||||
void succeeded(int index);
|
|
||||||
void failed(int index);
|
|
||||||
void aborted(int index);
|
|
||||||
|
|
||||||
protected slots:
|
|
||||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
|
||||||
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
|
||||||
virtual void downloadFinished() = 0;
|
virtual void downloadFinished() = 0;
|
||||||
virtual void downloadReadyRead() = 0;
|
virtual void downloadReadyRead() = 0;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start(shared_qobject_ptr<QNetworkAccessManager> network) {
|
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
|
||||||
|
{
|
||||||
m_network = network;
|
m_network = network;
|
||||||
startImpl();
|
executeTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void startImpl() = 0;
|
void executeTask() override {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
/// index within the parent job, FIXME: nuke
|
/// index within the parent job, FIXME: nuke
|
||||||
@ -113,10 +81,4 @@ public:
|
|||||||
|
|
||||||
/// source URL
|
/// source URL
|
||||||
QUrl m_url;
|
QUrl m_url;
|
||||||
|
|
||||||
qint64 m_progress = 0;
|
|
||||||
qint64 m_total_progress = 1;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
JobStatus m_status = Job_NotStarted;
|
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,64 +36,138 @@
|
|||||||
#include "NetJob.h"
|
#include "NetJob.h"
|
||||||
#include "Download.h"
|
#include "Download.h"
|
||||||
|
|
||||||
#include <QDebug>
|
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
|
||||||
|
{
|
||||||
|
action->m_index_within_job = m_downloads.size();
|
||||||
|
m_downloads.append(action);
|
||||||
|
part_info pi;
|
||||||
|
m_parts_progress.append(pi);
|
||||||
|
|
||||||
|
partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress());
|
||||||
|
|
||||||
|
if (action->isRunning()) {
|
||||||
|
connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); });
|
||||||
|
connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); });
|
||||||
|
connect(action.get(), &NetAction::aborted, [this, action](){ partAborted(action->index()); });
|
||||||
|
connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); });
|
||||||
|
connect(action.get(), &NetAction::status, this, &NetJob::status);
|
||||||
|
} else {
|
||||||
|
m_todo.append(m_parts_progress.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NetJob::canAbort() const -> bool
|
||||||
|
{
|
||||||
|
bool canFullyAbort = true;
|
||||||
|
|
||||||
|
// can abort the downloads on the queue?
|
||||||
|
for (auto index : m_todo) {
|
||||||
|
auto part = m_downloads[index];
|
||||||
|
canFullyAbort &= part->canAbort();
|
||||||
|
}
|
||||||
|
// can abort the active downloads?
|
||||||
|
for (auto index : m_doing) {
|
||||||
|
auto part = m_downloads[index];
|
||||||
|
canFullyAbort &= part->canAbort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return canFullyAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetJob::executeTask()
|
||||||
|
{
|
||||||
|
// hack that delays early failures so they can be caught easier
|
||||||
|
QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NetJob::getFailedFiles() -> QStringList
|
||||||
|
{
|
||||||
|
QStringList failed;
|
||||||
|
for (auto index : m_failed) {
|
||||||
|
failed.push_back(m_downloads[index]->url().toString());
|
||||||
|
}
|
||||||
|
failed.sort();
|
||||||
|
return failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NetJob::abort() -> bool
|
||||||
|
{
|
||||||
|
bool fullyAborted = true;
|
||||||
|
|
||||||
|
// fail all downloads on the queue
|
||||||
|
m_failed.unite(m_todo.toSet());
|
||||||
|
m_todo.clear();
|
||||||
|
|
||||||
|
// abort active downloads
|
||||||
|
auto toKill = m_doing.toList();
|
||||||
|
for (auto index : toKill) {
|
||||||
|
auto part = m_downloads[index];
|
||||||
|
fullyAborted &= part->abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullyAborted;
|
||||||
|
}
|
||||||
|
|
||||||
void NetJob::partSucceeded(int index)
|
void NetJob::partSucceeded(int index)
|
||||||
{
|
{
|
||||||
// do progress. all slots are 1 in size at least
|
// do progress. all slots are 1 in size at least
|
||||||
auto &slot = parts_progress[index];
|
auto& slot = m_parts_progress[index];
|
||||||
partProgress(index, slot.total_progress, slot.total_progress);
|
partProgress(index, slot.total_progress, slot.total_progress);
|
||||||
|
|
||||||
m_doing.remove(index);
|
m_doing.remove(index);
|
||||||
m_done.insert(index);
|
m_done.insert(index);
|
||||||
downloads[index].get()->disconnect(this);
|
m_downloads[index].get()->disconnect(this);
|
||||||
|
|
||||||
startMoreParts();
|
startMoreParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetJob::partFailed(int index)
|
void NetJob::partFailed(int index)
|
||||||
{
|
{
|
||||||
m_doing.remove(index);
|
m_doing.remove(index);
|
||||||
auto &slot = parts_progress[index];
|
|
||||||
if (slot.failures == 3)
|
auto& slot = m_parts_progress[index];
|
||||||
{
|
// Can try 3 times before failing by definitive
|
||||||
|
if (slot.failures == 3) {
|
||||||
m_failed.insert(index);
|
m_failed.insert(index);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
slot.failures++;
|
slot.failures++;
|
||||||
m_todo.enqueue(index);
|
m_todo.enqueue(index);
|
||||||
}
|
}
|
||||||
downloads[index].get()->disconnect(this);
|
|
||||||
|
m_downloads[index].get()->disconnect(this);
|
||||||
|
|
||||||
startMoreParts();
|
startMoreParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetJob::partAborted(int index)
|
void NetJob::partAborted(int index)
|
||||||
{
|
{
|
||||||
m_aborted = true;
|
m_aborted = true;
|
||||||
|
|
||||||
m_doing.remove(index);
|
m_doing.remove(index);
|
||||||
m_failed.insert(index);
|
m_failed.insert(index);
|
||||||
downloads[index].get()->disconnect(this);
|
m_downloads[index].get()->disconnect(this);
|
||||||
|
|
||||||
startMoreParts();
|
startMoreParts();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
|
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
auto &slot = parts_progress[index];
|
auto& slot = m_parts_progress[index];
|
||||||
slot.current_progress = bytesReceived;
|
slot.current_progress = bytesReceived;
|
||||||
slot.total_progress = bytesTotal;
|
slot.total_progress = bytesTotal;
|
||||||
|
|
||||||
int done = m_done.size();
|
int done = m_done.size();
|
||||||
int doing = m_doing.size();
|
int doing = m_doing.size();
|
||||||
int all = parts_progress.size();
|
int all = m_parts_progress.size();
|
||||||
|
|
||||||
qint64 bytesAll = 0;
|
qint64 bytesAll = 0;
|
||||||
qint64 bytesTotalAll = 0;
|
qint64 bytesTotalAll = 0;
|
||||||
for(auto & partIdx: m_doing)
|
for (auto& partIdx : m_doing) {
|
||||||
{
|
auto part = m_parts_progress[partIdx];
|
||||||
auto part = parts_progress[partIdx];
|
|
||||||
// do not count parts with unknown/nonsensical total size
|
// do not count parts with unknown/nonsensical total size
|
||||||
if(part.total_progress <= 0)
|
if (part.total_progress <= 0) {
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bytesAll += part.current_progress;
|
bytesAll += part.current_progress;
|
||||||
@ -85,134 +179,54 @@ void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
|
|||||||
auto current_total = all * 1000;
|
auto current_total = all * 1000;
|
||||||
// HACK: make sure it never jumps backwards.
|
// HACK: make sure it never jumps backwards.
|
||||||
// FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress
|
// FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress
|
||||||
if(m_current_progress == 1000) {
|
if (m_current_progress == 1000) {
|
||||||
m_current_progress = inprogress;
|
m_current_progress = inprogress;
|
||||||
}
|
}
|
||||||
if(m_current_progress > current)
|
if (m_current_progress > current) {
|
||||||
{
|
|
||||||
current = m_current_progress;
|
current = m_current_progress;
|
||||||
}
|
}
|
||||||
m_current_progress = current;
|
m_current_progress = current;
|
||||||
setProgress(current, current_total);
|
setProgress(current, current_total);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetJob::executeTask()
|
|
||||||
{
|
|
||||||
// hack that delays early failures so they can be caught easier
|
|
||||||
QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetJob::startMoreParts()
|
void NetJob::startMoreParts()
|
||||||
{
|
{
|
||||||
if(!isRunning())
|
if (!isRunning()) {
|
||||||
{
|
// this actually makes sense. You can put running m_downloads into a NetJob and then not start it until much later.
|
||||||
// this actually makes sense. You can put running downloads into a NetJob and then not start it until much later.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OK. We are actively processing tasks, proceed.
|
// OK. We are actively processing tasks, proceed.
|
||||||
// Check for final conditions if there's nothing in the queue.
|
// Check for final conditions if there's nothing in the queue.
|
||||||
if(!m_todo.size())
|
if (!m_todo.size()) {
|
||||||
{
|
if (!m_doing.size()) {
|
||||||
if(!m_doing.size())
|
if (!m_failed.size()) {
|
||||||
{
|
|
||||||
if(!m_failed.size())
|
|
||||||
{
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
} else if (m_aborted) {
|
||||||
else if(m_aborted)
|
|
||||||
{
|
|
||||||
emitAborted();
|
emitAborted();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n")));
|
emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// There's work to do, try to start more parts.
|
|
||||||
while (m_doing.size() < 6)
|
// There's work to do, try to start more parts, to a maximum of 6 concurrent ones.
|
||||||
{
|
while (m_doing.size() < 6) {
|
||||||
if(!m_todo.size())
|
if (m_todo.size() == 0)
|
||||||
return;
|
return;
|
||||||
int doThis = m_todo.dequeue();
|
int doThis = m_todo.dequeue();
|
||||||
m_doing.insert(doThis);
|
m_doing.insert(doThis);
|
||||||
auto part = downloads[doThis];
|
|
||||||
|
auto part = m_downloads[doThis];
|
||||||
|
|
||||||
// connect signals :D
|
// connect signals :D
|
||||||
connect(part.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
|
connect(part.get(), &NetAction::succeeded, this, [this, part]{ partSucceeded(part->index()); });
|
||||||
connect(part.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
|
connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); });
|
||||||
connect(part.get(), SIGNAL(aborted(int)), SLOT(partAborted(int)));
|
connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); });
|
||||||
connect(part.get(), SIGNAL(netActionProgress(int, qint64, qint64)),
|
connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); });
|
||||||
SLOT(partProgress(int, qint64, qint64)));
|
connect(part.get(), &NetAction::status, this, &NetJob::status);
|
||||||
part->start(m_network);
|
|
||||||
|
part->startAction(m_network);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QStringList NetJob::getFailedFiles()
|
|
||||||
{
|
|
||||||
QStringList failed;
|
|
||||||
for (auto index: m_failed)
|
|
||||||
{
|
|
||||||
failed.push_back(downloads[index]->url().toString());
|
|
||||||
}
|
|
||||||
failed.sort();
|
|
||||||
return failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetJob::canAbort() const
|
|
||||||
{
|
|
||||||
bool canFullyAbort = true;
|
|
||||||
// can abort the waiting?
|
|
||||||
for(auto index: m_todo)
|
|
||||||
{
|
|
||||||
auto part = downloads[index];
|
|
||||||
canFullyAbort &= part->canAbort();
|
|
||||||
}
|
|
||||||
// can abort the active?
|
|
||||||
for(auto index: m_doing)
|
|
||||||
{
|
|
||||||
auto part = downloads[index];
|
|
||||||
canFullyAbort &= part->canAbort();
|
|
||||||
}
|
|
||||||
return canFullyAbort;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetJob::abort()
|
|
||||||
{
|
|
||||||
bool fullyAborted = true;
|
|
||||||
// fail all waiting
|
|
||||||
m_failed.unite(m_todo.toSet());
|
|
||||||
m_todo.clear();
|
|
||||||
// abort active
|
|
||||||
auto toKill = m_doing.toList();
|
|
||||||
for(auto index: toKill)
|
|
||||||
{
|
|
||||||
auto part = downloads[index];
|
|
||||||
fullyAborted &= part->abort();
|
|
||||||
}
|
|
||||||
return fullyAborted;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetJob::addNetAction(NetAction::Ptr action)
|
|
||||||
{
|
|
||||||
action->m_index_within_job = downloads.size();
|
|
||||||
downloads.append(action);
|
|
||||||
part_info pi;
|
|
||||||
parts_progress.append(pi);
|
|
||||||
partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress());
|
|
||||||
|
|
||||||
if(action->isRunning())
|
|
||||||
{
|
|
||||||
connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
|
|
||||||
connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
|
|
||||||
connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64)));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_todo.append(parts_progress.size() - 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetJob::~NetJob() = default;
|
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,75 +34,65 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtNetwork>
|
#include <QtNetwork>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include "NetAction.h"
|
#include "NetAction.h"
|
||||||
#include "Download.h"
|
|
||||||
#include "HttpMetaCache.h"
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
#include "QObjectPtr.h"
|
|
||||||
|
|
||||||
class NetJob;
|
// Those are included so that they are also included by anyone using NetJob
|
||||||
|
#include "net/Download.h"
|
||||||
|
#include "net/HttpMetaCache.h"
|
||||||
|
|
||||||
class NetJob : public Task
|
class NetJob : public Task {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
|
||||||
|
public:
|
||||||
using Ptr = shared_qobject_ptr<NetJob>;
|
using Ptr = shared_qobject_ptr<NetJob>;
|
||||||
|
|
||||||
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : Task(), m_network(network)
|
explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network) : Task(), m_network(network)
|
||||||
{
|
{
|
||||||
setObjectName(job_name);
|
setObjectName(job_name);
|
||||||
}
|
}
|
||||||
virtual ~NetJob();
|
virtual ~NetJob() = default;
|
||||||
|
|
||||||
bool addNetAction(NetAction::Ptr action);
|
void executeTask() override;
|
||||||
|
|
||||||
NetAction::Ptr operator[](int index)
|
auto canAbort() const -> bool override;
|
||||||
{
|
|
||||||
return downloads[index];
|
|
||||||
}
|
|
||||||
const NetAction::Ptr at(const int index)
|
|
||||||
{
|
|
||||||
return downloads.at(index);
|
|
||||||
}
|
|
||||||
NetAction::Ptr first()
|
|
||||||
{
|
|
||||||
if (downloads.size())
|
|
||||||
return downloads[0];
|
|
||||||
return NetAction::Ptr();
|
|
||||||
}
|
|
||||||
int size() const
|
|
||||||
{
|
|
||||||
return downloads.size();
|
|
||||||
}
|
|
||||||
QStringList getFailedFiles();
|
|
||||||
|
|
||||||
bool canAbort() const override;
|
auto addNetAction(NetAction::Ptr action) -> bool;
|
||||||
|
|
||||||
private slots:
|
auto operator[](int index) -> NetAction::Ptr { return m_downloads[index]; }
|
||||||
|
auto at(int index) -> const NetAction::Ptr { return m_downloads.at(index); }
|
||||||
|
auto size() const -> int { return m_downloads.size(); }
|
||||||
|
auto first() -> NetAction::Ptr { return m_downloads.size() != 0 ? m_downloads[0] : NetAction::Ptr{}; }
|
||||||
|
|
||||||
|
auto getFailedFiles() -> QStringList;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
// Qt can't handle auto at the start for some reason?
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
void startMoreParts();
|
void startMoreParts();
|
||||||
|
|
||||||
public slots:
|
|
||||||
virtual void executeTask() override;
|
|
||||||
virtual bool abort() override;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
|
void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void partSucceeded(int index);
|
void partSucceeded(int index);
|
||||||
void partFailed(int index);
|
void partFailed(int index);
|
||||||
void partAborted(int index);
|
void partAborted(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
|
||||||
struct part_info
|
struct part_info {
|
||||||
{
|
|
||||||
qint64 current_progress = 0;
|
qint64 current_progress = 0;
|
||||||
qint64 total_progress = 1;
|
qint64 total_progress = 1;
|
||||||
int failures = 0;
|
int failures = 0;
|
||||||
};
|
};
|
||||||
QList<NetAction::Ptr> downloads;
|
|
||||||
QList<part_info> parts_progress;
|
QList<NetAction::Ptr> m_downloads;
|
||||||
|
QList<part_info> m_parts_progress;
|
||||||
QQueue<int> m_todo;
|
QQueue<int> m_todo;
|
||||||
QSet<int> m_doing;
|
QSet<int> m_doing;
|
||||||
QSet<int> m_done;
|
QSet<int> m_done;
|
||||||
|
@ -1,3 +1,39 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
|
* Copyright (C) 2022 Swirl <swurl@swurl.xyz>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "PasteUpload.h"
|
#include "PasteUpload.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
@ -8,8 +44,22 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
|
||||||
PasteUpload::PasteUpload(QWidget *window, QString text, QString url) : m_window(window), m_uploadUrl(url), m_text(text.toUtf8())
|
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
|
||||||
|
{{"0x0.st", "https://0x0.st", ""},
|
||||||
|
{"hastebin", "https://hst.sh", "/documents"},
|
||||||
|
{"paste.gg", "https://paste.gg", "/api/v1/pastes"},
|
||||||
|
{"mclo.gs", "https://api.mclo.gs", "/1/log"}}};
|
||||||
|
|
||||||
|
PasteUpload::PasteUpload(QWidget *window, QString text, QString baseUrl, PasteType pasteType) : m_window(window), m_baseUrl(baseUrl), m_pasteType(pasteType), m_text(text.toUtf8())
|
||||||
{
|
{
|
||||||
|
if (m_baseUrl == "")
|
||||||
|
m_baseUrl = PasteTypes.at(pasteType).defaultBase;
|
||||||
|
|
||||||
|
// HACK: Paste's docs say the standard API path is at /api/<version> but the official instance paste.gg doesn't follow that??
|
||||||
|
if (pasteType == PasteGG && m_baseUrl == PasteTypes.at(pasteType).defaultBase)
|
||||||
|
m_uploadUrl = "https://api.paste.gg/v1/pastes";
|
||||||
|
else
|
||||||
|
m_uploadUrl = m_baseUrl + PasteTypes.at(pasteType).endpointPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
PasteUpload::~PasteUpload()
|
PasteUpload::~PasteUpload()
|
||||||
@ -19,26 +69,73 @@ PasteUpload::~PasteUpload()
|
|||||||
void PasteUpload::executeTask()
|
void PasteUpload::executeTask()
|
||||||
{
|
{
|
||||||
QNetworkRequest request{QUrl(m_uploadUrl)};
|
QNetworkRequest request{QUrl(m_uploadUrl)};
|
||||||
|
QNetworkReply *rep{};
|
||||||
|
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
||||||
|
|
||||||
QHttpMultiPart *multiPart = new QHttpMultiPart{QHttpMultiPart::FormDataType};
|
switch (m_pasteType) {
|
||||||
|
case NullPointer: {
|
||||||
|
QHttpMultiPart *multiPart =
|
||||||
|
new QHttpMultiPart{QHttpMultiPart::FormDataType};
|
||||||
|
|
||||||
QHttpPart filePart;
|
QHttpPart filePart;
|
||||||
filePart.setBody(m_text);
|
filePart.setBody(m_text);
|
||||||
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
filePart.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
|
||||||
filePart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"file\"; filename=\"log.txt\"");
|
filePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
"form-data; name=\"file\"; filename=\"log.txt\"");
|
||||||
multiPart->append(filePart);
|
multiPart->append(filePart);
|
||||||
|
|
||||||
QNetworkReply *rep = APPLICATION->network()->post(request, multiPart);
|
rep = APPLICATION->network()->post(request, multiPart);
|
||||||
multiPart->setParent(rep);
|
multiPart->setParent(rep);
|
||||||
|
|
||||||
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
break;
|
||||||
setStatus(tr("Uploading to %1").arg(m_uploadUrl));
|
}
|
||||||
|
case Hastebin: {
|
||||||
|
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
||||||
|
rep = APPLICATION->network()->post(request, m_text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Mclogs: {
|
||||||
|
QUrlQuery postData;
|
||||||
|
postData.addQueryItem("content", m_text);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
|
rep = APPLICATION->network()->post(request, postData.toString().toUtf8());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PasteGG: {
|
||||||
|
QJsonObject obj;
|
||||||
|
QJsonDocument doc;
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
obj.insert("expires", QDateTime::currentDateTimeUtc().addDays(100).toString(Qt::DateFormat::ISODate));
|
||||||
|
|
||||||
|
QJsonArray files;
|
||||||
|
QJsonObject logFileInfo;
|
||||||
|
QJsonObject logFileContentInfo;
|
||||||
|
logFileContentInfo.insert("format", "text");
|
||||||
|
logFileContentInfo.insert("value", QString::fromUtf8(m_text));
|
||||||
|
logFileInfo.insert("name", "log.txt");
|
||||||
|
logFileInfo.insert("content", logFileContentInfo);
|
||||||
|
files.append(logFileInfo);
|
||||||
|
|
||||||
|
obj.insert("files", files);
|
||||||
|
|
||||||
|
doc.setObject(obj);
|
||||||
|
rep = APPLICATION->network()->post(request, doc.toJson());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, &QNetworkReply::finished, this, &PasteUpload::downloadFinished);
|
||||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
// This function call would be a lot shorter if we were using the latest Qt
|
||||||
|
connect(rep,
|
||||||
|
static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
|
||||||
|
this, &PasteUpload::downloadError);
|
||||||
|
|
||||||
|
m_reply = std::shared_ptr<QNetworkReply>(rep);
|
||||||
|
|
||||||
|
setStatus(tr("Uploading to %1").arg(m_uploadUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
||||||
@ -68,6 +165,82 @@ void PasteUpload::downloadFinished()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (m_pasteType)
|
||||||
|
{
|
||||||
|
case NullPointer:
|
||||||
m_pasteLink = QString::fromUtf8(data).trimmed();
|
m_pasteLink = QString::fromUtf8(data).trimmed();
|
||||||
|
break;
|
||||||
|
case Hastebin: {
|
||||||
|
QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
|
||||||
|
QJsonObject jsonObj{jsonDoc.object()};
|
||||||
|
if (jsonObj.contains("key") && jsonObj["key"].isString())
|
||||||
|
{
|
||||||
|
QString key = jsonDoc.object()["key"].toString();
|
||||||
|
m_pasteLink = m_baseUrl + "/" + key;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
|
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Mclogs: {
|
||||||
|
QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
|
||||||
|
QJsonObject jsonObj{jsonDoc.object()};
|
||||||
|
if (jsonObj.contains("success") && jsonObj["success"].isBool())
|
||||||
|
{
|
||||||
|
bool success = jsonObj["success"].toBool();
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
m_pasteLink = jsonObj["url"].toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QString error = jsonObj["error"].toString();
|
||||||
|
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
||||||
|
qCritical() << m_uploadUrl << " returned error: " << error;
|
||||||
|
qCritical() << "Response body: " << data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
|
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PasteGG:
|
||||||
|
QJsonDocument jsonDoc{QJsonDocument::fromJson(data)};
|
||||||
|
QJsonObject jsonObj{jsonDoc.object()};
|
||||||
|
if (jsonObj.contains("status") && jsonObj["status"].isString())
|
||||||
|
{
|
||||||
|
QString status = jsonObj["status"].toString();
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
m_pasteLink = m_baseUrl + "/p/anonymous/" + jsonObj["result"].toObject()["id"].toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QString error = jsonObj["error"].toString();
|
||||||
|
QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
||||||
|
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
||||||
|
qCritical() << m_uploadUrl << " returned error: " << error;
|
||||||
|
qCritical() << "Error message: " << message;
|
||||||
|
qCritical() << "Response body: " << data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
|
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,74 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.h"
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QString>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
class PasteUpload : public Task
|
class PasteUpload : public Task
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
PasteUpload(QWidget *window, QString text, QString url);
|
enum PasteType : int {
|
||||||
|
// 0x0.st
|
||||||
|
NullPointer,
|
||||||
|
// hastebin.com
|
||||||
|
Hastebin,
|
||||||
|
// paste.gg
|
||||||
|
PasteGG,
|
||||||
|
// mclo.gs
|
||||||
|
Mclogs,
|
||||||
|
// Helpful to get the range of valid values on the enum for input sanitisation:
|
||||||
|
First = NullPointer,
|
||||||
|
Last = Mclogs
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PasteTypeInfo {
|
||||||
|
const QString name;
|
||||||
|
const QString defaultBase;
|
||||||
|
const QString endpointPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::array<PasteTypeInfo, 4> PasteTypes;
|
||||||
|
|
||||||
|
PasteUpload(QWidget *window, QString text, QString url, PasteType pasteType);
|
||||||
virtual ~PasteUpload();
|
virtual ~PasteUpload();
|
||||||
|
|
||||||
QString pasteLink()
|
QString pasteLink()
|
||||||
@ -21,7 +81,9 @@ protected:
|
|||||||
private:
|
private:
|
||||||
QWidget *m_window;
|
QWidget *m_window;
|
||||||
QString m_pasteLink;
|
QString m_pasteLink;
|
||||||
|
QString m_baseUrl;
|
||||||
QString m_uploadUrl;
|
QString m_uploadUrl;
|
||||||
|
PasteType m_pasteType;
|
||||||
QByteArray m_text;
|
QByteArray m_text;
|
||||||
std::shared_ptr<QNetworkReply> m_reply;
|
std::shared_ptr<QNetworkReply> m_reply;
|
||||||
public
|
public
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
@ -5,33 +40,39 @@
|
|||||||
#include "Validator.h"
|
#include "Validator.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
class Sink
|
class Sink {
|
||||||
{
|
public:
|
||||||
public: /* con/des */
|
Sink() = default;
|
||||||
Sink() {};
|
virtual ~Sink() = default;
|
||||||
virtual ~Sink() {};
|
|
||||||
|
|
||||||
public: /* methods */
|
public:
|
||||||
virtual JobStatus init(QNetworkRequest & request) = 0;
|
virtual auto init(QNetworkRequest& request) -> Task::State = 0;
|
||||||
virtual JobStatus write(QByteArray & data) = 0;
|
virtual auto write(QByteArray& data) -> Task::State = 0;
|
||||||
virtual JobStatus abort() = 0;
|
virtual auto abort() -> Task::State = 0;
|
||||||
virtual JobStatus finalize(QNetworkReply & reply) = 0;
|
virtual auto finalize(QNetworkReply& reply) -> Task::State = 0;
|
||||||
virtual bool hasLocalData() = 0;
|
|
||||||
|
|
||||||
void addValidator(Validator * validator)
|
virtual auto hasLocalData() -> bool = 0;
|
||||||
{
|
|
||||||
if(validator)
|
void addValidator(Validator* validator)
|
||||||
{
|
{
|
||||||
|
if (validator) {
|
||||||
validators.push_back(std::shared_ptr<Validator>(validator));
|
validators.push_back(std::shared_ptr<Validator>(validator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected: /* methods */
|
protected:
|
||||||
bool finalizeAllValidators(QNetworkReply & reply)
|
bool initAllValidators(QNetworkRequest& request)
|
||||||
{
|
{
|
||||||
for(auto & validator: validators)
|
for (auto& validator : validators) {
|
||||||
|
if (!validator->init(request))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool finalizeAllValidators(QNetworkReply& reply)
|
||||||
{
|
{
|
||||||
if(!validator->validate(reply))
|
for (auto& validator : validators) {
|
||||||
|
if (!validator->validate(reply))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -39,32 +80,21 @@ protected: /* methods */
|
|||||||
bool failAllValidators()
|
bool failAllValidators()
|
||||||
{
|
{
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for(auto & validator: validators)
|
for (auto& validator : validators) {
|
||||||
{
|
|
||||||
success &= validator->abort();
|
success &= validator->abort();
|
||||||
}
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
bool initAllValidators(QNetworkRequest & request)
|
bool writeAllValidators(QByteArray& data)
|
||||||
{
|
{
|
||||||
for(auto & validator: validators)
|
for (auto& validator : validators) {
|
||||||
{
|
if (!validator->write(data))
|
||||||
if(!validator->init(request))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
bool writeAllValidators(QByteArray & data)
|
|
||||||
{
|
|
||||||
for(auto & validator: validators)
|
|
||||||
{
|
|
||||||
if(!validator->write(data))
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected: /* data */
|
protected:
|
||||||
std::vector<std::shared_ptr<Validator>> validators;
|
std::vector<std::shared_ptr<Validator>> validators;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Net
|
||||||
|
@ -1,3 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 849 B |
@ -6,8 +6,7 @@
|
|||||||
<!-- REDDIT logo icon, needs reddit license! -->
|
<!-- REDDIT logo icon, needs reddit license! -->
|
||||||
<file>scalable/reddit-alien.svg</file>
|
<file>scalable/reddit-alien.svg</file>
|
||||||
|
|
||||||
<!-- Icon for CurseForge. Unknown license? -->
|
<!-- Icon for CurseForge. CC0 -->
|
||||||
<file alias="32x32/flame.png">32x32/instances/flame.png</file>
|
|
||||||
<file alias="128x128/flame.png">128x128/instances/flame.png</file>
|
<file alias="128x128/flame.png">128x128/instances/flame.png</file>
|
||||||
|
|
||||||
<!-- launcher settings page -->
|
<!-- launcher settings page -->
|
||||||
@ -272,7 +271,6 @@
|
|||||||
<file>32x32/instances/ftb_logo.png</file>
|
<file>32x32/instances/ftb_logo.png</file>
|
||||||
<file>128x128/instances/ftb_logo.png</file>
|
<file>128x128/instances/ftb_logo.png</file>
|
||||||
|
|
||||||
<file>32x32/instances/flame.png</file>
|
|
||||||
<file>128x128/instances/flame.png</file>
|
<file>128x128/instances/flame.png</file>
|
||||||
|
|
||||||
<file>32x32/instances/gear.png</file>
|
<file>32x32/instances/gear.png</file>
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "ImgurAlbumCreation.h"
|
#include "ImgurAlbumCreation.h"
|
||||||
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
@ -13,12 +48,12 @@
|
|||||||
ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenShot::Ptr> screenshots) : NetAction(), m_screenshots(screenshots)
|
ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenShot::Ptr> screenshots) : NetAction(), m_screenshots(screenshots)
|
||||||
{
|
{
|
||||||
m_url = BuildConfig.IMGUR_BASE_URL + "album.json";
|
m_url = BuildConfig.IMGUR_BASE_URL + "album.json";
|
||||||
m_status = Job_NotStarted;
|
m_state = State::Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImgurAlbumCreation::startImpl()
|
void ImgurAlbumCreation::executeTask()
|
||||||
{
|
{
|
||||||
m_status = Job_InProgress;
|
m_state = State::Running;
|
||||||
QNetworkRequest request(m_url);
|
QNetworkRequest request(m_url);
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||||
@ -43,11 +78,11 @@ void ImgurAlbumCreation::startImpl()
|
|||||||
void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
|
void ImgurAlbumCreation::downloadError(QNetworkReply::NetworkError error)
|
||||||
{
|
{
|
||||||
qDebug() << m_reply->errorString();
|
qDebug() << m_reply->errorString();
|
||||||
m_status = Job_Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
void ImgurAlbumCreation::downloadFinished()
|
void ImgurAlbumCreation::downloadFinished()
|
||||||
{
|
{
|
||||||
if (m_status != Job_Failed)
|
if (m_state != State::Failed)
|
||||||
{
|
{
|
||||||
QByteArray data = m_reply->readAll();
|
QByteArray data = m_reply->readAll();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
@ -56,33 +91,32 @@ void ImgurAlbumCreation::downloadFinished()
|
|||||||
if (jsonError.error != QJsonParseError::NoError)
|
if (jsonError.error != QJsonParseError::NoError)
|
||||||
{
|
{
|
||||||
qDebug() << jsonError.errorString();
|
qDebug() << jsonError.errorString();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto object = doc.object();
|
auto object = doc.object();
|
||||||
if (!object.value("success").toBool())
|
if (!object.value("success").toBool())
|
||||||
{
|
{
|
||||||
qDebug() << doc.toJson();
|
qDebug() << doc.toJson();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_deleteHash = object.value("data").toObject().value("deletehash").toString();
|
m_deleteHash = object.value("data").toObject().value("deletehash").toString();
|
||||||
m_id = object.value("data").toObject().value("id").toString();
|
m_id = object.value("data").toObject().value("id").toString();
|
||||||
m_status = Job_Finished;
|
m_state = State::Succeeded;
|
||||||
emit succeeded(m_index_within_job);
|
emit succeeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qDebug() << m_reply->readAll();
|
qDebug() << m_reply->readAll();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void ImgurAlbumCreation::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
m_total_progress = bytesTotal;
|
setProgress(bytesReceived, bytesTotal);
|
||||||
m_progress = bytesReceived;
|
emit progress(bytesReceived, bytesTotal);
|
||||||
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,42 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
#include "Screenshot.h"
|
#include "Screenshot.h"
|
||||||
#include "QObjectPtr.h"
|
|
||||||
|
|
||||||
typedef shared_qobject_ptr<class ImgurAlbumCreation> ImgurAlbumCreationPtr;
|
typedef shared_qobject_ptr<class ImgurAlbumCreation> ImgurAlbumCreationPtr;
|
||||||
class ImgurAlbumCreation : public NetAction
|
class ImgurAlbumCreation : public NetAction
|
||||||
@ -24,16 +59,14 @@ public:
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
slots:
|
slots:
|
||||||
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||||
virtual void downloadError(QNetworkReply::NetworkError error);
|
void downloadError(QNetworkReply::NetworkError error) override;
|
||||||
virtual void downloadFinished();
|
void downloadFinished() override;
|
||||||
virtual void downloadReadyRead()
|
void downloadReadyRead() override {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
public
|
||||||
slots:
|
slots:
|
||||||
virtual void startImpl();
|
void executeTask() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<ScreenShot::Ptr> m_screenshots;
|
QList<ScreenShot::Ptr> m_screenshots;
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "ImgurUpload.h"
|
#include "ImgurUpload.h"
|
||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
@ -13,13 +48,13 @@
|
|||||||
ImgurUpload::ImgurUpload(ScreenShot::Ptr shot) : NetAction(), m_shot(shot)
|
ImgurUpload::ImgurUpload(ScreenShot::Ptr shot) : NetAction(), m_shot(shot)
|
||||||
{
|
{
|
||||||
m_url = BuildConfig.IMGUR_BASE_URL + "upload.json";
|
m_url = BuildConfig.IMGUR_BASE_URL + "upload.json";
|
||||||
m_status = Job_NotStarted;
|
m_state = State::Inactive;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImgurUpload::startImpl()
|
void ImgurUpload::executeTask()
|
||||||
{
|
{
|
||||||
finished = false;
|
finished = false;
|
||||||
m_status = Job_InProgress;
|
m_state = Task::State::Running;
|
||||||
QNetworkRequest request(m_url);
|
QNetworkRequest request(m_url);
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
|
||||||
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
|
request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
|
||||||
@ -28,7 +63,7 @@ void ImgurUpload::startImpl()
|
|||||||
QFile f(m_shot->m_file.absoluteFilePath());
|
QFile f(m_shot->m_file.absoluteFilePath());
|
||||||
if (!f.open(QFile::ReadOnly))
|
if (!f.open(QFile::ReadOnly))
|
||||||
{
|
{
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +98,10 @@ void ImgurUpload::downloadError(QNetworkReply::NetworkError error)
|
|||||||
qCritical() << "Double finished ImgurUpload!";
|
qCritical() << "Double finished ImgurUpload!";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_status = Job_Failed;
|
m_state = Task::State::Failed;
|
||||||
finished = true;
|
finished = true;
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
}
|
}
|
||||||
void ImgurUpload::downloadFinished()
|
void ImgurUpload::downloadFinished()
|
||||||
{
|
{
|
||||||
@ -84,7 +119,7 @@ void ImgurUpload::downloadFinished()
|
|||||||
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
|
qDebug() << "imgur server did not reply with JSON" << jsonError.errorString();
|
||||||
finished = true;
|
finished = true;
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto object = doc.object();
|
auto object = doc.object();
|
||||||
@ -93,20 +128,19 @@ void ImgurUpload::downloadFinished()
|
|||||||
qDebug() << "Screenshot upload not successful:" << doc.toJson();
|
qDebug() << "Screenshot upload not successful:" << doc.toJson();
|
||||||
finished = true;
|
finished = true;
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed(m_index_within_job);
|
emitFailed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
|
m_shot->m_imgurId = object.value("data").toObject().value("id").toString();
|
||||||
m_shot->m_url = object.value("data").toObject().value("link").toString();
|
m_shot->m_url = object.value("data").toObject().value("link").toString();
|
||||||
m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString();
|
m_shot->m_imgurDeleteHash = object.value("data").toObject().value("deletehash").toString();
|
||||||
m_status = Job_Finished;
|
m_state = Task::State::Succeeded;
|
||||||
finished = true;
|
finished = true;
|
||||||
emit succeeded(m_index_within_job);
|
emit succeeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void ImgurUpload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
m_total_progress = bytesTotal;
|
setProgress(bytesReceived, bytesTotal);
|
||||||
m_progress = bytesReceived;
|
emit progress(bytesReceived, bytesTotal);
|
||||||
emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "QObjectPtr.h"
|
|
||||||
#include "net/NetAction.h"
|
#include "net/NetAction.h"
|
||||||
#include "Screenshot.h"
|
#include "Screenshot.h"
|
||||||
|
|
||||||
@ -21,7 +56,7 @@ slots:
|
|||||||
|
|
||||||
public
|
public
|
||||||
slots:
|
slots:
|
||||||
void startImpl() override;
|
void executeTask() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScreenShot::Ptr m_shot;
|
ScreenShot::Ptr m_shot;
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -99,7 +119,7 @@ void Task::emitAborted()
|
|||||||
m_state = State::AbortedByUser;
|
m_state = State::AbortedByUser;
|
||||||
m_failReason = "Aborted.";
|
m_failReason = "Aborted.";
|
||||||
qDebug() << "Task" << describe() << "aborted.";
|
qDebug() << "Task" << describe() << "aborted.";
|
||||||
emit failed(m_failReason);
|
emit aborted();
|
||||||
emit finished();
|
emit finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
/* Copyright 2013-2021 MultiMC Contributors
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,10 +35,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
#include "QObjectPtr.h"
|
#include "QObjectPtr.h"
|
||||||
|
|
||||||
class Task : public QObject {
|
class Task : public QObject {
|
||||||
@ -52,6 +68,8 @@ class Task : public QObject {
|
|||||||
|
|
||||||
virtual bool canAbort() const { return false; }
|
virtual bool canAbort() const { return false; }
|
||||||
|
|
||||||
|
auto getState() const -> State { return m_state; }
|
||||||
|
|
||||||
QString getStatus() { return m_status; }
|
QString getStatus() { return m_status; }
|
||||||
virtual auto getStepStatus() const -> QString { return m_status; }
|
virtual auto getStepStatus() const -> QString { return m_status; }
|
||||||
|
|
||||||
@ -68,15 +86,16 @@ class Task : public QObject {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void started();
|
void started();
|
||||||
virtual void progress(qint64 current, qint64 total);
|
void progress(qint64 current, qint64 total);
|
||||||
void finished();
|
void finished();
|
||||||
void succeeded();
|
void succeeded();
|
||||||
|
void aborted();
|
||||||
void failed(QString reason);
|
void failed(QString reason);
|
||||||
void status(QString status);
|
void status(QString status);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
virtual void start();
|
virtual void start();
|
||||||
virtual bool abort() { return false; };
|
virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() = 0;
|
virtual void executeTask() = 0;
|
||||||
@ -84,13 +103,13 @@ class Task : public QObject {
|
|||||||
protected slots:
|
protected slots:
|
||||||
virtual void emitSucceeded();
|
virtual void emitSucceeded();
|
||||||
virtual void emitAborted();
|
virtual void emitAborted();
|
||||||
virtual void emitFailed(QString reason);
|
virtual void emitFailed(QString reason = "");
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStatus(const QString& status);
|
void setStatus(const QString& status);
|
||||||
void setProgress(qint64 current, qint64 total);
|
void setProgress(qint64 current, qint64 total);
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
State m_state = State::Inactive;
|
State m_state = State::Inactive;
|
||||||
QStringList m_Warnings;
|
QStringList m_Warnings;
|
||||||
QString m_failReason = "";
|
QString m_failReason = "";
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
#include "TranslationsModel.h"
|
#include "TranslationsModel.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@ -667,7 +702,7 @@ void TranslationsModel::downloadTranslation(QString key)
|
|||||||
auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
|
auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
|
||||||
auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1());
|
auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1());
|
||||||
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
|
||||||
dl->m_total_progress = lang->file_size;
|
dl->setProgress(dl->getProgress(), lang->file_size);
|
||||||
|
|
||||||
d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network());
|
d->m_dl_job = new NetJob("Translation for " + key, APPLICATION->network());
|
||||||
d->m_dl_job->addNetAction(dl);
|
d->m_dl_job->addNetAction(dl);
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
|
*
|
||||||
|
* 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 "GuiUtil.h"
|
#include "GuiUtil.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
@ -16,8 +51,9 @@
|
|||||||
QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
|
QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
|
||||||
{
|
{
|
||||||
ProgressDialog dialog(parentWidget);
|
ProgressDialog dialog(parentWidget);
|
||||||
auto pasteUrlSetting = APPLICATION->settings()->get("PastebinURL").toString();
|
auto pasteTypeSetting = static_cast<PasteUpload::PasteType>(APPLICATION->settings()->get("PastebinType").toInt());
|
||||||
std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteUrlSetting));
|
auto pasteCustomAPIBaseSetting = APPLICATION->settings()->get("PastebinCustomAPIBase").toString();
|
||||||
|
std::unique_ptr<PasteUpload> paste(new PasteUpload(parentWidget, text, pasteCustomAPIBaseSetting, pasteTypeSetting));
|
||||||
|
|
||||||
dialog.execWithTask(paste.get());
|
dialog.execWithTask(paste.get());
|
||||||
if (!paste->wasSuccessful())
|
if (!paste->wasSuccessful())
|
||||||
|
@ -1868,6 +1868,9 @@ void MainWindow::globalSettingsClosed()
|
|||||||
updateMainToolBar();
|
updateMainToolBar();
|
||||||
updateToolsMenu();
|
updateToolsMenu();
|
||||||
updateStatusCenter();
|
updateStatusCenter();
|
||||||
|
// This needs to be done to prevent UI elements disappearing in the event the config is changed
|
||||||
|
// but PolyMC exits abnormally, causing the window state to never be saved:
|
||||||
|
APPLICATION->settings()->set("MainWindowState", saveState().toBase64());
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
* Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -46,16 +47,43 @@
|
|||||||
#include "settings/SettingsObject.h"
|
#include "settings/SettingsObject.h"
|
||||||
#include "tools/BaseProfiler.h"
|
#include "tools/BaseProfiler.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "net/PasteUpload.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
APIPage::APIPage(QWidget *parent) :
|
APIPage::APIPage(QWidget *parent) :
|
||||||
QWidget(parent),
|
QWidget(parent),
|
||||||
ui(new Ui::APIPage)
|
ui(new Ui::APIPage)
|
||||||
{
|
{
|
||||||
|
// This is here so you can reorder the entries in the combobox without messing stuff up
|
||||||
|
int comboBoxEntries[] = {
|
||||||
|
PasteUpload::PasteType::Mclogs,
|
||||||
|
PasteUpload::PasteType::NullPointer,
|
||||||
|
PasteUpload::PasteType::PasteGG,
|
||||||
|
PasteUpload::PasteType::Hastebin
|
||||||
|
};
|
||||||
|
|
||||||
static QRegularExpression validUrlRegExp("https?://.+");
|
static QRegularExpression validUrlRegExp("https?://.+");
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
ui->urlChoices->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->urlChoices));
|
|
||||||
ui->tabWidget->tabBar()->hide();\
|
for (auto pasteType : comboBoxEntries) {
|
||||||
|
ui->pasteTypeComboBox->addItem(PasteUpload::PasteTypes.at(pasteType).name, pasteType);
|
||||||
|
}
|
||||||
|
|
||||||
|
void (QComboBox::*currentIndexChangedSignal)(int) (&QComboBox::currentIndexChanged);
|
||||||
|
connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLPlaceholder);
|
||||||
|
// This function needs to be called even when the ComboBox's index is still in its default state.
|
||||||
|
updateBaseURLPlaceholder(ui->pasteTypeComboBox->currentIndex());
|
||||||
|
ui->baseURLEntry->setValidator(new QRegularExpressionValidator(validUrlRegExp, ui->baseURLEntry));
|
||||||
|
ui->tabWidget->tabBar()->hide();
|
||||||
|
|
||||||
|
ui->metaURL->setPlaceholderText(BuildConfig.META_URL);
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
resetBaseURLNote();
|
||||||
|
connect(ui->pasteTypeComboBox, currentIndexChangedSignal, this, &APIPage::updateBaseURLNote);
|
||||||
|
connect(ui->baseURLEntry, &QLineEdit::textEdited, this, &APIPage::resetBaseURLNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
APIPage::~APIPage()
|
APIPage::~APIPage()
|
||||||
@ -63,13 +91,52 @@ APIPage::~APIPage()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIPage::resetBaseURLNote()
|
||||||
|
{
|
||||||
|
ui->baseURLNote->hide();
|
||||||
|
baseURLPasteType = ui->pasteTypeComboBox->currentIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIPage::updateBaseURLNote(int index)
|
||||||
|
{
|
||||||
|
if (baseURLPasteType == index)
|
||||||
|
{
|
||||||
|
ui->baseURLNote->hide();
|
||||||
|
}
|
||||||
|
else if (!ui->baseURLEntry->text().isEmpty())
|
||||||
|
{
|
||||||
|
ui->baseURLNote->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIPage::updateBaseURLPlaceholder(int index)
|
||||||
|
{
|
||||||
|
int pasteType = ui->pasteTypeComboBox->itemData(index).toInt();
|
||||||
|
QString pasteDefaultURL = PasteUpload::PasteTypes.at(pasteType).defaultBase;
|
||||||
|
ui->baseURLEntry->setPlaceholderText(pasteDefaultURL);
|
||||||
|
}
|
||||||
|
|
||||||
void APIPage::loadSettings()
|
void APIPage::loadSettings()
|
||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
QString pastebinURL = s->get("PastebinURL").toString();
|
|
||||||
ui->urlChoices->setCurrentText(pastebinURL);
|
int pasteType = s->get("PastebinType").toInt();
|
||||||
|
QString pastebinURL = s->get("PastebinCustomAPIBase").toString();
|
||||||
|
|
||||||
|
ui->baseURLEntry->setText(pastebinURL);
|
||||||
|
int pasteTypeIndex = ui->pasteTypeComboBox->findData(pasteType);
|
||||||
|
if (pasteTypeIndex == -1)
|
||||||
|
{
|
||||||
|
pasteTypeIndex = ui->pasteTypeComboBox->findData(PasteUpload::PasteType::Mclogs);
|
||||||
|
ui->baseURLEntry->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->pasteTypeComboBox->setCurrentIndex(pasteTypeIndex);
|
||||||
|
|
||||||
QString msaClientID = s->get("MSAClientIDOverride").toString();
|
QString msaClientID = s->get("MSAClientIDOverride").toString();
|
||||||
ui->msaClientID->setText(msaClientID);
|
ui->msaClientID->setText(msaClientID);
|
||||||
|
QString metaURL = s->get("MetaURLOverride").toString();
|
||||||
|
ui->metaURL->setText(metaURL);
|
||||||
QString curseKey = s->get("CFKeyOverride").toString();
|
QString curseKey = s->get("CFKeyOverride").toString();
|
||||||
ui->curseKey->setText(curseKey);
|
ui->curseKey->setText(curseKey);
|
||||||
}
|
}
|
||||||
@ -77,10 +144,27 @@ void APIPage::loadSettings()
|
|||||||
void APIPage::applySettings()
|
void APIPage::applySettings()
|
||||||
{
|
{
|
||||||
auto s = APPLICATION->settings();
|
auto s = APPLICATION->settings();
|
||||||
QString pastebinURL = ui->urlChoices->currentText();
|
|
||||||
s->set("PastebinURL", pastebinURL);
|
s->set("PastebinType", ui->pasteTypeComboBox->currentData().toInt());
|
||||||
|
s->set("PastebinCustomAPIBase", ui->baseURLEntry->text());
|
||||||
|
|
||||||
QString msaClientID = ui->msaClientID->text();
|
QString msaClientID = ui->msaClientID->text();
|
||||||
s->set("MSAClientIDOverride", msaClientID);
|
s->set("MSAClientIDOverride", msaClientID);
|
||||||
|
QUrl metaURL = ui->metaURL->text();
|
||||||
|
// Add required trailing slash
|
||||||
|
if (!metaURL.isEmpty() && !metaURL.path().endsWith('/'))
|
||||||
|
{
|
||||||
|
QString path = metaURL.path();
|
||||||
|
path.append('/');
|
||||||
|
metaURL.setPath(path);
|
||||||
|
}
|
||||||
|
// Don't allow HTTP, since meta is basically RCE with all the jar files.
|
||||||
|
if(!metaURL.isEmpty() && metaURL.scheme() == "http")
|
||||||
|
{
|
||||||
|
metaURL.setScheme("https");
|
||||||
|
}
|
||||||
|
|
||||||
|
s->set("MetaURLOverride", metaURL);
|
||||||
QString curseKey = ui->curseKey->text();
|
QString curseKey = ui->curseKey->text();
|
||||||
s->set("CFKeyOverride", curseKey);
|
s->set("CFKeyOverride", curseKey);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
|
* Copyright (c) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -73,6 +74,10 @@ public:
|
|||||||
void retranslate() override;
|
void retranslate() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int baseURLPasteType;
|
||||||
|
void resetBaseURLNote();
|
||||||
|
void updateBaseURLNote(int index);
|
||||||
|
void updateBaseURLPlaceholder(int index);
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void applySettings();
|
void applySettings();
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>603</width>
|
<width>800</width>
|
||||||
<height>530</height>
|
<height>600</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
@ -36,60 +36,44 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_paste">
|
<widget class="QGroupBox" name="groupBox_paste">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Pastebin URL</string>
|
<string>Pastebin Service</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line">
|
<widget class="QLabel" name="pasteServiceTypeLabel">
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>10</pointsize>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string><html><head/><body><p>Note: only input that starts with <span style=" font-weight:600;">http://</span> or <span style=" font-weight:600;">https://</span> will be accepted.</p></body></html></string>
|
<string>Paste Service Type</string>
|
||||||
</property>
|
|
||||||
<property name="scaledContents">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="urlChoices">
|
<widget class="QComboBox" name="pasteTypeComboBox"/>
|
||||||
<property name="editable">
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="baseURLLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Base URL</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="baseURLEntry">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="clearButtonEnabled">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="insertPolicy">
|
|
||||||
<enum>QComboBox::NoInsert</enum>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">https://0x0.st</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="baseURLNote">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string><html><head/><body><p>Here you can choose from a predefined list of paste services, or input the URL of a different paste service of your choice, provided it supports the same protocol as 0x0.st, that is POST a file parameter to the URL and return a link in the response body.</p></body></html></string>
|
<string>Note: you probably want to change or clear the Base URL after changing the paste service type.</string>
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::RichText</enum>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="wordWrap">
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="openExternalLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@ -101,13 +85,6 @@
|
|||||||
<string>&Microsoft Authentication</string>
|
<string>&Microsoft Authentication</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
<item>
|
|
||||||
<widget class="Line" name="line_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_3">
|
<widget class="QLabel" name="label_3">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -147,6 +124,51 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_meta">
|
||||||
|
<property name="title">
|
||||||
|
<string>Meta&data Server</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>You can set this to a third-party metadata server to use patched libraries or other hacks.</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="metaURL">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enter a custom URL for meta here.</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_curse">
|
<widget class="QGroupBox" name="groupBox_curse">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -155,16 +177,9 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&CurseForge Core API</string>
|
<string>&CurseForge Core API</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_3">
|
<widget class="QLabel" name="label_8">
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_6">
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Note: you probably don't need to set this if CurseForge already works.</string>
|
<string>Note: you probably don't need to set this if CurseForge already works.</string>
|
||||||
</property>
|
</property>
|
||||||
@ -181,7 +196,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Enter a custom API Key for CurseForge here. </string>
|
<string>Enter a custom API Key for CurseForge here. </string>
|
||||||
</property>
|
</property>
|
||||||
@ -199,6 +214,19 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -391,7 +391,7 @@ void ModFolderPage::on_actionInstall_mods_triggered()
|
|||||||
return; //this is a null instance or a legacy instance
|
return; //this is a null instance or a legacy instance
|
||||||
}
|
}
|
||||||
auto profile = ((MinecraftInstance *)m_inst)->getPackProfile();
|
auto profile = ((MinecraftInstance *)m_inst)->getPackProfile();
|
||||||
if (profile->getModLoader() == ModAPI::Unspecified) {
|
if (profile->getModLoaders() == ModAPI::Unspecified) {
|
||||||
QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!"));
|
QMessageBox::critical(this,tr("Error"),tr("Please install a mod loader first!"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current)
|
|||||||
{
|
{
|
||||||
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
|
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
|
||||||
|
|
||||||
m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoader() });
|
m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoaders() });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListModel::performPaginatedSearch()
|
void ListModel::performPaginatedSearch()
|
||||||
@ -76,7 +76,7 @@ void ListModel::performPaginatedSearch()
|
|||||||
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
|
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
|
||||||
|
|
||||||
m_parent->apiProvider()->searchMods(
|
m_parent->apiProvider()->searchMods(
|
||||||
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() });
|
this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() });
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListModel::refresh()
|
void ListModel::refresh()
|
||||||
|
@ -175,7 +175,7 @@ void ModPage::updateModVersions(int prev_count)
|
|||||||
bool valid = false;
|
bool valid = false;
|
||||||
for(auto& mcVer : m_filter->versions){
|
for(auto& mcVer : m_filter->versions){
|
||||||
//NOTE: Flame doesn't care about loader, so passing it changes nothing.
|
//NOTE: Flame doesn't care about loader, so passing it changes nothing.
|
||||||
if (validateVersion(version, mcVer.toString(), packProfile->getModLoader())) {
|
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
|
||||||
valid = true;
|
valid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class ModPage : public QWidget, public BasePage {
|
|||||||
void retranslate() override;
|
void retranslate() override;
|
||||||
|
|
||||||
auto shouldDisplay() const -> bool override = 0;
|
auto shouldDisplay() const -> bool override = 0;
|
||||||
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool = 0;
|
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0;
|
||||||
|
|
||||||
auto apiProvider() const -> const ModAPI* { return api.get(); };
|
auto apiProvider() const -> const ModAPI* { return api.get(); };
|
||||||
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
|
||||||
|
@ -43,8 +43,11 @@
|
|||||||
#include "modplatform/atlauncher/ATLShareCode.h"
|
#include "modplatform/atlauncher/ATLShareCode.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
|
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||||
: QAbstractListModel(parent), m_mods(mods) {
|
: QAbstractListModel(parent)
|
||||||
|
, m_version(version)
|
||||||
|
, m_mods(mods)
|
||||||
|
{
|
||||||
// fill mod index
|
// fill mod index
|
||||||
for (int i = 0; i < m_mods.size(); i++) {
|
for (int i = 0; i < m_mods.size(); i++) {
|
||||||
auto mod = m_mods.at(i);
|
auto mod = m_mods.at(i);
|
||||||
@ -97,6 +100,11 @@ QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const
|
|||||||
return mod.description;
|
return mod.description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (role == Qt::ForegroundRole) {
|
||||||
|
if (!mod.colour.isEmpty() && m_version.colours.contains(mod.colour)) {
|
||||||
|
return QColor(QString("#%1").arg(m_version.colours[mod.colour]));
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (role == Qt::CheckStateRole) {
|
else if (role == Qt::CheckStateRole) {
|
||||||
if (index.column() == EnabledColumn) {
|
if (index.column() == EnabledColumn) {
|
||||||
return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked;
|
return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked;
|
||||||
@ -223,7 +231,21 @@ void AtlOptionalModListModel::clearAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) {
|
void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) {
|
||||||
setMod(mod, index, !m_selection[mod.name]);
|
auto enable = !m_selection[mod.name];
|
||||||
|
|
||||||
|
// If there is a warning for the mod, display that first (if we would be enabling the mod)
|
||||||
|
if (enable && !mod.warning.isEmpty() && m_version.warnings.contains(mod.warning)) {
|
||||||
|
auto message = QString("%1<br><br>%2")
|
||||||
|
.arg(m_version.warnings[mod.warning], tr("Are you sure that you want to enable this mod?"));
|
||||||
|
|
||||||
|
// fixme: avoid casting here
|
||||||
|
auto result = QMessageBox::warning((QWidget*) this->parent(), tr("Warning"), message, QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (result != QMessageBox::Yes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMod(mod, index, enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) {
|
void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) {
|
||||||
@ -287,12 +309,13 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||||
AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
|
: QDialog(parent)
|
||||||
: QDialog(parent), ui(new Ui::AtlOptionalModDialog) {
|
, ui(new Ui::AtlOptionalModDialog)
|
||||||
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
listModel = new AtlOptionalModListModel(this, mods);
|
listModel = new AtlOptionalModListModel(this, version, mods);
|
||||||
ui->treeView->setModel(listModel);
|
ui->treeView->setModel(listModel);
|
||||||
|
|
||||||
ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
|
@ -56,7 +56,7 @@ public:
|
|||||||
DescriptionColumn,
|
DescriptionColumn,
|
||||||
};
|
};
|
||||||
|
|
||||||
AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
|
AtlOptionalModListModel(QWidget *parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
|
||||||
|
|
||||||
QVector<QString> getResult();
|
QVector<QString> getResult();
|
||||||
|
|
||||||
@ -86,7 +86,9 @@ private:
|
|||||||
NetJob::Ptr m_jobPtr;
|
NetJob::Ptr m_jobPtr;
|
||||||
QByteArray m_response;
|
QByteArray m_response;
|
||||||
|
|
||||||
|
ATLauncher::PackVersion m_version;
|
||||||
QVector<ATLauncher::VersionMod> m_mods;
|
QVector<ATLauncher::VersionMod> m_mods;
|
||||||
|
|
||||||
QMap<QString, bool> m_selection;
|
QMap<QString, bool> m_selection;
|
||||||
QMap<QString, int> m_index;
|
QMap<QString, int> m_index;
|
||||||
QMap<QString, QVector<QString>> m_dependants;
|
QMap<QString, QVector<QString>> m_dependants;
|
||||||
@ -96,7 +98,7 @@ class AtlOptionalModDialog : public QDialog {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
|
AtlOptionalModDialog(QWidget *parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
|
||||||
~AtlOptionalModDialog() override;
|
~AtlOptionalModDialog() override;
|
||||||
|
|
||||||
QVector<QString> getResult() {
|
QVector<QString> getResult() {
|
||||||
|
@ -45,8 +45,12 @@
|
|||||||
|
|
||||||
#include <BuildConfig.h>
|
#include <BuildConfig.h>
|
||||||
|
|
||||||
AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent)
|
#include <QMessageBox>
|
||||||
: QWidget(parent), ui(new Ui::AtlPage), dialog(dialog)
|
|
||||||
|
AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::AtlPage)
|
||||||
|
, dialog(dialog)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
@ -169,8 +173,9 @@ void AtlPage::onVersionSelectionChanged(QString data)
|
|||||||
suggestCurrent();
|
suggestCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<QString> AtlPage::chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) {
|
QVector<QString> AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||||
AtlOptionalModDialog optionalModDialog(this, mods);
|
{
|
||||||
|
AtlOptionalModDialog optionalModDialog(this, version, mods);
|
||||||
optionalModDialog.exec();
|
optionalModDialog.exec();
|
||||||
return optionalModDialog.getResult();
|
return optionalModDialog.getResult();
|
||||||
}
|
}
|
||||||
@ -210,3 +215,8 @@ QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVers
|
|||||||
vselect.exec();
|
vselect.exec();
|
||||||
return vselect.selectedVersion()->descriptor();
|
return vselect.selectedVersion()->descriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AtlPage::displayMessage(QString message)
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("Installing"), message);
|
||||||
|
}
|
||||||
|
@ -84,7 +84,8 @@ private:
|
|||||||
void suggestCurrent();
|
void suggestCurrent();
|
||||||
|
|
||||||
QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
|
QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
|
||||||
QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) override;
|
QVector<QString> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
|
||||||
|
void displayMessage(QString message) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void triggerSearch();
|
void triggerSearch();
|
||||||
|
@ -61,9 +61,9 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
|
|||||||
connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected);
|
connect(ui->modSelectionButton, &QPushButton::clicked, this, &FlameModPage::onModSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
|
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
|
||||||
{
|
{
|
||||||
Q_UNUSED(loader);
|
Q_UNUSED(loaders);
|
||||||
return ver.mcVersion.contains(mineVer);
|
return ver.mcVersion.contains(mineVer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class FlameModPage : public ModPage {
|
|||||||
inline auto debugName() const -> QString override { return "Flame"; }
|
inline auto debugName() const -> QString override { return "Flame"; }
|
||||||
inline auto metaEntryBase() const -> QString override { return "FlameMods"; };
|
inline auto metaEntryBase() const -> QString override { return "FlameMods"; };
|
||||||
|
|
||||||
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
|
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
|
||||||
|
|
||||||
auto shouldDisplay() const -> bool override;
|
auto shouldDisplay() const -> bool override;
|
||||||
};
|
};
|
||||||
|
@ -61,9 +61,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan
|
|||||||
connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
|
connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool
|
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders) const -> bool
|
||||||
{
|
{
|
||||||
auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader);
|
auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders);
|
||||||
|
|
||||||
auto loaderCompatible = false;
|
auto loaderCompatible = false;
|
||||||
for (auto remoteLoader : ver.loaders)
|
for (auto remoteLoader : ver.loaders)
|
||||||
|
@ -55,7 +55,7 @@ class ModrinthModPage : public ModPage {
|
|||||||
inline auto debugName() const -> QString override { return "Modrinth"; }
|
inline auto debugName() const -> QString override { return "Modrinth"; }
|
||||||
inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
|
inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; };
|
||||||
|
|
||||||
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override;
|
auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override;
|
||||||
|
|
||||||
auto shouldDisplay() const -> bool override;
|
auto shouldDisplay() const -> bool override;
|
||||||
};
|
};
|
||||||
|
42
launcher/ui/setupwizard/PasteWizardPage.cpp
Normal file
42
launcher/ui/setupwizard/PasteWizardPage.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#include "PasteWizardPage.h"
|
||||||
|
#include "ui_PasteWizardPage.h"
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "net/PasteUpload.h"
|
||||||
|
|
||||||
|
PasteWizardPage::PasteWizardPage(QWidget *parent) :
|
||||||
|
BaseWizardPage(parent),
|
||||||
|
ui(new Ui::PasteWizardPage)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PasteWizardPage::~PasteWizardPage()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasteWizardPage::initializePage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PasteWizardPage::validatePage()
|
||||||
|
{
|
||||||
|
auto s = APPLICATION->settings();
|
||||||
|
QString prevPasteURL = s->get("PastebinURL").toString();
|
||||||
|
s->reset("PastebinURL");
|
||||||
|
if (ui->previousSettingsRadioButton->isChecked())
|
||||||
|
{
|
||||||
|
bool usingDefaultBase = prevPasteURL == PasteUpload::PasteTypes.at(PasteUpload::PasteType::NullPointer).defaultBase;
|
||||||
|
s->set("PastebinType", PasteUpload::PasteType::NullPointer);
|
||||||
|
if (!usingDefaultBase)
|
||||||
|
s->set("PastebinCustomAPIBase", prevPasteURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PasteWizardPage::retranslate()
|
||||||
|
{
|
||||||
|
ui->retranslateUi(this);
|
||||||
|
}
|
27
launcher/ui/setupwizard/PasteWizardPage.h
Normal file
27
launcher/ui/setupwizard/PasteWizardPage.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef PASTEDEFAULTSCONFIRMATIONWIZARD_H
|
||||||
|
#define PASTEDEFAULTSCONFIRMATIONWIZARD_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include "BaseWizardPage.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PasteWizardPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasteWizardPage : public BaseWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PasteWizardPage(QWidget *parent = nullptr);
|
||||||
|
~PasteWizardPage();
|
||||||
|
|
||||||
|
void initializePage() override;
|
||||||
|
bool validatePage() override;
|
||||||
|
void retranslate() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::PasteWizardPage *ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PASTEDEFAULTSCONFIRMATIONWIZARD_H
|
80
launcher/ui/setupwizard/PasteWizardPage.ui
Normal file
80
launcher/ui/setupwizard/PasteWizardPage.ui
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PasteWizardPage</class>
|
||||||
|
<widget class="QWidget" name="PasteWizardPage">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>The default paste service has changed to mclo.gs, please choose what you want to do with your settings.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="defaultSettingsRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use new default service</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">buttonGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QRadioButton" name="previousSettingsRadioButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Keep previous settings</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="buttonGroup">
|
||||||
|
<string notr="true">buttonGroup</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>156</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
<buttongroups>
|
||||||
|
<buttongroup name="buttonGroup"/>
|
||||||
|
</buttongroups>
|
||||||
|
</ui>
|
@ -4,17 +4,18 @@ find_package(Java 1.7 REQUIRED COMPONENTS Development)
|
|||||||
|
|
||||||
include(UseJava)
|
include(UseJava)
|
||||||
set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
|
set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
|
||||||
set(CMAKE_JAVA_COMPILE_FLAGS -target 8 -source 8 -Xlint:deprecation -Xlint:unchecked)
|
set(CMAKE_JAVA_COMPILE_FLAGS -target 7 -source 7 -Xlint:deprecation -Xlint:unchecked)
|
||||||
|
|
||||||
set(SRC
|
set(SRC
|
||||||
org/multimc/EntryPoint.java
|
org/multimc/EntryPoint.java
|
||||||
org/multimc/Launcher.java
|
org/multimc/Launcher.java
|
||||||
org/multimc/LegacyFrame.java
|
org/multimc/LauncherFactory.java
|
||||||
org/multimc/NotFoundException.java
|
org/multimc/impl/OneSixLauncher.java
|
||||||
org/multimc/ParamBucket.java
|
org/multimc/applet/LegacyFrame.java
|
||||||
org/multimc/ParseException.java
|
org/multimc/exception/ParameterNotFoundException.java
|
||||||
org/multimc/Utils.java
|
org/multimc/exception/ParseException.java
|
||||||
org/multimc/onesix/OneSixLauncher.java
|
org/multimc/utils/Parameters.java
|
||||||
|
org/multimc/utils/Utils.java
|
||||||
net/minecraft/Launcher.java
|
net/minecraft/Launcher.java
|
||||||
)
|
)
|
||||||
add_jar(NewLaunch ${SRC})
|
add_jar(NewLaunch ${SRC})
|
||||||
|
@ -16,31 +16,28 @@
|
|||||||
|
|
||||||
package net.minecraft;
|
package net.minecraft;
|
||||||
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.applet.Applet;
|
import java.applet.Applet;
|
||||||
import java.applet.AppletStub;
|
import java.applet.AppletStub;
|
||||||
|
import java.awt.*;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
public final class Launcher extends Applet implements AppletStub {
|
||||||
|
|
||||||
|
private final Map<String, String> params = new TreeMap<>();
|
||||||
|
|
||||||
|
private final Applet wrappedApplet;
|
||||||
|
|
||||||
public class Launcher extends Applet implements AppletStub
|
|
||||||
{
|
|
||||||
private Applet wrappedApplet;
|
|
||||||
private URL documentBase;
|
|
||||||
private boolean active = false;
|
private boolean active = false;
|
||||||
private final Map<String, String> params;
|
|
||||||
|
|
||||||
public Launcher(Applet applet, URL documentBase)
|
|
||||||
{
|
|
||||||
params = new TreeMap<String, String>();
|
|
||||||
|
|
||||||
|
public Launcher(Applet applet) {
|
||||||
this.setLayout(new BorderLayout());
|
this.setLayout(new BorderLayout());
|
||||||
|
|
||||||
this.add(applet, "Center");
|
this.add(applet, "Center");
|
||||||
|
|
||||||
this.wrappedApplet = applet;
|
this.wrappedApplet = applet;
|
||||||
this.documentBase = documentBase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParameter(String name, String value)
|
public void setParameter(String name, String value)
|
||||||
@ -48,84 +45,61 @@ public class Launcher extends Applet implements AppletStub
|
|||||||
params.put(name, value);
|
params.put(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void replace(Applet applet)
|
|
||||||
{
|
|
||||||
this.wrappedApplet = applet;
|
|
||||||
|
|
||||||
applet.setStub(this);
|
|
||||||
applet.setSize(getWidth(), getHeight());
|
|
||||||
|
|
||||||
this.setLayout(new BorderLayout());
|
|
||||||
this.add(applet, "Center");
|
|
||||||
|
|
||||||
applet.init();
|
|
||||||
active = true;
|
|
||||||
applet.start();
|
|
||||||
validate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getParameter(String name)
|
public String getParameter(String name) {
|
||||||
{
|
|
||||||
String param = params.get(name);
|
String param = params.get(name);
|
||||||
|
|
||||||
if (param != null)
|
if (param != null)
|
||||||
return param;
|
return param;
|
||||||
try
|
|
||||||
{
|
try {
|
||||||
return super.getParameter(name);
|
return super.getParameter(name);
|
||||||
} catch (Exception ignore){}
|
} catch (Exception ignore) {}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive()
|
public boolean isActive() {
|
||||||
{
|
|
||||||
return active;
|
return active;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void appletResize(int width, int height)
|
public void appletResize(int width, int height) {
|
||||||
{
|
|
||||||
wrappedApplet.resize(width, height);
|
wrappedApplet.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resize(int width, int height)
|
public void resize(int width, int height) {
|
||||||
{
|
|
||||||
wrappedApplet.resize(width, height);
|
wrappedApplet.resize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resize(Dimension d)
|
public void resize(Dimension d) {
|
||||||
{
|
|
||||||
wrappedApplet.resize(d);
|
wrappedApplet.resize(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init()
|
public void init() {
|
||||||
{
|
|
||||||
if (wrappedApplet != null)
|
if (wrappedApplet != null)
|
||||||
{
|
|
||||||
wrappedApplet.init();
|
wrappedApplet.init();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start()
|
public void start() {
|
||||||
{
|
|
||||||
wrappedApplet.start();
|
wrappedApplet.start();
|
||||||
|
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop()
|
public void stop() {
|
||||||
{
|
|
||||||
wrappedApplet.stop();
|
wrappedApplet.stop();
|
||||||
|
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void destroy()
|
public void destroy() {
|
||||||
{
|
|
||||||
wrappedApplet.destroy();
|
wrappedApplet.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,34 +110,34 @@ public class Launcher extends Applet implements AppletStub
|
|||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getDocumentBase()
|
public URL getDocumentBase() {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
// Special case only for Classic versions
|
// Special case only for Classic versions
|
||||||
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) {
|
if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang"))
|
||||||
return new URL("http", "www.minecraft.net", 80, "/game/", null);
|
return new URL("http", "www.minecraft.net", 80, "/game/");
|
||||||
}
|
|
||||||
return new URL("http://www.minecraft.net/game/");
|
return new URL("http://www.minecraft.net/game/");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setVisible(boolean b)
|
public void setVisible(boolean b) {
|
||||||
{
|
|
||||||
super.setVisible(b);
|
super.setVisible(b);
|
||||||
|
|
||||||
wrappedApplet.setVisible(b);
|
wrappedApplet.setVisible(b);
|
||||||
}
|
}
|
||||||
public void update(Graphics paramGraphics)
|
|
||||||
{
|
public void update(Graphics paramGraphics) {}
|
||||||
}
|
|
||||||
public void paint(Graphics paramGraphics)
|
public void paint(Graphics paramGraphics) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,24 @@
|
|||||||
package org.multimc;/*
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
* Copyright 2012-2021 MultiMC Contributors
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -14,7 +33,10 @@ package org.multimc;/*
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.multimc.onesix.OneSixLauncher;
|
package org.multimc;
|
||||||
|
|
||||||
|
import org.multimc.exception.ParseException;
|
||||||
|
import org.multimc.utils.Parameters;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -23,31 +45,25 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class EntryPoint
|
public final class EntryPoint {
|
||||||
{
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger("EntryPoint");
|
private static final Logger LOGGER = Logger.getLogger("EntryPoint");
|
||||||
|
|
||||||
private final ParamBucket params = new ParamBucket();
|
private final Parameters params = new Parameters();
|
||||||
|
|
||||||
private org.multimc.Launcher launcher;
|
public static void main(String[] args) {
|
||||||
|
|
||||||
public static void main(String[] args)
|
|
||||||
{
|
|
||||||
EntryPoint listener = new EntryPoint();
|
EntryPoint listener = new EntryPoint();
|
||||||
|
|
||||||
int retCode = listener.listen();
|
int retCode = listener.listen();
|
||||||
|
|
||||||
if (retCode != 0)
|
if (retCode != 0) {
|
||||||
{
|
|
||||||
LOGGER.info("Exiting with " + retCode);
|
LOGGER.info("Exiting with " + retCode);
|
||||||
|
|
||||||
System.exit(retCode);
|
System.exit(retCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Action parseLine(String inData) throws ParseException
|
private Action parseLine(String inData) throws ParseException {
|
||||||
{
|
|
||||||
String[] tokens = inData.split("\\s+", 2);
|
String[] tokens = inData.split("\\s+", 2);
|
||||||
|
|
||||||
if (tokens.length == 0)
|
if (tokens.length == 0)
|
||||||
@ -62,21 +78,6 @@ public class EntryPoint
|
|||||||
return Action.Abort;
|
return Action.Abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "launcher": {
|
|
||||||
if (tokens.length != 2)
|
|
||||||
throw new ParseException("Expected 2 tokens, got " + tokens.length);
|
|
||||||
|
|
||||||
if (tokens[1].equals("onesix")) {
|
|
||||||
launcher = new OneSixLauncher();
|
|
||||||
|
|
||||||
LOGGER.info("Using onesix launcher.");
|
|
||||||
|
|
||||||
return Action.Proceed;
|
|
||||||
} else {
|
|
||||||
throw new ParseException("Invalid launcher type: " + tokens[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
if (tokens.length != 2)
|
if (tokens.length != 2)
|
||||||
throw new ParseException("Error while parsing:" + inData);
|
throw new ParseException("Error while parsing:" + inData);
|
||||||
@ -88,8 +89,7 @@ public class EntryPoint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int listen()
|
public int listen() {
|
||||||
{
|
|
||||||
Action action = Action.Proceed;
|
Action action = Action.Proceed;
|
||||||
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||||
@ -112,21 +112,30 @@ public class EntryPoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
if (action == Action.Abort)
|
if (action == Action.Abort) {
|
||||||
{
|
|
||||||
LOGGER.info("Launch aborted by the launcher.");
|
LOGGER.info("Launch aborted by the launcher.");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (launcher != null)
|
try {
|
||||||
{
|
Launcher launcher =
|
||||||
return launcher.launch(params);
|
LauncherFactory
|
||||||
}
|
.getInstance()
|
||||||
|
.createLauncher(params);
|
||||||
|
|
||||||
LOGGER.log(Level.SEVERE, "No valid launcher implementation specified.");
|
launcher.launch();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Wrong argument.", e);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Exception caught from launcher.", e);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Action {
|
private enum Action {
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
package org.multimc;
|
package org.multimc;
|
||||||
|
|
||||||
public interface Launcher
|
public interface Launcher {
|
||||||
{
|
|
||||||
int launch(ParamBucket params);
|
void launch() throws Exception;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
63
libraries/launcher/org/multimc/LauncherFactory.java
Normal file
63
libraries/launcher/org/multimc/LauncherFactory.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* PolyMC - Minecraft Launcher
|
||||||
|
* Copyright (C) 2022 icelimetea, <fr3shtea@outlook.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.multimc;
|
||||||
|
|
||||||
|
import org.multimc.impl.OneSixLauncher;
|
||||||
|
import org.multimc.utils.Parameters;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class LauncherFactory {
|
||||||
|
|
||||||
|
private static final LauncherFactory INSTANCE = new LauncherFactory();
|
||||||
|
|
||||||
|
private final Map<String, LauncherProvider> launcherRegistry = new HashMap<>();
|
||||||
|
|
||||||
|
private LauncherFactory() {
|
||||||
|
launcherRegistry.put("onesix", new LauncherProvider() {
|
||||||
|
@Override
|
||||||
|
public Launcher provide(Parameters parameters) {
|
||||||
|
return new OneSixLauncher(parameters);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Launcher createLauncher(Parameters parameters) {
|
||||||
|
String name = parameters.first("launcher");
|
||||||
|
|
||||||
|
LauncherProvider launcherProvider = launcherRegistry.get(name);
|
||||||
|
|
||||||
|
if (launcherProvider == null)
|
||||||
|
throw new IllegalArgumentException("Invalid launcher type: " + name);
|
||||||
|
|
||||||
|
return launcherProvider.provide(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LauncherFactory getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LauncherProvider {
|
||||||
|
|
||||||
|
Launcher provide(Parameters parameters);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,176 +0,0 @@
|
|||||||
package org.multimc;/*
|
|
||||||
* Copyright 2012-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import net.minecraft.Launcher;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.applet.Applet;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import java.awt.event.WindowListener;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
public class LegacyFrame extends Frame implements WindowListener
|
|
||||||
{
|
|
||||||
private Launcher appletWrap = null;
|
|
||||||
public LegacyFrame(String title)
|
|
||||||
{
|
|
||||||
super ( title );
|
|
||||||
BufferedImage image;
|
|
||||||
try {
|
|
||||||
image = ImageIO.read ( new File ( "icon.png" ) );
|
|
||||||
setIconImage ( image );
|
|
||||||
} catch ( IOException e ) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
this.addWindowListener ( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start (
|
|
||||||
Applet mcApplet,
|
|
||||||
String user,
|
|
||||||
String session,
|
|
||||||
int winSizeW,
|
|
||||||
int winSizeH,
|
|
||||||
boolean maximize,
|
|
||||||
String serverAddress,
|
|
||||||
String serverPort
|
|
||||||
)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) );
|
|
||||||
} catch ( MalformedURLException ignored ) {}
|
|
||||||
|
|
||||||
// Implements support for launching in to multiplayer on classic servers using a mpticket
|
|
||||||
// file generated by an external program and stored in the instance's root folder.
|
|
||||||
File mpticketFile = null;
|
|
||||||
Scanner fileReader = null;
|
|
||||||
try {
|
|
||||||
mpticketFile = new File(System.getProperty("user.dir") + "/../mpticket").getCanonicalFile();
|
|
||||||
fileReader = new Scanner(new FileInputStream(mpticketFile), "ascii");
|
|
||||||
String[] mpticketParams = new String[3];
|
|
||||||
|
|
||||||
for(int i=0;i<3;i++) {
|
|
||||||
if(fileReader.hasNextLine()) {
|
|
||||||
mpticketParams[i] = fileReader.nextLine();
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assumes parameters are valid and in the correct order
|
|
||||||
appletWrap.setParameter("server", mpticketParams[0]);
|
|
||||||
appletWrap.setParameter("port", mpticketParams[1]);
|
|
||||||
appletWrap.setParameter("mppass", mpticketParams[2]);
|
|
||||||
|
|
||||||
fileReader.close();
|
|
||||||
mpticketFile.delete();
|
|
||||||
}
|
|
||||||
catch (FileNotFoundException e) {}
|
|
||||||
catch (IllegalArgumentException e) {
|
|
||||||
|
|
||||||
fileReader.close();
|
|
||||||
File mpticketFileCorrupt = new File(System.getProperty("user.dir") + "/../mpticket.corrupt");
|
|
||||||
if(mpticketFileCorrupt.exists()) {
|
|
||||||
mpticketFileCorrupt.delete();
|
|
||||||
}
|
|
||||||
mpticketFile.renameTo(mpticketFileCorrupt);
|
|
||||||
|
|
||||||
System.err.println("Malformed mpticket file, missing argument.");
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
System.exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverAddress != null)
|
|
||||||
{
|
|
||||||
appletWrap.setParameter("server", serverAddress);
|
|
||||||
appletWrap.setParameter("port", serverPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
appletWrap.setParameter ( "username", user );
|
|
||||||
appletWrap.setParameter ( "sessionid", session );
|
|
||||||
appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button.
|
|
||||||
appletWrap.setParameter ( "haspaid", "true" ); // Some old versions need this for world saves to work.
|
|
||||||
appletWrap.setParameter ( "demo", "false" );
|
|
||||||
appletWrap.setParameter ( "fullscreen", "false" );
|
|
||||||
mcApplet.setStub(appletWrap);
|
|
||||||
this.add ( appletWrap );
|
|
||||||
appletWrap.setPreferredSize ( new Dimension (winSizeW, winSizeH) );
|
|
||||||
this.pack();
|
|
||||||
this.setLocationRelativeTo ( null );
|
|
||||||
this.setResizable ( true );
|
|
||||||
if ( maximize ) {
|
|
||||||
this.setExtendedState ( MAXIMIZED_BOTH );
|
|
||||||
}
|
|
||||||
validate();
|
|
||||||
appletWrap.init();
|
|
||||||
appletWrap.start();
|
|
||||||
setVisible ( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowActivated ( WindowEvent e ) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowClosed ( WindowEvent e ) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowClosing ( WindowEvent e )
|
|
||||||
{
|
|
||||||
new Thread() {
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
Thread.sleep ( 30000L );
|
|
||||||
} catch ( InterruptedException localInterruptedException ) {
|
|
||||||
localInterruptedException.printStackTrace();
|
|
||||||
}
|
|
||||||
System.out.println ( "FORCING EXIT!" );
|
|
||||||
System.exit ( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.start();
|
|
||||||
|
|
||||||
if ( appletWrap != null ) {
|
|
||||||
appletWrap.stop();
|
|
||||||
appletWrap.destroy();
|
|
||||||
}
|
|
||||||
// old minecraft versions can hang without this >_<
|
|
||||||
System.exit ( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowDeactivated ( WindowEvent e ) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowDeiconified ( WindowEvent e ) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowIconified ( WindowEvent e ) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void windowOpened ( WindowEvent e ) {}
|
|
||||||
}
|
|
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2012-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.multimc;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Utils
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Combine two parts of a path.
|
|
||||||
*
|
|
||||||
* @param path1
|
|
||||||
* @param path2
|
|
||||||
* @return the paths, combined
|
|
||||||
*/
|
|
||||||
public static String combine(String path1, String path2)
|
|
||||||
{
|
|
||||||
File file1 = new File(path1);
|
|
||||||
File file2 = new File(file1, path2);
|
|
||||||
return file2.getPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Join a list of strings into a string using a separator!
|
|
||||||
*
|
|
||||||
* @param strings the string list to join
|
|
||||||
* @param separator the glue
|
|
||||||
* @return the result.
|
|
||||||
*/
|
|
||||||
public static String join(List<String> strings, String separator)
|
|
||||||
{
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
String sep = "";
|
|
||||||
for (String s : strings)
|
|
||||||
{
|
|
||||||
sb.append(sep).append(s);
|
|
||||||
sep = separator;
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a field that looks like a Minecraft base folder in a supplied class
|
|
||||||
*
|
|
||||||
* @param mc the class to scan
|
|
||||||
*/
|
|
||||||
public static Field getMCPathField(Class<?> mc)
|
|
||||||
{
|
|
||||||
Field[] fields = mc.getDeclaredFields();
|
|
||||||
|
|
||||||
for (Field f : fields)
|
|
||||||
{
|
|
||||||
if (f.getType() != File.class)
|
|
||||||
{
|
|
||||||
// Has to be File
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (f.getModifiers() != (Modifier.PRIVATE + Modifier.STATIC))
|
|
||||||
{
|
|
||||||
// And Private Static.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
162
libraries/launcher/org/multimc/applet/LegacyFrame.java
Normal file
162
libraries/launcher/org/multimc/applet/LegacyFrame.java
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.multimc.applet;
|
||||||
|
|
||||||
|
import net.minecraft.Launcher;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.applet.Applet;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public final class LegacyFrame extends Frame {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("LegacyFrame");
|
||||||
|
|
||||||
|
private final Launcher appletWrap;
|
||||||
|
|
||||||
|
public LegacyFrame(String title, Applet mcApplet) {
|
||||||
|
super(title);
|
||||||
|
|
||||||
|
appletWrap = new Launcher(mcApplet);
|
||||||
|
|
||||||
|
mcApplet.setStub(appletWrap);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIconImage(ImageIO.read(new File("icon.png")));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Unable to read Minecraft icon!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
addWindowListener(new ForceExitHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start (
|
||||||
|
String user,
|
||||||
|
String session,
|
||||||
|
int winSizeW,
|
||||||
|
int winSizeH,
|
||||||
|
boolean maximize,
|
||||||
|
String serverAddress,
|
||||||
|
String serverPort
|
||||||
|
) {
|
||||||
|
// Implements support for launching in to multiplayer on classic servers using a mpticket
|
||||||
|
// file generated by an external program and stored in the instance's root folder.
|
||||||
|
|
||||||
|
Path mpticketFile =
|
||||||
|
Paths.get(System.getProperty("user.dir"), "..", "mpticket");
|
||||||
|
|
||||||
|
Path mpticketFileCorrupt =
|
||||||
|
Paths.get(System.getProperty("user.dir"), "..", "mpticket.corrupt");
|
||||||
|
|
||||||
|
if (Files.exists(mpticketFile)) {
|
||||||
|
try {
|
||||||
|
List<String> lines = Files.readAllLines(mpticketFile, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if (lines.size() < 3) {
|
||||||
|
Files.move(
|
||||||
|
mpticketFile,
|
||||||
|
mpticketFileCorrupt,
|
||||||
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
|
);
|
||||||
|
|
||||||
|
LOGGER.warning("Mpticket file is corrupted!");
|
||||||
|
} else {
|
||||||
|
// Assumes parameters are valid and in the correct order
|
||||||
|
appletWrap.setParameter("server", lines.get(0));
|
||||||
|
appletWrap.setParameter("port", lines.get(1));
|
||||||
|
appletWrap.setParameter("mppass", lines.get(2));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Unable to read mpticket file!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverAddress != null) {
|
||||||
|
appletWrap.setParameter("server", serverAddress);
|
||||||
|
appletWrap.setParameter("port", serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
appletWrap.setParameter("username", user);
|
||||||
|
appletWrap.setParameter("sessionid", session);
|
||||||
|
appletWrap.setParameter("stand-alone", "true"); // Show the quit button.
|
||||||
|
appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work.
|
||||||
|
appletWrap.setParameter("demo", "false");
|
||||||
|
appletWrap.setParameter("fullscreen", "false");
|
||||||
|
|
||||||
|
add(appletWrap);
|
||||||
|
|
||||||
|
appletWrap.setPreferredSize(new Dimension(winSizeW, winSizeH));
|
||||||
|
|
||||||
|
pack();
|
||||||
|
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
setResizable(true);
|
||||||
|
|
||||||
|
if (maximize)
|
||||||
|
this.setExtendedState(MAXIMIZED_BOTH);
|
||||||
|
|
||||||
|
validate();
|
||||||
|
|
||||||
|
appletWrap.init();
|
||||||
|
appletWrap.start();
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ForceExitHandler extends WindowAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(30000L);
|
||||||
|
} catch (InterruptedException localInterruptedException) {
|
||||||
|
localInterruptedException.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Forcing exit!");
|
||||||
|
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
if (appletWrap != null) {
|
||||||
|
appletWrap.stop();
|
||||||
|
appletWrap.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// old minecraft versions can hang without this >_<
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -14,8 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.multimc;
|
package org.multimc.exception;
|
||||||
|
|
||||||
|
public final class ParameterNotFoundException extends IllegalArgumentException {
|
||||||
|
|
||||||
|
public ParameterNotFoundException(String key) {
|
||||||
|
super("Unknown parameter name: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
public class NotFoundException extends Exception
|
|
||||||
{
|
|
||||||
}
|
}
|
@ -14,12 +14,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.multimc;
|
package org.multimc.exception;
|
||||||
|
|
||||||
|
public final class ParseException extends IllegalArgumentException {
|
||||||
|
|
||||||
public class ParseException extends java.lang.Exception
|
|
||||||
{
|
|
||||||
public ParseException() { super(); }
|
|
||||||
public ParseException(String message) {
|
public ParseException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
189
libraries/launcher/org/multimc/impl/OneSixLauncher.java
Normal file
189
libraries/launcher/org/multimc/impl/OneSixLauncher.java
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
/* Copyright 2012-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.multimc.impl;
|
||||||
|
|
||||||
|
import org.multimc.Launcher;
|
||||||
|
import org.multimc.applet.LegacyFrame;
|
||||||
|
import org.multimc.utils.Parameters;
|
||||||
|
import org.multimc.utils.Utils;
|
||||||
|
|
||||||
|
import java.applet.Applet;
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public final class OneSixLauncher implements Launcher {
|
||||||
|
|
||||||
|
private static final int DEFAULT_WINDOW_WIDTH = 854;
|
||||||
|
private static final int DEFAULT_WINDOW_HEIGHT = 480;
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
|
||||||
|
|
||||||
|
// parameters, separated from ParamBucket
|
||||||
|
private final List<String> mcParams;
|
||||||
|
private final List<String> traits;
|
||||||
|
private final String appletClass;
|
||||||
|
private final String mainClass;
|
||||||
|
private final String userName, sessionId;
|
||||||
|
private final String windowTitle;
|
||||||
|
|
||||||
|
// secondary parameters
|
||||||
|
private final int winSizeW;
|
||||||
|
private final int winSizeH;
|
||||||
|
private final boolean maximize;
|
||||||
|
private final String cwd;
|
||||||
|
|
||||||
|
private final String serverAddress;
|
||||||
|
private final String serverPort;
|
||||||
|
|
||||||
|
private final ClassLoader classLoader;
|
||||||
|
|
||||||
|
public OneSixLauncher(Parameters params) {
|
||||||
|
classLoader = ClassLoader.getSystemClassLoader();
|
||||||
|
|
||||||
|
mcParams = params.allSafe("param", Collections.<String>emptyList());
|
||||||
|
mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
|
||||||
|
appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
|
||||||
|
traits = params.allSafe("traits", Collections.<String>emptyList());
|
||||||
|
|
||||||
|
userName = params.first("userName");
|
||||||
|
sessionId = params.first("sessionId");
|
||||||
|
windowTitle = params.firstSafe("windowTitle", "Minecraft");
|
||||||
|
|
||||||
|
serverAddress = params.firstSafe("serverAddress", null);
|
||||||
|
serverPort = params.firstSafe("serverPort", null);
|
||||||
|
|
||||||
|
cwd = System.getProperty("user.dir");
|
||||||
|
|
||||||
|
String windowParams = params.firstSafe("windowParams", null);
|
||||||
|
|
||||||
|
if (windowParams != null) {
|
||||||
|
String[] dimStrings = windowParams.split("x");
|
||||||
|
|
||||||
|
if (windowParams.equalsIgnoreCase("max")) {
|
||||||
|
maximize = true;
|
||||||
|
|
||||||
|
winSizeW = DEFAULT_WINDOW_WIDTH;
|
||||||
|
winSizeH = DEFAULT_WINDOW_HEIGHT;
|
||||||
|
} else if (dimStrings.length == 2) {
|
||||||
|
maximize = false;
|
||||||
|
|
||||||
|
winSizeW = Integer.parseInt(dimStrings[0]);
|
||||||
|
winSizeH = Integer.parseInt(dimStrings[1]);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unexpected window size parameter value: " + windowParams);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maximize = false;
|
||||||
|
|
||||||
|
winSizeW = DEFAULT_WINDOW_WIDTH;
|
||||||
|
winSizeH = DEFAULT_WINDOW_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeMain(Class<?> mainClass) throws Exception {
|
||||||
|
Method method = mainClass.getMethod("main", String[].class);
|
||||||
|
|
||||||
|
method.invoke(null, (Object) mcParams.toArray(new String[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void legacyLaunch() throws Exception {
|
||||||
|
// Get the Minecraft Class and set the base folder
|
||||||
|
Class<?> minecraftClass = classLoader.loadClass(mainClass);
|
||||||
|
|
||||||
|
Field baseDirField = Utils.getMinecraftBaseDirField(minecraftClass);
|
||||||
|
|
||||||
|
if (baseDirField == null) {
|
||||||
|
LOGGER.warning("Could not find Minecraft path field.");
|
||||||
|
} else {
|
||||||
|
baseDirField.setAccessible(true);
|
||||||
|
|
||||||
|
baseDirField.set(null, new File(cwd));
|
||||||
|
}
|
||||||
|
|
||||||
|
System.setProperty("minecraft.applet.TargetDirectory", cwd);
|
||||||
|
|
||||||
|
if (!traits.contains("noapplet")) {
|
||||||
|
LOGGER.info("Launching with applet wrapper...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> mcAppletClass = classLoader.loadClass(appletClass);
|
||||||
|
|
||||||
|
Applet mcApplet = (Applet) mcAppletClass.getConstructor().newInstance();
|
||||||
|
|
||||||
|
LegacyFrame mcWindow = new LegacyFrame(windowTitle, mcApplet);
|
||||||
|
|
||||||
|
mcWindow.start(
|
||||||
|
userName,
|
||||||
|
sessionId,
|
||||||
|
winSizeW,
|
||||||
|
winSizeH,
|
||||||
|
maximize,
|
||||||
|
serverAddress,
|
||||||
|
serverPort
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Applet wrapper failed: ", e);
|
||||||
|
|
||||||
|
LOGGER.warning("Falling back to using main class.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeMain(minecraftClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launchWithMainClass() throws Exception {
|
||||||
|
// window size, title and state, onesix
|
||||||
|
|
||||||
|
// FIXME: there is no good way to maximize the minecraft window in onesix.
|
||||||
|
// the following often breaks linux screen setups
|
||||||
|
// mcparams.add("--fullscreen");
|
||||||
|
|
||||||
|
if (!maximize) {
|
||||||
|
mcParams.add("--width");
|
||||||
|
mcParams.add(Integer.toString(winSizeW));
|
||||||
|
mcParams.add("--height");
|
||||||
|
mcParams.add(Integer.toString(winSizeH));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverAddress != null) {
|
||||||
|
mcParams.add("--server");
|
||||||
|
mcParams.add(serverAddress);
|
||||||
|
mcParams.add("--port");
|
||||||
|
mcParams.add(serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeMain(classLoader.loadClass(mainClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch() throws Exception {
|
||||||
|
if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) {
|
||||||
|
// legacy launch uses the applet wrapper
|
||||||
|
legacyLaunch();
|
||||||
|
} else {
|
||||||
|
// normal launch just calls main()
|
||||||
|
launchWithMainClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,256 +0,0 @@
|
|||||||
/* Copyright 2012-2021 MultiMC Contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.multimc.onesix;
|
|
||||||
|
|
||||||
import org.multimc.*;
|
|
||||||
|
|
||||||
import java.applet.Applet;
|
|
||||||
import java.io.File;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class OneSixLauncher implements Launcher
|
|
||||||
{
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger("OneSixLauncher");
|
|
||||||
|
|
||||||
// parameters, separated from ParamBucket
|
|
||||||
private List<String> libraries;
|
|
||||||
private List<String> mcparams;
|
|
||||||
private List<String> mods;
|
|
||||||
private List<String> jarmods;
|
|
||||||
private List<String> coremods;
|
|
||||||
private List<String> traits;
|
|
||||||
private String appletClass;
|
|
||||||
private String mainClass;
|
|
||||||
private String nativePath;
|
|
||||||
private String userName, sessionId;
|
|
||||||
private String windowTitle;
|
|
||||||
private String windowParams;
|
|
||||||
|
|
||||||
// secondary parameters
|
|
||||||
private int winSizeW;
|
|
||||||
private int winSizeH;
|
|
||||||
private boolean maximize;
|
|
||||||
private String cwd;
|
|
||||||
|
|
||||||
private String serverAddress;
|
|
||||||
private String serverPort;
|
|
||||||
|
|
||||||
// the much abused system classloader, for convenience (for further abuse)
|
|
||||||
private ClassLoader cl;
|
|
||||||
|
|
||||||
private void processParams(ParamBucket params) throws NotFoundException
|
|
||||||
{
|
|
||||||
libraries = params.all("cp");
|
|
||||||
mcparams = params.allSafe("param", new ArrayList<String>() );
|
|
||||||
mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft");
|
|
||||||
appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet");
|
|
||||||
traits = params.allSafe("traits", new ArrayList<String>());
|
|
||||||
nativePath = params.first("natives");
|
|
||||||
|
|
||||||
userName = params.first("userName");
|
|
||||||
sessionId = params.first("sessionId");
|
|
||||||
windowTitle = params.firstSafe("windowTitle", "Minecraft");
|
|
||||||
windowParams = params.firstSafe("windowParams", "854x480");
|
|
||||||
|
|
||||||
serverAddress = params.firstSafe("serverAddress", null);
|
|
||||||
serverPort = params.firstSafe("serverPort", null);
|
|
||||||
|
|
||||||
cwd = System.getProperty("user.dir");
|
|
||||||
|
|
||||||
winSizeW = 854;
|
|
||||||
winSizeH = 480;
|
|
||||||
maximize = false;
|
|
||||||
|
|
||||||
String[] dimStrings = windowParams.split("x");
|
|
||||||
|
|
||||||
if (windowParams.equalsIgnoreCase("max"))
|
|
||||||
{
|
|
||||||
maximize = true;
|
|
||||||
}
|
|
||||||
else if (dimStrings.length == 2)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
winSizeW = Integer.parseInt(dimStrings[0]);
|
|
||||||
winSizeH = Integer.parseInt(dimStrings[1]);
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int legacyLaunch()
|
|
||||||
{
|
|
||||||
// Get the Minecraft Class and set the base folder
|
|
||||||
Class<?> mc;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mc = cl.loadClass(mainClass);
|
|
||||||
|
|
||||||
Field f = Utils.getMCPathField(mc);
|
|
||||||
|
|
||||||
if (f == null)
|
|
||||||
{
|
|
||||||
LOGGER.warning("Could not find Minecraft path field.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
f.setAccessible(true);
|
|
||||||
f.set(null, new File(cwd));
|
|
||||||
}
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.log(
|
|
||||||
Level.SEVERE,
|
|
||||||
"Could not set base folder. Failed to find/access Minecraft main class:",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.setProperty("minecraft.applet.TargetDirectory", cwd);
|
|
||||||
|
|
||||||
if(!traits.contains("noapplet"))
|
|
||||||
{
|
|
||||||
LOGGER.info("Launching with applet wrapper...");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Class<?> MCAppletClass = cl.loadClass(appletClass);
|
|
||||||
Applet mcappl = (Applet) MCAppletClass.newInstance();
|
|
||||||
LegacyFrame mcWindow = new LegacyFrame(windowTitle);
|
|
||||||
mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize, serverAddress, serverPort);
|
|
||||||
return 0;
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Applet wrapper failed:", e);
|
|
||||||
|
|
||||||
LOGGER.warning("Falling back to using main class.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// init params for the main method to chomp on.
|
|
||||||
String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray);
|
|
||||||
return 0;
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Failed to invoke the Minecraft main class:", e);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int launchWithMainClass()
|
|
||||||
{
|
|
||||||
// window size, title and state, onesix
|
|
||||||
if (maximize)
|
|
||||||
{
|
|
||||||
// FIXME: there is no good way to maximize the minecraft window in onesix.
|
|
||||||
// the following often breaks linux screen setups
|
|
||||||
// mcparams.add("--fullscreen");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mcparams.add("--width");
|
|
||||||
mcparams.add(Integer.toString(winSizeW));
|
|
||||||
mcparams.add("--height");
|
|
||||||
mcparams.add(Integer.toString(winSizeH));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverAddress != null)
|
|
||||||
{
|
|
||||||
mcparams.add("--server");
|
|
||||||
mcparams.add(serverAddress);
|
|
||||||
mcparams.add("--port");
|
|
||||||
mcparams.add(serverPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Minecraft Class.
|
|
||||||
Class<?> mc;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mc = cl.loadClass(mainClass);
|
|
||||||
} catch (ClassNotFoundException e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Failed to find Minecraft main class:", e);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the main method.
|
|
||||||
Method meth;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
meth = mc.getMethod("main", String[].class);
|
|
||||||
} catch (NoSuchMethodException e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Failed to acquire the main method:", e);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// init params for the main method to chomp on.
|
|
||||||
String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// static method doesn't have an instance
|
|
||||||
meth.invoke(null, (Object) paramsArray);
|
|
||||||
} catch (Exception e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Failed to start Minecraft:", e);
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int launch(ParamBucket params)
|
|
||||||
{
|
|
||||||
// get and process the launch script params
|
|
||||||
try
|
|
||||||
{
|
|
||||||
processParams(params);
|
|
||||||
} catch (NotFoundException e)
|
|
||||||
{
|
|
||||||
LOGGER.log(Level.SEVERE, "Not enough arguments!");
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab the system classloader and ...
|
|
||||||
cl = ClassLoader.getSystemClassLoader();
|
|
||||||
|
|
||||||
if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch") )
|
|
||||||
{
|
|
||||||
// legacy launch uses the applet wrapper
|
|
||||||
return legacyLaunch();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// normal launch just calls main()
|
|
||||||
return launchWithMainClass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -14,36 +14,41 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.multimc;
|
package org.multimc.utils;
|
||||||
|
|
||||||
|
import org.multimc.exception.ParameterNotFoundException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ParamBucket
|
public final class Parameters {
|
||||||
{
|
|
||||||
|
|
||||||
private final Map<String, List<String>> paramsMap = new HashMap<>();
|
private final Map<String, List<String>> paramsMap = new HashMap<>();
|
||||||
|
|
||||||
public void add(String key, String value)
|
public void add(String key, String value) {
|
||||||
{
|
List<String> params = paramsMap.get(key);
|
||||||
paramsMap.computeIfAbsent(key, k -> new ArrayList<>())
|
|
||||||
.add(value);
|
if (params == null) {
|
||||||
|
params = new ArrayList<>();
|
||||||
|
|
||||||
|
paramsMap.put(key, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> all(String key) throws NotFoundException
|
params.add(value);
|
||||||
{
|
}
|
||||||
|
|
||||||
|
public List<String> all(String key) throws ParameterNotFoundException {
|
||||||
List<String> params = paramsMap.get(key);
|
List<String> params = paramsMap.get(key);
|
||||||
|
|
||||||
if (params == null)
|
if (params == null)
|
||||||
throw new NotFoundException();
|
throw new ParameterNotFoundException(key);
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> allSafe(String key, List<String> def)
|
public List<String> allSafe(String key, List<String> def) {
|
||||||
{
|
|
||||||
List<String> params = paramsMap.get(key);
|
List<String> params = paramsMap.get(key);
|
||||||
|
|
||||||
if (params == null || params.isEmpty())
|
if (params == null || params.isEmpty())
|
||||||
@ -52,23 +57,16 @@ public class ParamBucket
|
|||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> allSafe(String key)
|
public String first(String key) throws ParameterNotFoundException {
|
||||||
{
|
|
||||||
return allSafe(key, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String first(String key) throws NotFoundException
|
|
||||||
{
|
|
||||||
List<String> list = all(key);
|
List<String> list = all(key);
|
||||||
|
|
||||||
if (list.isEmpty())
|
if (list.isEmpty())
|
||||||
throw new NotFoundException();
|
throw new ParameterNotFoundException(key);
|
||||||
|
|
||||||
return list.get(0);
|
return list.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String firstSafe(String key, String def)
|
public String firstSafe(String key, String def) {
|
||||||
{
|
|
||||||
List<String> params = paramsMap.get(key);
|
List<String> params = paramsMap.get(key);
|
||||||
|
|
||||||
if (params == null || params.isEmpty())
|
if (params == null || params.isEmpty())
|
||||||
@ -77,9 +75,4 @@ public class ParamBucket
|
|||||||
return params.get(0);
|
return params.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String firstSafe(String key)
|
|
||||||
{
|
|
||||||
return firstSafe(key, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
49
libraries/launcher/org/multimc/utils/Utils.java
Normal file
49
libraries/launcher/org/multimc/utils/Utils.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012-2021 MultiMC Contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.multimc.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
public final class Utils {
|
||||||
|
|
||||||
|
private Utils() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a field that looks like a Minecraft base folder in a supplied class
|
||||||
|
*
|
||||||
|
* @param clazz the class to scan
|
||||||
|
*/
|
||||||
|
public static Field getMinecraftBaseDirField(Class<?> clazz) {
|
||||||
|
for (Field f : clazz.getDeclaredFields()) {
|
||||||
|
// Has to be File
|
||||||
|
if (f.getType() != File.class)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// And Private Static.
|
||||||
|
if (!Modifier.isStatic(f.getModifiers()) || !Modifier.isPrivate(f.getModifiers()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -28,23 +28,23 @@
|
|||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<caption>The main PolyMC window</caption>
|
<caption>The main PolyMC window</caption>
|
||||||
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherDark.png</image>
|
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherDark.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<caption>Modpack installation</caption>
|
<caption>Modpack installation</caption>
|
||||||
<image type="source" width="911" height="682">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
|
<image type="source" width="860" height="848">https://polymc.org/img/screenshots/ModpackInstallDark.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<caption>Mod installation</caption>
|
<caption>Mod installation</caption>
|
||||||
<image type="source" width="987" height="723">https://polymc.org/img/screenshots/ModInstallDark.png</image>
|
<image type="source" width="1018" height="858">https://polymc.org/img/screenshots/ModInstallDark.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<caption>Instance management</caption>
|
<caption>Instance management</caption>
|
||||||
<image type="source" width="902" height="920">https://polymc.org/img/screenshots/PropertiesDark.png</image>
|
<image type="source" width="777" height="693">https://polymc.org/img/screenshots/PropertiesDark.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<caption>Cat :)</caption>
|
<caption>Cat :)</caption>
|
||||||
<image type="source" width="1011" height="994">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
|
<image type="source" width="931" height="759">https://polymc.org/img/screenshots/LauncherCatDark.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<releases>
|
<releases>
|
||||||
|
233
program_info/win_install.nsi
Normal file
233
program_info/win_install.nsi
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
!include "FileFunc.nsh"
|
||||||
|
!include "LogicLib.nsh"
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
Unicode true
|
||||||
|
|
||||||
|
Name "PolyMC"
|
||||||
|
InstallDir "$LOCALAPPDATA\Programs\PolyMC"
|
||||||
|
InstallDirRegKey HKCU "Software\PolyMC" "InstallDir"
|
||||||
|
RequestExecutionLevel user
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
|
||||||
|
; Pages
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!define MUI_COMPONENTSPAGE_NODESC
|
||||||
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!define MUI_FINISHPAGE_RUN "$InstDir\polymc.exe"
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
|
||||||
|
; Languages
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
|
!insertmacro MUI_LANGUAGE "French"
|
||||||
|
!insertmacro MUI_LANGUAGE "German"
|
||||||
|
!insertmacro MUI_LANGUAGE "Spanish"
|
||||||
|
!insertmacro MUI_LANGUAGE "SpanishInternational"
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
!insertmacro MUI_LANGUAGE "TradChinese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Japanese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Korean"
|
||||||
|
!insertmacro MUI_LANGUAGE "Italian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Dutch"
|
||||||
|
!insertmacro MUI_LANGUAGE "Danish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Swedish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Norwegian"
|
||||||
|
!insertmacro MUI_LANGUAGE "NorwegianNynorsk"
|
||||||
|
!insertmacro MUI_LANGUAGE "Finnish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Greek"
|
||||||
|
!insertmacro MUI_LANGUAGE "Russian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Portuguese"
|
||||||
|
!insertmacro MUI_LANGUAGE "PortugueseBR"
|
||||||
|
!insertmacro MUI_LANGUAGE "Polish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Ukrainian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Czech"
|
||||||
|
!insertmacro MUI_LANGUAGE "Slovak"
|
||||||
|
!insertmacro MUI_LANGUAGE "Croatian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Bulgarian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hungarian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Thai"
|
||||||
|
!insertmacro MUI_LANGUAGE "Romanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Latvian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Macedonian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Estonian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Turkish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Lithuanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Slovenian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Serbian"
|
||||||
|
!insertmacro MUI_LANGUAGE "SerbianLatin"
|
||||||
|
!insertmacro MUI_LANGUAGE "Arabic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Farsi"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hebrew"
|
||||||
|
!insertmacro MUI_LANGUAGE "Indonesian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Mongolian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Luxembourgish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Albanian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Breton"
|
||||||
|
!insertmacro MUI_LANGUAGE "Belarusian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Icelandic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Malay"
|
||||||
|
!insertmacro MUI_LANGUAGE "Bosnian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Kurdish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Irish"
|
||||||
|
!insertmacro MUI_LANGUAGE "Uzbek"
|
||||||
|
!insertmacro MUI_LANGUAGE "Galician"
|
||||||
|
!insertmacro MUI_LANGUAGE "Afrikaans"
|
||||||
|
!insertmacro MUI_LANGUAGE "Catalan"
|
||||||
|
!insertmacro MUI_LANGUAGE "Esperanto"
|
||||||
|
!insertmacro MUI_LANGUAGE "Asturian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Basque"
|
||||||
|
!insertmacro MUI_LANGUAGE "Pashto"
|
||||||
|
!insertmacro MUI_LANGUAGE "ScotsGaelic"
|
||||||
|
!insertmacro MUI_LANGUAGE "Georgian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Vietnamese"
|
||||||
|
!insertmacro MUI_LANGUAGE "Welsh"
|
||||||
|
!insertmacro MUI_LANGUAGE "Armenian"
|
||||||
|
!insertmacro MUI_LANGUAGE "Corsican"
|
||||||
|
!insertmacro MUI_LANGUAGE "Tatar"
|
||||||
|
!insertmacro MUI_LANGUAGE "Hindi"
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
|
||||||
|
; The stuff to install
|
||||||
|
Section "PolyMC"
|
||||||
|
|
||||||
|
SectionIn RO
|
||||||
|
|
||||||
|
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
|
||||||
|
|
||||||
|
SetOutPath $INSTDIR
|
||||||
|
|
||||||
|
File "polymc.exe"
|
||||||
|
File "qt.conf"
|
||||||
|
File *.dll
|
||||||
|
File /r "iconengines"
|
||||||
|
File /r "imageformats"
|
||||||
|
File /r "jars"
|
||||||
|
File /r "platforms"
|
||||||
|
File /r "styles"
|
||||||
|
|
||||||
|
; Write the installation path into the registry
|
||||||
|
WriteRegStr HKCU Software\PolyMC "InstallDir" "$INSTDIR"
|
||||||
|
|
||||||
|
; Write the uninstall keys for Windows
|
||||||
|
${GetParameters} $R0
|
||||||
|
${GetOptions} $R0 "/NoUninstaller" $R1
|
||||||
|
${If} ${Errors}
|
||||||
|
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "PolyMC"
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\polymc.exe"
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "PolyMC Contributors"
|
||||||
|
WriteRegStr HKCU "${UNINST_KEY}" "ProductVersion" "${VERSION}"
|
||||||
|
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
|
||||||
|
IntFmt $0 "0x%08X" $0
|
||||||
|
WriteRegDWORD HKCU "${UNINST_KEY}" "EstimatedSize" "$0"
|
||||||
|
WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1
|
||||||
|
WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1
|
||||||
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Start Menu Shortcuts" SHORTCUTS
|
||||||
|
|
||||||
|
CreateShortcut "$SMPROGRAMS\PolyMC.lnk" "$INSTDIR\polymc.exe" "" "$INSTDIR\polymc.exe" 0
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
|
||||||
|
; Uninstaller
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
|
||||||
|
nsExec::Exec /TIMEOUT=2000 'TaskKill /IM polymc.exe /F'
|
||||||
|
|
||||||
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\PolyMC"
|
||||||
|
DeleteRegKey HKCU SOFTWARE\PolyMC
|
||||||
|
|
||||||
|
Delete $INSTDIR\polymc.exe
|
||||||
|
Delete $INSTDIR\uninstall.exe
|
||||||
|
Delete $INSTDIR\portable.txt
|
||||||
|
|
||||||
|
Delete $INSTDIR\libbrotlicommon.dll
|
||||||
|
Delete $INSTDIR\libbrotlidec.dll
|
||||||
|
Delete $INSTDIR\libbz2-1.dll
|
||||||
|
Delete $INSTDIR\libcrypto-1_1-x64.dll
|
||||||
|
Delete $INSTDIR\libcrypto-1_1.dll
|
||||||
|
Delete $INSTDIR\libdouble-conversion.dll
|
||||||
|
Delete $INSTDIR\libfreetype-6.dll
|
||||||
|
Delete $INSTDIR\libgcc_s_seh-1.dll
|
||||||
|
Delete $INSTDIR\libgcc_s_dw2-1.dll
|
||||||
|
Delete $INSTDIR\libglib-2.0-0.dll
|
||||||
|
Delete $INSTDIR\libgraphite2.dll
|
||||||
|
Delete $INSTDIR\libharfbuzz-0.dll
|
||||||
|
Delete $INSTDIR\libiconv-2.dll
|
||||||
|
Delete $INSTDIR\libicudt69.dll
|
||||||
|
Delete $INSTDIR\libicuin69.dll
|
||||||
|
Delete $INSTDIR\libicuuc69.dll
|
||||||
|
Delete $INSTDIR\libintl-8.dll
|
||||||
|
Delete $INSTDIR\libjasper-4.dll
|
||||||
|
Delete $INSTDIR\libjpeg-8.dll
|
||||||
|
Delete $INSTDIR\libmd4c.dll
|
||||||
|
Delete $INSTDIR\libpcre-1.dll
|
||||||
|
Delete $INSTDIR\libpcre2-16-0.dll
|
||||||
|
Delete $INSTDIR\libpng16-16.dll
|
||||||
|
Delete $INSTDIR\libssl-1_1-x64.dll
|
||||||
|
Delete $INSTDIR\libssl-1_1.dll
|
||||||
|
Delete $INSTDIR\libssp-0.dll
|
||||||
|
Delete $INSTDIR\libstdc++-6.dll
|
||||||
|
Delete $INSTDIR\libwebp-7.dll
|
||||||
|
Delete $INSTDIR\libwebpdemux-2.dll
|
||||||
|
Delete $INSTDIR\libwebpmux-3.dll
|
||||||
|
Delete $INSTDIR\libwinpthread-1.dll
|
||||||
|
Delete $INSTDIR\libzstd.dll
|
||||||
|
Delete $INSTDIR\Qt5Core.dll
|
||||||
|
Delete $INSTDIR\Qt5Gui.dll
|
||||||
|
Delete $INSTDIR\Qt5Network.dll
|
||||||
|
Delete $INSTDIR\Qt5Qml.dll
|
||||||
|
Delete $INSTDIR\Qt5QmlModels.dll
|
||||||
|
Delete $INSTDIR\Qt5Quick.dll
|
||||||
|
Delete $INSTDIR\Qt5Svg.dll
|
||||||
|
Delete $INSTDIR\Qt5WebSockets.dll
|
||||||
|
Delete $INSTDIR\Qt5Widgets.dll
|
||||||
|
Delete $INSTDIR\Qt5Xml.dll
|
||||||
|
Delete $INSTDIR\zlib1.dll
|
||||||
|
|
||||||
|
Delete $INSTDIR\qt.conf
|
||||||
|
|
||||||
|
RMDir /r $INSTDIR\iconengines
|
||||||
|
RMDir /r $INSTDIR\imageformats
|
||||||
|
RMDir /r $INSTDIR\jars
|
||||||
|
RMDir /r $INSTDIR\platforms
|
||||||
|
RMDir /r $INSTDIR\styles
|
||||||
|
|
||||||
|
Delete "$SMPROGRAMS\PolyMC.lnk"
|
||||||
|
|
||||||
|
RMDir "$INSTDIR"
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
;--------------------------------
|
||||||
|
|
||||||
|
; Extra command line parameters
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
${GetParameters} $R0
|
||||||
|
${GetOptions} $R0 "/NoShortcuts" $R1
|
||||||
|
${IfNot} ${Errors}
|
||||||
|
!insertmacro UnselectSection ${SHORTCUTS}
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
Loading…
Reference in New Issue
Block a user