mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-05-04 08:34:30 +02:00
Merge remote-tracking branch 'upstream/develop' into resource-meta
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
commit
89a327d363
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -173,7 +173,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||||
uses: actions/cache@v4.0.2
|
uses: actions/cache@v4.1.1
|
||||||
with:
|
with:
|
||||||
path: '${{ github.workspace }}\.ccache'
|
path: '${{ github.workspace }}\.ccache'
|
||||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||||
@ -410,9 +410,8 @@ jobs:
|
|||||||
if: matrix.name == 'macOS'
|
if: matrix.name == 'macOS'
|
||||||
run: |
|
run: |
|
||||||
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then
|
||||||
brew install openssl@3
|
|
||||||
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
echo '${{ secrets.SPARKLE_ED25519_KEY }}' > ed25519-priv.pem
|
||||||
signature=$(/usr/local/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
signature=$(/opt/homebrew/opt/openssl@3/bin/openssl pkeyutl -sign -rawin -in ${{ github.workspace }}/PrismLauncher.zip -inkey ed25519-priv.pem | openssl base64 | tr -d \\n)
|
||||||
rm ed25519-priv.pem
|
rm ed25519-priv.pem
|
||||||
cat >> $GITHUB_STEP_SUMMARY << EOF
|
cat >> $GITHUB_STEP_SUMMARY << EOF
|
||||||
### Artifact Information :information_source:
|
### Artifact Information :information_source:
|
||||||
@ -672,7 +671,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Nix
|
- name: Install Nix
|
||||||
uses: cachix/install-nix-action@v29
|
uses: cachix/install-nix-action@v30
|
||||||
|
|
||||||
# For PRs
|
# For PRs
|
||||||
- name: Setup Nix Magic Cache
|
- name: Setup Nix Magic Cache
|
||||||
|
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: cachix/install-nix-action@9f70348d77d0422624097c4b7a75563948901306 # v29
|
- uses: cachix/install-nix-action@08dcb3a5e62fa31e2da3d490afc4176ef55ecd72 # v30
|
||||||
|
|
||||||
- uses: DeterminateSystems/update-flake-lock@v24
|
- uses: DeterminateSystems/update-flake-lock@v24
|
||||||
with:
|
with:
|
||||||
|
@ -99,7 +99,7 @@ if ((CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebI
|
|||||||
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
|
message(STATUS "Address Sanitizer enabled for Debug builds, Turn it off with -DDEBUG_ADDRESS_SANITIZER=off")
|
||||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
# using clang with clang-cl front end
|
# using clang with clang-cl front end
|
||||||
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
|
message(STATUS "Address Sanitizer available on Clang MSVC frontend")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /Oy-")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /Oy-")
|
||||||
@ -225,7 +225,7 @@ set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build agains
|
|||||||
# Java downloader
|
# Java downloader
|
||||||
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
|
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
|
||||||
|
|
||||||
# Although we recommend enabling this, we cannot guarantee binary compatibility on
|
# Although we recommend enabling this, we cannot guarantee binary compatibility on
|
||||||
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
|
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
|
||||||
# feature if they know it will work with their distribution.
|
# feature if they know it will work with their distribution.
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
@ -438,10 +438,10 @@ elseif(UNIX)
|
|||||||
set(PLUGIN_DEST_DIR "plugins")
|
set(PLUGIN_DEST_DIR "plugins")
|
||||||
set(BUNDLE_DEST_DIR ".")
|
set(BUNDLE_DEST_DIR ".")
|
||||||
set(RESOURCES_DEST_DIR ".")
|
set(RESOURCES_DEST_DIR ".")
|
||||||
|
|
||||||
# Apps to bundle
|
# Apps to bundle
|
||||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
|
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/${Launcher_APP_BINARY_NAME}")
|
||||||
|
|
||||||
# directories to look for dependencies
|
# directories to look for dependencies
|
||||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||||
endif()
|
endif()
|
||||||
@ -495,7 +495,7 @@ if(FORCE_BUNDLED_ZLIB)
|
|||||||
set(SKIP_INSTALL_ALL ON)
|
set(SKIP_INSTALL_ALL ON)
|
||||||
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
add_subdirectory(libraries/zlib EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
|
# On OS where unistd.h exists, zlib's generated header defines `Z_HAVE_UNISTD_H`, while the included header does not.
|
||||||
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
|
# We cannot safely undo the rename on those systems, and they generally have packages for zlib anyway.
|
||||||
check_include_file(unistd.h NEED_GENERATED_ZCONF)
|
check_include_file(unistd.h NEED_GENERATED_ZCONF)
|
||||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
|
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/libraries/zlib/zconf.h.included" AND NOT NEED_GENERATED_ZCONF)
|
||||||
@ -532,10 +532,12 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
if(NOT cmark_FOUND)
|
if(NOT cmark_FOUND)
|
||||||
message(STATUS "Using bundled cmark")
|
message(STATUS "Using bundled cmark")
|
||||||
|
set(ORIGINAL_BUILD_TESTING ${BUILD_TESTING})
|
||||||
set(BUILD_TESTING 0)
|
set(BUILD_TESTING 0)
|
||||||
set(BUILD_SHARED_LIBS 0)
|
set(BUILD_SHARED_LIBS 0)
|
||||||
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
|
add_subdirectory(libraries/cmark EXCLUDE_FROM_ALL) # Markdown parser
|
||||||
add_library(cmark::cmark ALIAS cmark)
|
add_library(cmark::cmark ALIAS cmark)
|
||||||
|
set(BUILD_TESTING ${ORIGINAL_BUILD_TESTING})
|
||||||
else()
|
else()
|
||||||
message(STATUS "Using system cmark")
|
message(STATUS "Using system cmark")
|
||||||
endif()
|
endif()
|
||||||
|
6
flake.lock
generated
6
flake.lock
generated
@ -49,11 +49,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728018373,
|
"lastModified": 1729256560,
|
||||||
"narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
|
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
|
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -3,9 +3,7 @@ runtime: org.kde.Platform
|
|||||||
runtime-version: 6.7
|
runtime-version: 6.7
|
||||||
sdk: org.kde.Sdk
|
sdk: org.kde.Sdk
|
||||||
sdk-extensions:
|
sdk-extensions:
|
||||||
- org.freedesktop.Sdk.Extension.openjdk21
|
|
||||||
- org.freedesktop.Sdk.Extension.openjdk17
|
- org.freedesktop.Sdk.Extension.openjdk17
|
||||||
- org.freedesktop.Sdk.Extension.openjdk8
|
|
||||||
|
|
||||||
command: prismlauncher
|
command: prismlauncher
|
||||||
finish-args:
|
finish-args:
|
||||||
@ -22,9 +20,6 @@ finish-args:
|
|||||||
# FTBApp import
|
# FTBApp import
|
||||||
- --filesystem=~/.ftba:ro
|
- --filesystem=~/.ftba:ro
|
||||||
|
|
||||||
cleanup:
|
|
||||||
- /lib/libGLU*
|
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
# Might be needed by some Controller mods (see https://github.com/isXander/Controlify/issues/31)
|
||||||
- shared-modules/libusb/libusb.json
|
- shared-modules/libusb/libusb.json
|
||||||
@ -42,23 +37,11 @@ modules:
|
|||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
JAVA_HOME: /usr/lib/sdk/openjdk17/jvm/openjdk-17
|
||||||
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
|
JAVA_COMPILER: /usr/lib/sdk/openjdk17/jvm/openjdk-17/bin/javac
|
||||||
|
run-tests: true
|
||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
- name: openjdk
|
|
||||||
buildsystem: simple
|
|
||||||
build-commands:
|
|
||||||
- mkdir -p /app/jdk/
|
|
||||||
- /usr/lib/sdk/openjdk21/install.sh
|
|
||||||
- mv /app/jre /app/jdk/21
|
|
||||||
- /usr/lib/sdk/openjdk17/install.sh
|
|
||||||
- mv /app/jre /app/jdk/17
|
|
||||||
- /usr/lib/sdk/openjdk8/install.sh
|
|
||||||
- mv /app/jre /app/jdk/8
|
|
||||||
cleanup:
|
|
||||||
- /jre
|
|
||||||
|
|
||||||
- name: glfw
|
- name: glfw
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
config-opts:
|
config-opts:
|
||||||
|
@ -1071,6 +1071,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||||||
bool Application::createSetupWizard()
|
bool Application::createSetupWizard()
|
||||||
{
|
{
|
||||||
bool javaRequired = [&]() {
|
bool javaRequired = [&]() {
|
||||||
|
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
||||||
if (ignoreJavaWizard) {
|
if (ignoreJavaWizard) {
|
||||||
return false;
|
return false;
|
||||||
@ -1083,10 +1086,7 @@ bool Application::createSetupWizard()
|
|||||||
}
|
}
|
||||||
QString currentJavaPath = settings()->get("JavaPath").toString();
|
QString currentJavaPath = settings()->get("JavaPath").toString();
|
||||||
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
||||||
if (actualPath.isNull()) {
|
return actualPath.isNull();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}();
|
}();
|
||||||
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
||||||
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
||||||
@ -1883,3 +1883,31 @@ const QString Application::javaPath()
|
|||||||
{
|
{
|
||||||
return m_settings->get("JavaDir").toString();
|
return m_settings->get("JavaDir").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::addQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
m_qsaveResources[path] = m_qsaveResources.value(path, 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::removeQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
auto count = m_qsaveResources.value(path, 0) - 1;
|
||||||
|
if (count <= 0) {
|
||||||
|
m_qsaveResources.remove(path);
|
||||||
|
} else {
|
||||||
|
m_qsaveResources[path] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::checkQSavePath(QString path)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_qsaveResourcesMutex);
|
||||||
|
for (auto partialPath : m_qsaveResources.keys()) {
|
||||||
|
if (path.startsWith(partialPath) && m_qsaveResources.value(partialPath, 0) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -42,6 +42,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFlag>
|
#include <QFlag>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
#include <QMutex>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@ -81,6 +82,12 @@ class Index;
|
|||||||
#endif
|
#endif
|
||||||
#define APPLICATION (static_cast<Application*>(QCoreApplication::instance()))
|
#define APPLICATION (static_cast<Application*>(QCoreApplication::instance()))
|
||||||
|
|
||||||
|
// Used for checking if is a test
|
||||||
|
#if defined(APPLICATION_DYN)
|
||||||
|
#undef APPLICATION_DYN
|
||||||
|
#endif
|
||||||
|
#define APPLICATION_DYN (dynamic_cast<Application*>(QCoreApplication::instance()))
|
||||||
|
|
||||||
class Application : public QApplication {
|
class Application : public QApplication {
|
||||||
// friends for the purpose of limiting access to deprecated stuff
|
// friends for the purpose of limiting access to deprecated stuff
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -297,4 +304,13 @@ class Application : public QApplication {
|
|||||||
QList<QUrl> m_urlsToImport;
|
QList<QUrl> m_urlsToImport;
|
||||||
QString m_instanceIdToShowWindowOf;
|
QString m_instanceIdToShowWindowOf;
|
||||||
std::unique_ptr<QFile> logFile;
|
std::unique_ptr<QFile> logFile;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addQSavePath(QString);
|
||||||
|
void removeQSavePath(QString);
|
||||||
|
bool checkQSavePath(QString);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<QString, int> m_qsaveResources;
|
||||||
|
mutable QMutex m_qsaveResourcesMutex;
|
||||||
};
|
};
|
||||||
|
@ -30,6 +30,7 @@ set(CORE_SOURCES
|
|||||||
StringUtils.cpp
|
StringUtils.cpp
|
||||||
QVariantUtils.h
|
QVariantUtils.h
|
||||||
RuntimeContext.h
|
RuntimeContext.h
|
||||||
|
PSaveFile.h
|
||||||
|
|
||||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||||
InstanceCreationTask.h
|
InstanceCreationTask.h
|
||||||
@ -607,7 +608,7 @@ set(PRISMUPDATER_SOURCES
|
|||||||
updater/prismupdater/UpdaterDialogs.cpp
|
updater/prismupdater/UpdaterDialogs.cpp
|
||||||
updater/prismupdater/GitHubRelease.h
|
updater/prismupdater/GitHubRelease.h
|
||||||
updater/prismupdater/GitHubRelease.cpp
|
updater/prismupdater/GitHubRelease.cpp
|
||||||
|
|
||||||
Json.h
|
Json.h
|
||||||
Json.cpp
|
Json.cpp
|
||||||
FileSystem.h
|
FileSystem.h
|
||||||
@ -624,7 +625,7 @@ set(PRISMUPDATER_SOURCES
|
|||||||
# Zip
|
# Zip
|
||||||
MMCZip.h
|
MMCZip.h
|
||||||
MMCZip.cpp
|
MMCZip.cpp
|
||||||
|
|
||||||
# Time
|
# Time
|
||||||
MMCTime.h
|
MMCTime.h
|
||||||
MMCTime.cpp
|
MMCTime.cpp
|
||||||
@ -1264,14 +1265,10 @@ include(CompilerWarnings)
|
|||||||
|
|
||||||
# Add executable
|
# Add executable
|
||||||
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
|
||||||
if(BUILD_TESTING)
|
|
||||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST)
|
|
||||||
endif()
|
|
||||||
set_project_warnings(Launcher_logic
|
set_project_warnings(Launcher_logic
|
||||||
"${Launcher_MSVC_WARNINGS}"
|
"${Launcher_MSVC_WARNINGS}"
|
||||||
"${Launcher_CLANG_WARNINGS}"
|
"${Launcher_CLANG_WARNINGS}"
|
||||||
"${Launcher_GCC_WARNINGS}")
|
"${Launcher_GCC_WARNINGS}")
|
||||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
|
||||||
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
|
||||||
target_link_libraries(Launcher_logic
|
target_link_libraries(Launcher_logic
|
||||||
@ -1375,7 +1372,7 @@ if(Launcher_BUILD_UPDATER)
|
|||||||
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
add_executable("${Launcher_Name}_updater" WIN32 updater/prismupdater/updater_main.cpp)
|
||||||
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
|
target_sources("${Launcher_Name}_updater" PRIVATE updater/prismupdater/updater.exe.manifest)
|
||||||
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
|
target_link_libraries("${Launcher_Name}_updater" prism_updater_logic)
|
||||||
|
|
||||||
if(DEFINED Launcher_APP_BINARY_NAME)
|
if(DEFINED Launcher_APP_BINARY_NAME)
|
||||||
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
|
set_target_properties("${Launcher_Name}_updater" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_updater")
|
||||||
endif()
|
endif()
|
||||||
|
@ -45,7 +45,6 @@
|
|||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QStorageInfo>
|
#include <QStorageInfo>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
@ -54,6 +53,7 @@
|
|||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
|
||||||
#include "DesktopServices.h"
|
#include "DesktopServices.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
@ -191,8 +191,8 @@ void ensureExists(const QDir& dir)
|
|||||||
void write(const QString& filename, const QByteArray& data)
|
void write(const QString& filename, const QByteArray& data)
|
||||||
{
|
{
|
||||||
ensureExists(QFileInfo(filename).dir());
|
ensureExists(QFileInfo(filename).dir());
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (data.size() != file.write(data)) {
|
if (data.size() != file.write(data)) {
|
||||||
@ -213,8 +213,8 @@ void appendSafe(const QString& filename, const QByteArray& data)
|
|||||||
buffer = QByteArray();
|
buffer = QByteArray();
|
||||||
}
|
}
|
||||||
buffer.append(data);
|
buffer.append(data);
|
||||||
QSaveFile file(filename);
|
PSaveFile file(filename);
|
||||||
if (!file.open(QSaveFile::WriteOnly)) {
|
if (!file.open(PSaveFile::WriteOnly)) {
|
||||||
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString());
|
||||||
}
|
}
|
||||||
if (buffer.size() != file.write(buffer)) {
|
if (buffer.size() != file.write(buffer)) {
|
||||||
@ -971,8 +971,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " \"" + args.join("\" \"") + "\"";
|
argstring = " \"" + args.join("\" \"") + "\"";
|
||||||
|
|
||||||
stream << "#!/bin/bash"
|
stream << "#!/bin/bash" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "\"" << target << "\" " << argstring << "\n";
|
stream << "\"" << target << "\" " << argstring << "\n";
|
||||||
|
|
||||||
stream.flush();
|
stream.flush();
|
||||||
@ -1016,12 +1015,9 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
|||||||
if (!args.empty())
|
if (!args.empty())
|
||||||
argstring = " '" + args.join("' '") + "'";
|
argstring = " '" + args.join("' '") + "'";
|
||||||
|
|
||||||
stream << "[Desktop Entry]"
|
stream << "[Desktop Entry]" << "\n";
|
||||||
<< "\n";
|
stream << "Type=Application" << "\n";
|
||||||
stream << "Type=Application"
|
stream << "Categories=Game;ActionGame;AdventureGame;Simulation" << "\n";
|
||||||
<< "\n";
|
|
||||||
stream << "Categories=Game;ActionGame;AdventureGame;Simulation"
|
|
||||||
<< "\n";
|
|
||||||
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
stream << "Exec=\"" << target.toLocal8Bit() << "\"" << argstring.toLocal8Bit() << "\n";
|
||||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||||
if (!icon.isEmpty()) {
|
if (!icon.isEmpty()) {
|
||||||
|
@ -38,22 +38,29 @@ void InstanceCreationTask::executeTask()
|
|||||||
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
|
// files scheduled to, and we'd better not let the user abort in the middle of it, since it'd
|
||||||
// put the instance in an invalid state.
|
// put the instance in an invalid state.
|
||||||
if (shouldOverride()) {
|
if (shouldOverride()) {
|
||||||
|
bool deleteFailed = false;
|
||||||
|
|
||||||
setAbortable(false);
|
setAbortable(false);
|
||||||
setStatus(tr("Removing old conflicting files..."));
|
setStatus(tr("Removing old conflicting files..."));
|
||||||
qDebug() << "Removing old files";
|
qDebug() << "Removing old files";
|
||||||
|
|
||||||
for (auto path : m_files_to_remove) {
|
for (const QString& path : m_files_to_remove) {
|
||||||
if (!QFile::exists(path))
|
if (!QFile::exists(path))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
qDebug() << "Removing" << path;
|
qDebug() << "Removing" << path;
|
||||||
if (!FS::deletePath(path)) {
|
|
||||||
qCritical() << "Couldn't remove the old conflicting files.";
|
if (!QFile::remove(path)) {
|
||||||
emitFailed(tr("Failed to remove old conflicting files."));
|
qCritical() << "Could not remove" << path;
|
||||||
return;
|
deleteFailed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deleteFailed) {
|
||||||
|
emitFailed(tr("Failed to remove old conflicting files."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,16 @@ if [ "x$DEPS_LIST" = "x" ]; then
|
|||||||
# Just to be sure...
|
# Just to be sure...
|
||||||
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
chmod +x "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}"
|
||||||
|
|
||||||
|
ARGS=("${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}")
|
||||||
|
|
||||||
|
if [ -f portable.txt ]; then
|
||||||
|
ARGS+=("-d" "${LAUNCHER_DIR}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARGS+=("$@")
|
||||||
|
|
||||||
# Run the launcher
|
# Run the launcher
|
||||||
exec -a "${LAUNCHER_DIR}/${LAUNCHER_NAME}" "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
exec -a "${ARGS[@]}"
|
||||||
|
|
||||||
# Run the launcher in valgrind
|
# Run the launcher in valgrind
|
||||||
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
# valgrind --log-file="valgrind.log" --leak-check=full --track-origins=yes "${LAUNCHER_DIR}/bin/${LAUNCHER_NAME}" -d "${LAUNCHER_DIR}" "$@"
|
||||||
|
@ -39,7 +39,8 @@
|
|||||||
#include <QTextDecoder>
|
#include <QTextDecoder>
|
||||||
#include "MessageLevel.h"
|
#include "MessageLevel.h"
|
||||||
|
|
||||||
LoggedProcess::LoggedProcess(QObject* parent) : QProcess(parent)
|
LoggedProcess::LoggedProcess(const QTextCodec* output_codec, QObject* parent)
|
||||||
|
: QProcess(parent), m_err_decoder(output_codec), m_out_decoder(output_codec)
|
||||||
{
|
{
|
||||||
// QProcess has a strange interface... let's map a lot of those into a few.
|
// QProcess has a strange interface... let's map a lot of those into a few.
|
||||||
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
||||||
|
@ -49,7 +49,7 @@ class LoggedProcess : public QProcess {
|
|||||||
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
|
enum State { NotRunning, Starting, FailedToStart, Running, Finished, Crashed, Aborted };
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LoggedProcess(QObject* parent = 0);
|
explicit LoggedProcess(const QTextCodec* output_codec = QTextCodec::codecForLocale(), QObject* parent = 0);
|
||||||
virtual ~LoggedProcess();
|
virtual ~LoggedProcess();
|
||||||
|
|
||||||
State state() const;
|
State state() const;
|
||||||
@ -80,8 +80,8 @@ class LoggedProcess : public QProcess {
|
|||||||
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
|
QStringList reprocess(const QByteArray& data, QTextDecoder& decoder);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
QTextDecoder m_err_decoder;
|
||||||
QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale());
|
QTextDecoder m_out_decoder;
|
||||||
QString m_leftover_line;
|
QString m_leftover_line;
|
||||||
bool m_killed = false;
|
bool m_killed = false;
|
||||||
State m_state = NotRunning;
|
State m_state = NotRunning;
|
||||||
|
@ -101,7 +101,7 @@ class PixmapCache final : public QObject {
|
|||||||
*/
|
*/
|
||||||
bool _markCacheMissByEviciton()
|
bool _markCacheMissByEviciton()
|
||||||
{
|
{
|
||||||
static constexpr uint maxInt = static_cast<uint>(std::numeric_limits<int>::max());
|
static constexpr uint maxCache = static_cast<uint>(std::numeric_limits<int>::max()) / 4;
|
||||||
static constexpr uint step = 10240;
|
static constexpr uint step = 10240;
|
||||||
static constexpr int oneSecond = 1000;
|
static constexpr int oneSecond = 1000;
|
||||||
|
|
||||||
@ -118,8 +118,8 @@ class PixmapCache final : public QObject {
|
|||||||
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
||||||
// increase the cache size
|
// increase the cache size
|
||||||
uint newSize = _cacheLimit() + step;
|
uint newSize = _cacheLimit() + step;
|
||||||
if (newSize >= maxInt) { // increase it until you overflow :D
|
if (newSize >= maxCache) { // increase it until you overflow :D
|
||||||
newSize = maxInt;
|
newSize = maxCache;
|
||||||
qDebug() << m_consecutive_fast_evicitons
|
qDebug() << m_consecutive_fast_evicitons
|
||||||
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
|
<< tr("pixmap cache misses by eviction happened too fast, doing nothing as the cache size reached it's limit");
|
||||||
} else {
|
} else {
|
||||||
|
71
launcher/PSaveFile.h
Normal file
71
launcher/PSaveFile.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (c) 2023-2024 Trial97 <alexandru.tripon97@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QSaveFile>
|
||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
|
|
||||||
|
/* PSaveFile
|
||||||
|
* A class that mimics QSaveFile for Windows.
|
||||||
|
*
|
||||||
|
* When reading resources, we need to avoid accessing temporary files
|
||||||
|
* generated by QSaveFile. If we start reading such a file, we may
|
||||||
|
* inadvertently keep it open while QSaveFile is trying to remove it,
|
||||||
|
* or we might detect the file just before it is removed, leading to
|
||||||
|
* race conditions and errors.
|
||||||
|
*
|
||||||
|
* Unfortunately, QSaveFile doesn't provide a way to retrieve the
|
||||||
|
* temporary file name or to set a specific template for the temporary
|
||||||
|
* file name it uses. By default, QSaveFile appends a `.XXXXXX` suffix
|
||||||
|
* to the original file name, where the `XXXXXX` part is dynamically
|
||||||
|
* generated to ensure uniqueness.
|
||||||
|
*
|
||||||
|
* This class acts like a lock by adding and removing the target file
|
||||||
|
* name into/from a global string set, helping to manage access to
|
||||||
|
* files during critical operations.
|
||||||
|
*
|
||||||
|
* Note: Please do not use the `setFileName` function directly, as it
|
||||||
|
* is not virtual and cannot be overridden.
|
||||||
|
*/
|
||||||
|
class PSaveFile : public QSaveFile {
|
||||||
|
public:
|
||||||
|
PSaveFile(const QString& name) : QSaveFile(name) { addPath(name); }
|
||||||
|
PSaveFile(const QString& name, QObject* parent) : QSaveFile(name, parent) { addPath(name); }
|
||||||
|
virtual ~PSaveFile()
|
||||||
|
{
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->removeQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void addPath(const QString& path)
|
||||||
|
{
|
||||||
|
m_absoluteFilePath = QFileInfo(path).absoluteFilePath() + "."; // add dot for tmp files only
|
||||||
|
if (auto app = APPLICATION_DYN) {
|
||||||
|
app->addQSavePath(m_absoluteFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QString m_absoluteFilePath;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#define PSaveFile QSaveFile
|
||||||
|
#endif
|
@ -123,8 +123,7 @@ QDebug operator<<(QDebug debug, const Version& v)
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.nospace() << " ]"
|
debug.nospace() << " ]" << " }";
|
||||||
<< " }";
|
|
||||||
|
|
||||||
return debug;
|
return debug;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,12 @@ bool JavaVersion::requiresPermGen() const
|
|||||||
return !m_parseable || m_major < 8;
|
return !m_parseable || m_major < 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool JavaVersion::defaultsToUtf8() const
|
||||||
|
{
|
||||||
|
// starting from Java 18, UTF-8 is the default charset: https://openjdk.org/jeps/400
|
||||||
|
return m_parseable && m_major >= 18;
|
||||||
|
}
|
||||||
|
|
||||||
bool JavaVersion::isModular() const
|
bool JavaVersion::isModular() const
|
||||||
{
|
{
|
||||||
return m_parseable && m_major >= 9;
|
return m_parseable && m_major >= 9;
|
||||||
|
@ -25,7 +25,7 @@ class JavaVersion {
|
|||||||
bool operator>(const JavaVersion& rhs);
|
bool operator>(const JavaVersion& rhs);
|
||||||
|
|
||||||
bool requiresPermGen() const;
|
bool requiresPermGen() const;
|
||||||
|
bool defaultsToUtf8() const;
|
||||||
bool isModular() const;
|
bool isModular() const;
|
||||||
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
@ -51,14 +51,14 @@ void LaunchTask::init()
|
|||||||
m_instance->setRunning(true);
|
m_instance->setRunning(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
shared_qobject_ptr<LaunchTask> LaunchTask::create(MinecraftInstancePtr inst)
|
||||||
{
|
{
|
||||||
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
|
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||||
proc->init();
|
proc->init();
|
||||||
return proc;
|
return proc;
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchTask::LaunchTask(InstancePtr instance) : m_instance(instance) {}
|
LaunchTask::LaunchTask(MinecraftInstancePtr instance) : m_instance(instance) {}
|
||||||
|
|
||||||
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
|
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
|
||||||
{
|
{
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QObjectPtr.h>
|
#include <QObjectPtr.h>
|
||||||
|
#include <minecraft/MinecraftInstance.h>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include "BaseInstance.h"
|
#include "BaseInstance.h"
|
||||||
#include "LaunchStep.h"
|
#include "LaunchStep.h"
|
||||||
@ -46,21 +47,21 @@
|
|||||||
class LaunchTask : public Task {
|
class LaunchTask : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit LaunchTask(InstancePtr instance);
|
explicit LaunchTask(MinecraftInstancePtr instance);
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished };
|
enum State { NotStarted, Running, Waiting, Failed, Aborted, Finished };
|
||||||
|
|
||||||
public: /* methods */
|
public: /* methods */
|
||||||
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
static shared_qobject_ptr<LaunchTask> create(MinecraftInstancePtr inst);
|
||||||
virtual ~LaunchTask() = default;
|
virtual ~LaunchTask() = default;
|
||||||
|
|
||||||
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
||||||
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
||||||
void setCensorFilter(QMap<QString, QString> filter);
|
void setCensorFilter(QMap<QString, QString> filter);
|
||||||
|
|
||||||
InstancePtr instance() { return m_instance; }
|
MinecraftInstancePtr instance() { return m_instance; }
|
||||||
|
|
||||||
void setPid(qint64 pid) { m_pid = pid; }
|
void setPid(qint64 pid) { m_pid = pid; }
|
||||||
|
|
||||||
@ -115,7 +116,7 @@ class LaunchTask : public Task {
|
|||||||
void finalizeSteps(bool successful, const QString& error);
|
void finalizeSteps(bool successful, const QString& error);
|
||||||
|
|
||||||
protected: /* data */
|
protected: /* data */
|
||||||
InstancePtr m_instance;
|
MinecraftInstancePtr m_instance;
|
||||||
shared_qobject_ptr<LogModel> m_logModel;
|
shared_qobject_ptr<LogModel> m_logModel;
|
||||||
QList<shared_qobject_ptr<LaunchStep>> m_steps;
|
QList<shared_qobject_ptr<LaunchStep>> m_steps;
|
||||||
QMap<QString, QString> m_censorFilter;
|
QMap<QString, QString> m_censorFilter;
|
||||||
|
@ -38,7 +38,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <FileSystem.h>
|
#include <FileSystem.h>
|
||||||
@ -57,6 +56,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
#include "PSaveFile.h"
|
||||||
|
|
||||||
using std::nullopt;
|
using std::nullopt;
|
||||||
using std::optional;
|
using std::optional;
|
||||||
@ -183,7 +183,7 @@ bool putLevelDatDataToFS(const QFileInfo& file, QByteArray& data)
|
|||||||
if (fullFilePath.isNull()) {
|
if (fullFilePath.isNull()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
QSaveFile f(fullFilePath);
|
PSaveFile f(fullFilePath);
|
||||||
if (!f.open(QIODevice::WriteOnly)) {
|
if (!f.open(QIODevice::WriteOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -180,6 +180,7 @@ bool parseMinecraftProfile(QByteArray& data, MinecraftProfile& output)
|
|||||||
if (!getString(skinObj.value("url"), skinOut.url)) {
|
if (!getString(skinObj.value("url"), skinOut.url)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
skinOut.url.replace("http://textures.minecraft.net", "https://textures.minecraft.net");
|
||||||
if (!getString(skinObj.value("variant"), skinOut.variant)) {
|
if (!getString(skinObj.value("variant"), skinOut.variant)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -221,9 +222,9 @@ namespace {
|
|||||||
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
// these skin URLs are for the MHF_Steve and MHF_Alex accounts (made by a Mojang employee)
|
||||||
// they are needed because the session server doesn't return skin urls for default skins
|
// they are needed because the session server doesn't return skin urls for default skins
|
||||||
static const QString SKIN_URL_STEVE =
|
static const QString SKIN_URL_STEVE =
|
||||||
"http://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
"https://textures.minecraft.net/texture/1a4af718455d4aab528e7a61f86fa25e6a369d1768dcb13f7df319a713eb810b";
|
||||||
static const QString SKIN_URL_ALEX =
|
static const QString SKIN_URL_ALEX =
|
||||||
"http://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
"https://textures.minecraft.net/texture/83cee5ca6afcdb171285aa00e8049c297b2dbeba0efb8ff970a5677a1b644032";
|
||||||
|
|
||||||
bool isDefaultModelSteve(QString uuid)
|
bool isDefaultModelSteve(QString uuid)
|
||||||
{
|
{
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
AutoInstallJava::AutoInstallJava(LaunchTask* parent)
|
||||||
: LaunchStep(parent)
|
: LaunchStep(parent)
|
||||||
, m_instance(std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance()))
|
, m_instance(m_parent->instance())
|
||||||
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
, m_supported_arch(SysInfo::getSupportedJavaArchitecture()) {};
|
||||||
|
|
||||||
void AutoInstallJava::executeTask()
|
void AutoInstallJava::executeTask()
|
||||||
|
@ -8,16 +8,15 @@ CreateGameFolders::CreateGameFolders(LaunchTask* parent) : LaunchStep(parent) {}
|
|||||||
void CreateGameFolders::executeTask()
|
void CreateGameFolders::executeTask()
|
||||||
{
|
{
|
||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
|
||||||
|
|
||||||
if (!FS::ensureFolderPathExists(minecraftInstance->gameRoot())) {
|
if (!FS::ensureFolderPathExists(instance->gameRoot())) {
|
||||||
emit logLine("Couldn't create the main game folder", MessageLevel::Error);
|
emit logLine("Couldn't create the main game folder", MessageLevel::Error);
|
||||||
emitFailed(tr("Couldn't create the main game folder"));
|
emitFailed(tr("Couldn't create the main game folder"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
|
// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
|
||||||
if (!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs"))) {
|
if (!FS::ensureFolderPathExists(FS::PathCombine(instance->gameRoot(), "server-resource-packs"))) {
|
||||||
emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error);
|
emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error);
|
||||||
}
|
}
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
|
@ -70,17 +70,16 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
|
|||||||
void ExtractNatives::executeTask()
|
void ExtractNatives::executeTask()
|
||||||
{
|
{
|
||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
auto toExtract = instance->getNativeJars();
|
||||||
auto toExtract = minecraftInstance->getNativeJars();
|
|
||||||
if (toExtract.isEmpty()) {
|
if (toExtract.isEmpty()) {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto settings = minecraftInstance->settings();
|
auto settings = instance->settings();
|
||||||
|
|
||||||
auto outputPath = minecraftInstance->getNativePath();
|
auto outputPath = instance->getNativePath();
|
||||||
FS::ensureFolderPathExists(outputPath);
|
FS::ensureFolderPathExists(outputPath);
|
||||||
auto javaVersion = minecraftInstance->getJavaVersion();
|
auto javaVersion = instance->getJavaVersion();
|
||||||
bool jniHackEnabled = javaVersion.major() >= 8;
|
bool jniHackEnabled = javaVersion.major() >= 8;
|
||||||
for (const auto& source : toExtract) {
|
for (const auto& source : toExtract) {
|
||||||
if (!unzipNatives(source, outputPath, jniHackEnabled)) {
|
if (!unzipNatives(source, outputPath, jniHackEnabled)) {
|
||||||
|
@ -48,18 +48,20 @@
|
|||||||
#include "gamemode_client.h"
|
#include "gamemode_client.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent)
|
LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent)
|
||||||
|
: LaunchStep(parent)
|
||||||
|
, m_process(parent->instance()->getJavaVersion().defaultsToUtf8() ? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale())
|
||||||
{
|
{
|
||||||
auto instance = parent->instance();
|
if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
|
||||||
if (instance->settings()->get("CloseAfterLaunch").toBool()) {
|
|
||||||
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
|
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
|
||||||
*connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, [[maybe_unused]] MessageLevel::Enum level) {
|
*connection =
|
||||||
qDebug() << lines;
|
connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
|
||||||
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
|
qDebug() << lines;
|
||||||
APPLICATION->closeAllWindows();
|
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
|
||||||
disconnect(*connection);
|
APPLICATION->closeAllWindows();
|
||||||
}
|
disconnect(*connection);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines);
|
connect(&m_process, &LoggedProcess::log, this, &LauncherPartLaunch::logLines);
|
||||||
@ -77,10 +79,9 @@ void LauncherPartLaunch::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
|
||||||
|
|
||||||
QString legacyJarPath;
|
QString legacyJarPath;
|
||||||
if (minecraftInstance->getLauncher() == "legacy" || minecraftInstance->shouldApplyOnlineFixes()) {
|
if (instance->getLauncher() == "legacy" || instance->shouldApplyOnlineFixes()) {
|
||||||
legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
|
legacyJarPath = APPLICATION->getJarPath("NewLaunchLegacy.jar");
|
||||||
if (legacyJarPath.isEmpty()) {
|
if (legacyJarPath.isEmpty()) {
|
||||||
const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
|
const char* reason = QT_TR_NOOP("Legacy launcher library could not be found. Please check your installation.");
|
||||||
@ -90,8 +91,8 @@ void LauncherPartLaunch::executeTask()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_launchScript = minecraftInstance->createLaunchScript(m_session, m_targetToJoin);
|
m_launchScript = instance->createLaunchScript(m_session, m_targetToJoin);
|
||||||
QStringList args = minecraftInstance->javaArguments();
|
QStringList args = instance->javaArguments();
|
||||||
QString allArgs = args.join(", ");
|
QString allArgs = args.join(", ");
|
||||||
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
|
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::Launcher);
|
||||||
|
|
||||||
@ -102,13 +103,13 @@ void LauncherPartLaunch::executeTask()
|
|||||||
// make detachable - this will keep the process running even if the object is destroyed
|
// make detachable - this will keep the process running even if the object is destroyed
|
||||||
m_process.setDetachable(true);
|
m_process.setDetachable(true);
|
||||||
|
|
||||||
auto classPath = minecraftInstance->getClassPath();
|
auto classPath = instance->getClassPath();
|
||||||
classPath.prepend(jarPath);
|
classPath.prepend(jarPath);
|
||||||
|
|
||||||
if (!legacyJarPath.isEmpty())
|
if (!legacyJarPath.isEmpty())
|
||||||
classPath.prepend(legacyJarPath);
|
classPath.prepend(legacyJarPath);
|
||||||
|
|
||||||
auto natPath = minecraftInstance->getNativePath();
|
auto natPath = instance->getNativePath();
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
natPath = FS::getPathNameInLocal8bit(natPath);
|
natPath = FS::getPathNameInLocal8bit(natPath);
|
||||||
#endif
|
#endif
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
void ModMinecraftJar::executeTask()
|
void ModMinecraftJar::executeTask()
|
||||||
{
|
{
|
||||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
auto m_inst = m_parent->instance();
|
||||||
|
|
||||||
if (!m_inst->getJarMods().size()) {
|
if (!m_inst->getJarMods().size()) {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
@ -82,7 +82,7 @@ void ModMinecraftJar::finalize()
|
|||||||
|
|
||||||
bool ModMinecraftJar::removeJar()
|
bool ModMinecraftJar::removeJar()
|
||||||
{
|
{
|
||||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
auto m_inst = m_parent->instance();
|
||||||
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
|
auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
|
||||||
QFile finalJar(finalJarPath);
|
QFile finalJar(finalJarPath);
|
||||||
if (finalJar.exists()) {
|
if (finalJar.exists()) {
|
||||||
|
@ -22,12 +22,11 @@
|
|||||||
void ReconstructAssets::executeTask()
|
void ReconstructAssets::executeTask()
|
||||||
{
|
{
|
||||||
auto instance = m_parent->instance();
|
auto instance = m_parent->instance();
|
||||||
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
|
auto components = instance->getPackProfile();
|
||||||
auto components = minecraftInstance->getPackProfile();
|
|
||||||
auto profile = components->getProfile();
|
auto profile = components->getProfile();
|
||||||
auto assets = profile->getMinecraftAssets();
|
auto assets = profile->getMinecraftAssets();
|
||||||
|
|
||||||
if (!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir())) {
|
if (!AssetsUtils::reconstructAssets(assets->id, instance->resourcesDir())) {
|
||||||
emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error);
|
emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
void ScanModFolders::executeTask()
|
void ScanModFolders::executeTask()
|
||||||
{
|
{
|
||||||
auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
auto m_inst = m_parent->instance();
|
||||||
|
|
||||||
auto loaders = m_inst->loaderModList();
|
auto loaders = m_inst->loaderModList();
|
||||||
connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
|
connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
void VerifyJavaInstall::executeTask()
|
void VerifyJavaInstall::executeTask()
|
||||||
{
|
{
|
||||||
auto instance = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
|
auto instance = m_parent->instance();
|
||||||
auto packProfile = instance->getPackProfile();
|
auto packProfile = instance->getPackProfile();
|
||||||
auto settings = instance->settings();
|
auto settings = instance->settings();
|
||||||
auto storedVersion = settings->get("JavaVersion").toString();
|
auto storedVersion = settings->get("JavaVersion").toString();
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Mod.h"
|
#include "Mod.h"
|
||||||
|
#include <qpixmap.h>
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
@ -210,7 +211,7 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
|
|||||||
|
|
||||||
m_local_details = std::move(details);
|
m_local_details = std::move(details);
|
||||||
if (!iconPath().isEmpty()) {
|
if (!iconPath().isEmpty()) {
|
||||||
m_pack_image_cache_key.was_read_attempt = false;
|
m_packImageCacheKey.wasReadAttempt = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,45 +225,53 @@ auto Mod::issueTracker() const -> QString
|
|||||||
return details().issue_tracker;
|
return details().issue_tracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mod::setIcon(QImage new_image) const
|
QPixmap Mod::setIcon(QImage new_image) const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_data_lock);
|
QMutexLocker locker(&m_data_lock);
|
||||||
|
|
||||||
Q_ASSERT(!new_image.isNull());
|
Q_ASSERT(!new_image.isNull());
|
||||||
|
|
||||||
if (m_pack_image_cache_key.key.isValid())
|
if (m_packImageCacheKey.key.isValid())
|
||||||
PixmapCache::remove(m_pack_image_cache_key.key);
|
PixmapCache::remove(m_packImageCacheKey.key);
|
||||||
|
|
||||||
// scale the image to avoid flooding the pixmapcache
|
// scale the image to avoid flooding the pixmapcache
|
||||||
auto pixmap =
|
auto pixmap =
|
||||||
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
|
||||||
|
|
||||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
m_packImageCacheKey.key = PixmapCache::insert(pixmap);
|
||||||
m_pack_image_cache_key.was_ever_used = true;
|
m_packImageCacheKey.wasEverUsed = true;
|
||||||
m_pack_image_cache_key.was_read_attempt = true;
|
m_packImageCacheKey.wasReadAttempt = true;
|
||||||
|
return pixmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
||||||
{
|
{
|
||||||
QPixmap cached_image;
|
auto pixmap_transform = [&size, &mode](QPixmap pixmap) {
|
||||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
|
||||||
if (size.isNull())
|
if (size.isNull())
|
||||||
return cached_image;
|
return pixmap;
|
||||||
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
|
return pixmap.scaled(size, mode, Qt::SmoothTransformation);
|
||||||
|
};
|
||||||
|
|
||||||
|
QPixmap cached_image;
|
||||||
|
if (PixmapCache::find(m_packImageCacheKey.key, &cached_image)) {
|
||||||
|
return pixmap_transform(cached_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No valid image we can get
|
// No valid image we can get
|
||||||
if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
|
if ((!m_packImageCacheKey.wasEverUsed && m_packImageCacheKey.wasReadAttempt) || iconPath().isEmpty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
if (m_pack_image_cache_key.was_ever_used) {
|
if (m_packImageCacheKey.wasEverUsed) {
|
||||||
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
|
qDebug() << "Mod" << name() << "Had it's icon evicted from the cache. reloading...";
|
||||||
PixmapCache::markCacheMissByEviciton();
|
PixmapCache::markCacheMissByEviciton();
|
||||||
}
|
}
|
||||||
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||||
m_pack_image_cache_key.was_read_attempt = true;
|
m_packImageCacheKey.wasReadAttempt = true;
|
||||||
ModUtils::loadIconFile(*this);
|
if (ModUtils::loadIconFile(*this, &cached_image)) {
|
||||||
return icon(size);
|
return pixmap_transform(cached_image);
|
||||||
|
}
|
||||||
|
// Image failed to load
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::valid() const
|
bool Mod::valid() const
|
||||||
|
@ -77,7 +77,7 @@ class Mod : public Resource {
|
|||||||
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
|
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
|
||||||
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||||
/** Thread-safe. */
|
/** Thread-safe. */
|
||||||
void setIcon(QImage new_image) const;
|
QPixmap setIcon(QImage new_image) const;
|
||||||
|
|
||||||
void setDetails(const ModDetails& details);
|
void setDetails(const ModDetails& details);
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ class Mod : public Resource {
|
|||||||
|
|
||||||
struct {
|
struct {
|
||||||
QPixmapCache::Key key;
|
QPixmapCache::Key key;
|
||||||
bool was_ever_used = false;
|
bool wasEverUsed = false;
|
||||||
bool was_read_attempt = false;
|
bool wasReadAttempt = false;
|
||||||
} mutable m_pack_image_cache_key;
|
} mutable m_packImageCacheKey;
|
||||||
};
|
};
|
||||||
|
@ -40,10 +40,9 @@ ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance
|
|||||||
|
|
||||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
|
||||||
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
|
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
|
||||||
#ifndef LAUNCHER_TEST
|
if (APPLICATION_DYN) { // in tests the application macro doesn't work
|
||||||
// in tests the application macro doesn't work
|
m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||||
m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceFolderModel::~ResourceFolderModel()
|
ResourceFolderModel::~ResourceFolderModel()
|
||||||
|
85
launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
Normal file
85
launcher/minecraft/mod/tasks/BasicFolderLoadTask.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "minecraft/mod/Resource.h"
|
||||||
|
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
/** Very simple task that just loads a folder's contents directly.
|
||||||
|
*/
|
||||||
|
class BasicFolderLoadTask : public Task {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
struct Result {
|
||||||
|
QMap<QString, Resource::Ptr> resources;
|
||||||
|
};
|
||||||
|
using ResultPtr = std::shared_ptr<Result>;
|
||||||
|
|
||||||
|
[[nodiscard]] ResultPtr result() const { return m_result; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
|
||||||
|
{
|
||||||
|
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
|
||||||
|
}
|
||||||
|
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
|
||||||
|
: Task(nullptr, false)
|
||||||
|
, m_dir(dir)
|
||||||
|
, m_result(new Result)
|
||||||
|
, m_create_func(std::move(create_function))
|
||||||
|
, m_thread_to_spawn_into(thread())
|
||||||
|
{}
|
||||||
|
|
||||||
|
[[nodiscard]] bool canAbort() const override { return true; }
|
||||||
|
bool abort() override
|
||||||
|
{
|
||||||
|
m_aborted.store(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void executeTask() override
|
||||||
|
{
|
||||||
|
if (thread() != m_thread_to_spawn_into)
|
||||||
|
connect(this, &Task::finished, this->thread(), &QThread::quit);
|
||||||
|
|
||||||
|
m_dir.refresh();
|
||||||
|
for (auto entry : m_dir.entryInfoList()) {
|
||||||
|
auto filePath = entry.absoluteFilePath();
|
||||||
|
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||||
|
if (newFilePath != filePath) {
|
||||||
|
FS::move(filePath, newFilePath);
|
||||||
|
entry = QFileInfo(newFilePath);
|
||||||
|
}
|
||||||
|
auto resource = m_create_func(entry);
|
||||||
|
resource->moveToThread(m_thread_to_spawn_into);
|
||||||
|
m_result->resources.insert(resource->internal_id(), resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_aborted)
|
||||||
|
emit finished();
|
||||||
|
else
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QDir m_dir;
|
||||||
|
ResultPtr m_result;
|
||||||
|
|
||||||
|
std::atomic<bool> m_aborted = false;
|
||||||
|
|
||||||
|
std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
|
||||||
|
|
||||||
|
/** This is the thread in which we should put new mod objects */
|
||||||
|
QThread* m_thread_to_spawn_into;
|
||||||
|
};
|
@ -647,11 +647,11 @@ bool validate(QFileInfo file)
|
|||||||
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap)
|
||||||
{
|
{
|
||||||
auto img = QImage::fromData(raw_data);
|
auto img = QImage::fromData(raw_data);
|
||||||
if (!img.isNull()) {
|
if (!img.isNull()) {
|
||||||
mod.setIcon(img);
|
*pixmap = mod.setIcon(img);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
|
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
|
||||||
return false;
|
return false;
|
||||||
@ -659,15 +659,15 @@ bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadIconFile(const Mod& mod)
|
bool loadIconFile(const Mod& mod, QPixmap* pixmap)
|
||||||
{
|
{
|
||||||
if (mod.iconPath().isEmpty()) {
|
if (mod.iconPath().isEmpty()) {
|
||||||
qWarning() << "No Iconfile set, be sure to parse the mod first";
|
qWarning() << "No Iconfile set, be sure to parse the mod first";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto png_invalid = [&mod]() {
|
auto png_invalid = [&mod](const QString& reason) {
|
||||||
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
|
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon:" << reason;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -676,24 +676,26 @@ bool loadIconFile(const Mod& mod)
|
|||||||
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
|
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
|
||||||
if (icon_info.exists() && icon_info.isFile()) {
|
if (icon_info.exists() && icon_info.isFile()) {
|
||||||
QFile icon(icon_info.filePath());
|
QFile icon(icon_info.filePath());
|
||||||
if (!icon.open(QIODevice::ReadOnly))
|
if (!icon.open(QIODevice::ReadOnly)) {
|
||||||
return false;
|
return png_invalid("failed to open file " + icon_info.filePath());
|
||||||
|
}
|
||||||
auto data = icon.readAll();
|
auto data = icon.readAll();
|
||||||
|
|
||||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap);
|
||||||
|
|
||||||
icon.close();
|
icon.close();
|
||||||
|
|
||||||
if (!icon_result) {
|
if (!icon_result) {
|
||||||
return png_invalid(); // icon invalid
|
return png_invalid("invalid png image"); // icon invalid
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return png_invalid("file '" + icon_info.filePath() + "' does not exists or is not a file");
|
||||||
}
|
}
|
||||||
case ResourceType::ZIPFILE: {
|
case ResourceType::ZIPFILE: {
|
||||||
QuaZip zip(mod.fileinfo().filePath());
|
QuaZip zip(mod.fileinfo().filePath());
|
||||||
if (!zip.open(QuaZip::mdUnzip))
|
if (!zip.open(QuaZip::mdUnzip))
|
||||||
return false;
|
return png_invalid("failed to open '" + mod.fileinfo().filePath() + "' as a zip archive");
|
||||||
|
|
||||||
QuaZipFile file(&zip);
|
QuaZipFile file(&zip);
|
||||||
|
|
||||||
@ -701,28 +703,27 @@ bool loadIconFile(const Mod& mod)
|
|||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
qCritical() << "Failed to open file in zip.";
|
qCritical() << "Failed to open file in zip.";
|
||||||
zip.close();
|
zip.close();
|
||||||
return png_invalid();
|
return png_invalid("Failed to open '" + mod.iconPath() + "' in zip archive");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto data = file.readAll();
|
auto data = file.readAll();
|
||||||
|
|
||||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
bool icon_result = ModUtils::processIconPNG(mod, std::move(data), pixmap);
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
if (!icon_result) {
|
if (!icon_result) {
|
||||||
return png_invalid(); // icon png invalid
|
return png_invalid("invalid png image"); // icon png invalid
|
||||||
}
|
}
|
||||||
} else {
|
return true;
|
||||||
return png_invalid(); // could not set icon as current file.
|
|
||||||
}
|
}
|
||||||
return false;
|
return png_invalid("Failed to set '" + mod.iconPath() +
|
||||||
|
"' as current file in zip archive"); // could not set icon as current file.
|
||||||
}
|
}
|
||||||
case ResourceType::LITEMOD: {
|
case ResourceType::LITEMOD: {
|
||||||
return false; // can lightmods even have icons?
|
return png_invalid("litemods do not have icons"); // can lightmods even have icons?
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
qWarning() << "Invalid type for mod, can not load icon.";
|
return png_invalid("Invalid type for mod, can not load icon.");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
|
|||||||
/** Checks whether a file is valid as a mod or not. */
|
/** Checks whether a file is valid as a mod or not. */
|
||||||
bool validate(QFileInfo file);
|
bool validate(QFileInfo file);
|
||||||
|
|
||||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
|
bool processIconPNG(const Mod& mod, QByteArray&& raw_data, QPixmap* pixmap);
|
||||||
bool loadIconFile(const Mod& mod);
|
bool loadIconFile(const Mod& mod, QPixmap* pixmap);
|
||||||
} // namespace ModUtils
|
} // namespace ModUtils
|
||||||
|
|
||||||
class LocalModParseTask : public Task {
|
class LocalModParseTask : public Task {
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "ResourceFolderLoadTask.h"
|
#include "ResourceFolderLoadTask.h"
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "minecraft/mod/MetadataHandler.h"
|
#include "minecraft/mod/MetadataHandler.h"
|
||||||
|
|
||||||
@ -70,6 +71,9 @@ void ResourceFolderLoadTask::executeTask()
|
|||||||
m_resource_dir.refresh();
|
m_resource_dir.refresh();
|
||||||
for (auto entry : m_resource_dir.entryInfoList()) {
|
for (auto entry : m_resource_dir.entryInfoList()) {
|
||||||
auto filePath = entry.absoluteFilePath();
|
auto filePath = entry.absoluteFilePath();
|
||||||
|
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||||
if (newFilePath != filePath) {
|
if (newFilePath != filePath) {
|
||||||
FS::move(filePath, newFilePath);
|
FS::move(filePath, newFilePath);
|
||||||
|
@ -36,7 +36,7 @@ Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
|
|||||||
|
|
||||||
enum class ResourceProvider { MODRINTH, FLAME };
|
enum class ResourceProvider { MODRINTH, FLAME };
|
||||||
|
|
||||||
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
|
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK, MODPACK };
|
||||||
|
|
||||||
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
||||||
|
|
||||||
|
@ -221,14 +221,20 @@ QList<ResourceAPI::SortingMethod> FlameAPI::getSortingMethods() const
|
|||||||
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
|
{ 8, "GameVersion", QObject::tr("Sort by Game Version") } };
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::Ptr FlameAPI::getModCategories(std::shared_ptr<QByteArray> response)
|
Task::Ptr FlameAPI::getCategories(std::shared_ptr<QByteArray> response, ModPlatform::ResourceType type)
|
||||||
{
|
{
|
||||||
auto netJob = makeShared<NetJob>(QString("Flame::GetCategories"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::GetCategories"), APPLICATION->network());
|
||||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl("https://api.curseforge.com/v1/categories?gameId=432&classId=6"), response));
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(
|
||||||
|
QUrl(QString("https://api.curseforge.com/v1/categories?gameId=432&classId=%1").arg(getClassId(type))), response));
|
||||||
QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; });
|
QObject::connect(netJob.get(), &Task::failed, [](QString msg) { qDebug() << "Flame failed to get categories:" << msg; });
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task::Ptr FlameAPI::getModCategories(std::shared_ptr<QByteArray> response)
|
||||||
|
{
|
||||||
|
return getCategories(response, ModPlatform::ResourceType::MOD);
|
||||||
|
}
|
||||||
|
|
||||||
QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteArray> response)
|
QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteArray> response)
|
||||||
{
|
{
|
||||||
QList<ModPlatform::Category> categories;
|
QList<ModPlatform::Category> categories;
|
||||||
|
@ -25,6 +25,7 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
|
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
|
||||||
Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
|
Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
|
||||||
|
|
||||||
|
static Task::Ptr getCategories(std::shared_ptr<QByteArray> response, ModPlatform::ResourceType type);
|
||||||
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
|
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
|
||||||
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
|
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
|
||||||
|
|
||||||
@ -46,6 +47,8 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
return 12;
|
return 12;
|
||||||
case ModPlatform::ResourceType::SHADER_PACK:
|
case ModPlatform::ResourceType::SHADER_PACK:
|
||||||
return 6552;
|
return 6552;
|
||||||
|
case ModPlatform::ResourceType::MODPACK:
|
||||||
|
return 4471;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,12 +85,9 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
|
|
||||||
static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
|
static const QString getModLoaderFilters(ModPlatform::ModLoaderTypes types) { return "[" + getModLoaderStrings(types).join(',') + "]"; }
|
||||||
|
|
||||||
private:
|
public:
|
||||||
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
auto gameVersionStr =
|
|
||||||
args.versions.has_value() ? QString("gameVersion=%1").arg(args.versions.value().front().toString()) : QString();
|
|
||||||
|
|
||||||
QStringList get_arguments;
|
QStringList get_arguments;
|
||||||
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
|
get_arguments.append(QString("classId=%1").arg(getClassId(args.type)));
|
||||||
get_arguments.append(QString("index=%1").arg(args.offset));
|
get_arguments.append(QString("index=%1").arg(args.offset));
|
||||||
@ -97,20 +97,22 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
if (args.sorting.has_value())
|
if (args.sorting.has_value())
|
||||||
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
|
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
|
||||||
get_arguments.append("sortOrder=desc");
|
get_arguments.append("sortOrder=desc");
|
||||||
if (args.loaders.has_value())
|
if (args.loaders.has_value() && args.loaders.value() != 0)
|
||||||
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
|
get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
|
||||||
if (args.categoryIds.has_value() && !args.categoryIds->empty())
|
if (args.categoryIds.has_value() && !args.categoryIds->empty())
|
||||||
get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
|
get_arguments.append(QString("categoryIds=[%1]").arg(args.categoryIds->join(",")));
|
||||||
|
|
||||||
get_arguments.append(gameVersionStr);
|
if (args.versions.has_value() && !args.versions.value().empty())
|
||||||
|
get_arguments.append(QString("gameVersion=%1").arg(args.versions.value().front().toString()));
|
||||||
|
|
||||||
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
||||||
};
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
||||||
{
|
{
|
||||||
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
||||||
};
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
@ -125,7 +127,7 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||||
{
|
{
|
||||||
@ -137,5 +139,5 @@ class FlameAPI : public NetworkResourceAPI {
|
|||||||
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
url += QString("&modLoaderType=%1").arg(mappedModLoader);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj)
|
||||||
{
|
{
|
||||||
@ -88,8 +89,27 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto mcVer : versionArray) {
|
||||||
|
auto str = mcVer.toString();
|
||||||
|
|
||||||
|
if (str.contains('.'))
|
||||||
|
file.mcVersion.append(str);
|
||||||
|
|
||||||
|
if (auto loader = str.toLower(); loader == "neoforge")
|
||||||
|
file.loaders |= ModPlatform::NeoForge;
|
||||||
|
else if (loader == "forge")
|
||||||
|
file.loaders |= ModPlatform::Forge;
|
||||||
|
else if (loader == "cauldron")
|
||||||
|
file.loaders |= ModPlatform::Cauldron;
|
||||||
|
else if (loader == "liteloader")
|
||||||
|
file.loaders |= ModPlatform::LiteLoader;
|
||||||
|
else if (loader == "fabric")
|
||||||
|
file.loaders |= ModPlatform::Fabric;
|
||||||
|
else if (loader == "quilt")
|
||||||
|
file.loaders |= ModPlatform::Quilt;
|
||||||
|
}
|
||||||
|
|
||||||
// pick the latest version supported
|
// pick the latest version supported
|
||||||
file.mcVersion = versionArray[0].toString();
|
|
||||||
file.version = Json::requireString(version, "displayName");
|
file.version = Json::requireString(version, "displayName");
|
||||||
|
|
||||||
ModPlatform::IndexedVersionType::VersionType ver_type;
|
ModPlatform::IndexedVersionType::VersionType ver_type;
|
||||||
|
@ -18,6 +18,7 @@ struct IndexedVersion {
|
|||||||
int fileId;
|
int fileId;
|
||||||
QString version;
|
QString version;
|
||||||
ModPlatform::IndexedVersionType version_type;
|
ModPlatform::IndexedVersionType version_type;
|
||||||
|
ModPlatform::ModLoaderTypes loaders = {};
|
||||||
QString mcVersion;
|
QString mcVersion;
|
||||||
QString downloadUrl;
|
QString downloadUrl;
|
||||||
};
|
};
|
||||||
|
@ -129,7 +129,7 @@ Task::Ptr ModrinthAPI::getModCategories(std::shared_ptr<QByteArray> response)
|
|||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByteArray> response)
|
QList<ModPlatform::Category> ModrinthAPI::loadCategories(std::shared_ptr<QByteArray> response, QString projectType)
|
||||||
{
|
{
|
||||||
QList<ModPlatform::Category> categories;
|
QList<ModPlatform::Category> categories;
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
@ -147,7 +147,7 @@ QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByt
|
|||||||
for (auto val : arr) {
|
for (auto val : arr) {
|
||||||
auto cat = Json::requireObject(val);
|
auto cat = Json::requireObject(val);
|
||||||
auto name = Json::requireString(cat, "name");
|
auto name = Json::requireString(cat, "name");
|
||||||
if (Json::ensureString(cat, "project_type", "") == "mod")
|
if (Json::ensureString(cat, "project_type", "") == projectType)
|
||||||
categories.push_back({ name, name });
|
categories.push_back({ name, name });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,4 +157,9 @@ QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByt
|
|||||||
qDebug() << doc;
|
qDebug() << doc;
|
||||||
}
|
}
|
||||||
return categories;
|
return categories;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
QList<ModPlatform::Category> ModrinthAPI::loadModCategories(std::shared_ptr<QByteArray> response)
|
||||||
|
{
|
||||||
|
return loadCategories(response, "mod");
|
||||||
|
};
|
||||||
|
@ -31,6 +31,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
|
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
|
||||||
|
|
||||||
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
|
static Task::Ptr getModCategories(std::shared_ptr<QByteArray> response);
|
||||||
|
static QList<ModPlatform::Category> loadCategories(std::shared_ptr<QByteArray> response, QString projectType);
|
||||||
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
|
static QList<ModPlatform::Category> loadModCategories(std::shared_ptr<QByteArray> response);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -90,6 +91,8 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
return "resourcepack";
|
return "resourcepack";
|
||||||
case ModPlatform::ResourceType::SHADER_PACK:
|
case ModPlatform::ResourceType::SHADER_PACK:
|
||||||
return "shader";
|
return "shader";
|
||||||
|
case ModPlatform::ResourceType::MODPACK:
|
||||||
|
return "modpack";
|
||||||
default:
|
default:
|
||||||
qWarning() << "Invalid resource type for Modrinth API!";
|
qWarning() << "Invalid resource type for Modrinth API!";
|
||||||
break;
|
break;
|
||||||
@ -102,9 +105,9 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
{
|
{
|
||||||
QStringList facets_list;
|
QStringList facets_list;
|
||||||
|
|
||||||
if (args.loaders.has_value())
|
if (args.loaders.has_value() && args.loaders.value() != 0)
|
||||||
facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value())));
|
facets_list.append(QString("[%1]").arg(getModLoaderFilters(args.loaders.value())));
|
||||||
if (args.versions.has_value())
|
if (args.versions.has_value() && !args.versions.value().empty())
|
||||||
facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value())));
|
facets_list.append(QString("[%1]").arg(getGameVersionsArray(args.versions.value())));
|
||||||
if (args.side.has_value()) {
|
if (args.side.has_value()) {
|
||||||
auto side = getSideFilters(args.side.value());
|
auto side = getSideFilters(args.side.value());
|
||||||
@ -122,7 +125,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
public:
|
public:
|
||||||
[[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> override
|
[[nodiscard]] inline auto getSearchURL(SearchArgs const& args) const -> std::optional<QString> override
|
||||||
{
|
{
|
||||||
if (args.loaders.has_value()) {
|
if (args.loaders.has_value() && args.loaders.value() != 0) {
|
||||||
if (!validateModLoaders(args.loaders.value())) {
|
if (!validateModLoaders(args.loaders.value())) {
|
||||||
qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!";
|
qWarning() << "Modrinth - or our interface - does not support any the provided mod loaders!";
|
||||||
return {};
|
return {};
|
||||||
@ -163,7 +166,7 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||||||
.arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&'));
|
.arg(BuildConfig.MODRINTH_PROD_URL, args.pack.addonId.toString(), get_arguments.isEmpty() ? "" : "?", get_arguments.join('&'));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
|
QString getGameVersionsArray(std::list<Version> mcVersions) const
|
||||||
{
|
{
|
||||||
QString s;
|
QString s;
|
||||||
for (auto& ver : mcVersions) {
|
for (auto& ver : mcVersions) {
|
||||||
|
@ -136,7 +136,7 @@ bool ModrinthCreationTask::updateInstance()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder);
|
auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder);
|
||||||
for (const auto& entry : old_overrides) {
|
for (const auto& entry : old_client_overrides) {
|
||||||
if (entry.isEmpty())
|
if (entry.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
qDebug() << "Scheduling" << entry << "for removal";
|
qDebug() << "Scheduling" << entry << "for removal";
|
||||||
@ -302,6 +302,13 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
|
|
||||||
loop.exec();
|
loop.exec();
|
||||||
|
|
||||||
|
if (!ended_well) {
|
||||||
|
for (auto m : mods) {
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
return ended_well;
|
||||||
|
}
|
||||||
|
|
||||||
QEventLoop ensureMetaLoop;
|
QEventLoop ensureMetaLoop;
|
||||||
QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
|
QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
|
||||||
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
|
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
|
||||||
|
@ -135,6 +135,21 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion
|
|||||||
if (!gameVersions.isEmpty()) {
|
if (!gameVersions.isEmpty()) {
|
||||||
file.gameVersion = Json::ensureString(gameVersions[0]);
|
file.gameVersion = Json::ensureString(gameVersions[0]);
|
||||||
}
|
}
|
||||||
|
auto loaders = Json::requireArray(obj, "loaders");
|
||||||
|
for (auto loader : loaders) {
|
||||||
|
if (loader == "neoforge")
|
||||||
|
file.loaders |= ModPlatform::NeoForge;
|
||||||
|
else if (loader == "forge")
|
||||||
|
file.loaders |= ModPlatform::Forge;
|
||||||
|
else if (loader == "cauldron")
|
||||||
|
file.loaders |= ModPlatform::Cauldron;
|
||||||
|
else if (loader == "liteloader")
|
||||||
|
file.loaders |= ModPlatform::LiteLoader;
|
||||||
|
else if (loader == "fabric")
|
||||||
|
file.loaders |= ModPlatform::Fabric;
|
||||||
|
else if (loader == "quilt")
|
||||||
|
file.loaders |= ModPlatform::Quilt;
|
||||||
|
}
|
||||||
file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
|
file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
|
||||||
file.changelog = Json::ensureString(obj, "changelog");
|
file.changelog = Json::ensureString(obj, "changelog");
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ struct ModpackVersion {
|
|||||||
QString gameVersion;
|
QString gameVersion;
|
||||||
ModPlatform::IndexedVersionType version_type;
|
ModPlatform::IndexedVersionType version_type;
|
||||||
QString changelog;
|
QString changelog;
|
||||||
|
ModPlatform::ModLoaderTypes loaders = {};
|
||||||
|
|
||||||
QString id;
|
QString id;
|
||||||
QString project_id;
|
QString project_id;
|
||||||
|
@ -72,7 +72,7 @@ auto stringEntry(toml::table table, QString entry_name) -> QString
|
|||||||
{
|
{
|
||||||
auto node = table[StringUtils::toStdString(entry_name)];
|
auto node = table[StringUtils::toStdString(entry_name)];
|
||||||
if (!node) {
|
if (!node) {
|
||||||
qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata.";
|
qWarning() << "Failed to read str property '" + entry_name + "' in mod metadata.";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ auto intEntry(toml::table table, QString entry_name) -> int
|
|||||||
{
|
{
|
||||||
auto node = table[StringUtils::toStdString(entry_name)];
|
auto node = table[StringUtils::toStdString(entry_name)];
|
||||||
if (!node) {
|
if (!node) {
|
||||||
qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata.";
|
qWarning() << "Failed to read int property '" + entry_name + "' in mod metadata.";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ Task::State FileSink::init(QNetworkRequest& request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
wroteAnyData = false;
|
wroteAnyData = false;
|
||||||
m_output_file.reset(new QSaveFile(m_filename));
|
m_output_file.reset(new PSaveFile(m_filename));
|
||||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||||
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
|
@ -35,8 +35,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QSaveFile>
|
#include "PSaveFile.h"
|
||||||
|
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
@ -60,6 +59,6 @@ class FileSink : public Sink {
|
|||||||
protected:
|
protected:
|
||||||
QString m_filename;
|
QString m_filename;
|
||||||
bool wroteAnyData = false;
|
bool wroteAnyData = false;
|
||||||
std::unique_ptr<QSaveFile> m_output_file;
|
std::unique_ptr<PSaveFile> m_output_file;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
@ -48,7 +48,7 @@ NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> netwo
|
|||||||
: ConcurrentTask(nullptr, job_name), m_network(network)
|
: ConcurrentTask(nullptr, job_name), m_network(network)
|
||||||
{
|
{
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
if (max_concurrent < 0)
|
if (APPLICATION_DYN && max_concurrent < 0)
|
||||||
max_concurrent = APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt();
|
max_concurrent = APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt();
|
||||||
#endif
|
#endif
|
||||||
if (max_concurrent > 0)
|
if (max_concurrent > 0)
|
||||||
@ -161,7 +161,8 @@ bool NetJob::isOnline()
|
|||||||
void NetJob::emitFailed(QString reason)
|
void NetJob::emitFailed(QString reason)
|
||||||
{
|
{
|
||||||
#if defined(LAUNCHER_APPLICATION)
|
#if defined(LAUNCHER_APPLICATION)
|
||||||
if (m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) {
|
|
||||||
|
if (APPLICATION_DYN && m_ask_retry && m_manual_try < APPLICATION->settings()->get("NumberOfManualRetries").toInt() && isOnline()) {
|
||||||
m_manual_try++;
|
m_manual_try++;
|
||||||
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
|
auto response = CustomMessageBox::selectable(nullptr, "Confirm retry",
|
||||||
"The tasks failed.\n"
|
"The tasks failed.\n"
|
||||||
|
@ -39,7 +39,6 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
@ -51,7 +51,6 @@
|
|||||||
#include <icons/IconList.h>
|
#include <icons/IconList.h>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QSaveFile>
|
|
||||||
#include <QSortFilterProxyModel>
|
#include <QSortFilterProxyModel>
|
||||||
#include <QStack>
|
#include <QStack>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -78,7 +78,7 @@ int MSALoginDialog::exec()
|
|||||||
connect(m_authflow_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
connect(m_authflow_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||||
connect(m_authflow_task.get(), &Task::succeeded, this, &QDialog::accept);
|
connect(m_authflow_task.get(), &Task::succeeded, this, &QDialog::accept);
|
||||||
connect(m_authflow_task.get(), &Task::aborted, this, &MSALoginDialog::reject);
|
connect(m_authflow_task.get(), &Task::aborted, this, &MSALoginDialog::reject);
|
||||||
connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
connect(m_authflow_task.get(), &Task::status, this, &MSALoginDialog::onAuthFlowStatus);
|
||||||
connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
||||||
connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
connect(m_authflow_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
||||||
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_authflow_task.get(), &Task::abort);
|
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_authflow_task.get(), &Task::abort);
|
||||||
@ -87,7 +87,7 @@ int MSALoginDialog::exec()
|
|||||||
connect(m_devicecode_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
connect(m_devicecode_task.get(), &Task::failed, this, &MSALoginDialog::onTaskFailed);
|
||||||
connect(m_devicecode_task.get(), &Task::succeeded, this, &QDialog::accept);
|
connect(m_devicecode_task.get(), &Task::succeeded, this, &QDialog::accept);
|
||||||
connect(m_devicecode_task.get(), &Task::aborted, this, &MSALoginDialog::reject);
|
connect(m_devicecode_task.get(), &Task::aborted, this, &MSALoginDialog::reject);
|
||||||
connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onTaskStatus);
|
connect(m_devicecode_task.get(), &Task::status, this, &MSALoginDialog::onDeviceFlowStatus);
|
||||||
connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowser, this, &MSALoginDialog::authorizeWithBrowser);
|
||||||
connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
connect(m_devicecode_task.get(), &AuthFlow::authorizeWithBrowserWithExtra, this, &MSALoginDialog::authorizeWithBrowserWithExtra);
|
||||||
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_devicecode_task.get(), &Task::abort);
|
connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, m_devicecode_task.get(), &Task::abort);
|
||||||
@ -132,7 +132,7 @@ void MSALoginDialog::onTaskFailed(QString reason)
|
|||||||
|
|
||||||
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
void MSALoginDialog::authorizeWithBrowser(const QUrl& url)
|
||||||
{
|
{
|
||||||
ui->stackedWidget->setCurrentIndex(1);
|
ui->stackedWidget2->setCurrentIndex(1);
|
||||||
ui->loginButton->setToolTip(QString("<div style='width: 200px;'>%1</div>").arg(url.toString()));
|
ui->loginButton->setToolTip(QString("<div style='width: 200px;'>%1</div>").arg(url.toString()));
|
||||||
m_url = url;
|
m_url = url;
|
||||||
}
|
}
|
||||||
@ -152,12 +152,18 @@ void MSALoginDialog::authorizeWithBrowserWithExtra(QString url, QString code, in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSALoginDialog::onTaskStatus(QString status)
|
void MSALoginDialog::onDeviceFlowStatus(QString status)
|
||||||
{
|
{
|
||||||
ui->stackedWidget->setCurrentIndex(0);
|
ui->stackedWidget->setCurrentIndex(0);
|
||||||
ui->status->setText(status);
|
ui->status->setText(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MSALoginDialog::onAuthFlowStatus(QString status)
|
||||||
|
{
|
||||||
|
ui->stackedWidget2->setCurrentIndex(0);
|
||||||
|
ui->status2->setText(status);
|
||||||
|
}
|
||||||
|
|
||||||
// Public interface
|
// Public interface
|
||||||
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent)
|
MinecraftAccountPtr MSALoginDialog::newAccount(QWidget* parent)
|
||||||
{
|
{
|
||||||
|
@ -40,7 +40,8 @@ class MSALoginDialog : public QDialog {
|
|||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onTaskFailed(QString reason);
|
void onTaskFailed(QString reason);
|
||||||
void onTaskStatus(QString status);
|
void onDeviceFlowStatus(QString status);
|
||||||
|
void onAuthFlowStatus(QString status);
|
||||||
void authorizeWithBrowser(const QUrl& url);
|
void authorizeWithBrowser(const QUrl& url);
|
||||||
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
void authorizeWithBrowserWithExtra(QString url, QString code, int expiresIn);
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>440</width>
|
<width>440</width>
|
||||||
<height>430</height>
|
<height>447</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
@ -20,6 +20,171 @@
|
|||||||
<string>Add Microsoft Account</string>
|
<string>Add Microsoft Account</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget2">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="loadingPage2">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_31">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="loadingLabel2">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>16</pointsize>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Please wait...</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="status2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Status</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_31">
|
||||||
|
<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>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="mpPage1">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_21" stretch="0">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="loginButton">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>250</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Sign in with Microsoft</string>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line_3">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="orLabel">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>16</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Or</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line_4">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QStackedWidget" name="stackedWidget">
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
@ -28,7 +193,7 @@
|
|||||||
<widget class="QWidget" name="loadingPage">
|
<widget class="QWidget" name="loadingPage">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_4">
|
<spacer name="verticalSpacer_41">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -89,98 +254,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="mpPage">
|
<widget class="QWidget" name="mpPage">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,0">
|
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0">
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_5">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="loginButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>250</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Sign in with Microsoft</string>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_6">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="line_3">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="orLabel">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<pointsize>16</pointsize>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Or</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="line_4">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -90,6 +90,9 @@ void ProgressDialog::on_skipButton_clicked(bool checked)
|
|||||||
|
|
||||||
ProgressDialog::~ProgressDialog()
|
ProgressDialog::~ProgressDialog()
|
||||||
{
|
{
|
||||||
|
for (auto conn : this->m_taskConnections) {
|
||||||
|
disconnect(conn);
|
||||||
|
}
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,15 +143,15 @@ int ProgressDialog::execWithTask(Task* task)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect signals.
|
// Connect signals.
|
||||||
connect(task, &Task::started, this, &ProgressDialog::onTaskStarted);
|
this->m_taskConnections.push_back(connect(task, &Task::started, this, &ProgressDialog::onTaskStarted));
|
||||||
connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed);
|
this->m_taskConnections.push_back(connect(task, &Task::failed, this, &ProgressDialog::onTaskFailed));
|
||||||
connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded);
|
this->m_taskConnections.push_back(connect(task, &Task::succeeded, this, &ProgressDialog::onTaskSucceeded));
|
||||||
connect(task, &Task::status, this, &ProgressDialog::changeStatus);
|
this->m_taskConnections.push_back(connect(task, &Task::status, this, &ProgressDialog::changeStatus));
|
||||||
connect(task, &Task::details, this, &ProgressDialog::changeStatus);
|
this->m_taskConnections.push_back(connect(task, &Task::details, this, &ProgressDialog::changeStatus));
|
||||||
connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress);
|
this->m_taskConnections.push_back(connect(task, &Task::stepProgress, this, &ProgressDialog::changeStepProgress));
|
||||||
connect(task, &Task::progress, this, &ProgressDialog::changeProgress);
|
this->m_taskConnections.push_back(connect(task, &Task::progress, this, &ProgressDialog::changeProgress));
|
||||||
connect(task, &Task::aborted, this, &ProgressDialog::hide);
|
this->m_taskConnections.push_back(connect(task, &Task::aborted, this, &ProgressDialog::hide));
|
||||||
connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled);
|
this->m_taskConnections.push_back(connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled));
|
||||||
|
|
||||||
m_is_multi_step = task->isMultiStep();
|
m_is_multi_step = task->isMultiStep();
|
||||||
ui->taskProgressScrollArea->setHidden(!m_is_multi_step);
|
ui->taskProgressScrollArea->setHidden(!m_is_multi_step);
|
||||||
|
@ -93,6 +93,8 @@ class ProgressDialog : public QDialog {
|
|||||||
Ui::ProgressDialog* ui;
|
Ui::ProgressDialog* ui;
|
||||||
|
|
||||||
Task* m_task;
|
Task* m_task;
|
||||||
|
|
||||||
|
QList<QMetaObject::Connection> m_taskConnections;
|
||||||
|
|
||||||
bool m_is_multi_step = false;
|
bool m_is_multi_step = false;
|
||||||
QHash<QUuid, SubTaskProgressBar*> taskProgress;
|
QHash<QUuid, SubTaskProgressBar*> taskProgress;
|
||||||
|
@ -139,6 +139,9 @@ void SkinManageDialog::on_fileBtn_clicked()
|
|||||||
{
|
{
|
||||||
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString();
|
||||||
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter);
|
||||||
|
if (raw_path.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto message = m_list.installSkin(raw_path, {});
|
auto message = m_list.installSkin(raw_path, {});
|
||||||
if (!message.isEmpty()) {
|
if (!message.isEmpty()) {
|
||||||
CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show();
|
CustomMessageBox::selectable(this, tr("Selected file is not a valid skin"), message, QMessageBox::Critical)->show();
|
||||||
|
@ -104,18 +104,6 @@ bool checkSide(QString filter, QString value)
|
|||||||
return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value;
|
return filter.isEmpty() || value.isEmpty() || filter == "both" || value == "both" || filter == value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkMcVersions(std::list<Version> filter, QStringList value)
|
|
||||||
{
|
|
||||||
bool valid = false;
|
|
||||||
for (auto mcVersion : filter) {
|
|
||||||
if (value.contains(mcVersion.toString())) {
|
|
||||||
valid = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filter.empty() || valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack)
|
bool ModModel::checkFilters(ModPlatform::IndexedPack::Ptr pack)
|
||||||
{
|
{
|
||||||
if (!m_filter)
|
if (!m_filter)
|
||||||
@ -135,7 +123,7 @@ bool ModModel::checkVersionFilters(const ModPlatform::IndexedVersion& v)
|
|||||||
checkSide(m_filter->side, v.side) && // side
|
checkSide(m_filter->side, v.side) && // side
|
||||||
(m_filter->releases.empty() || // releases
|
(m_filter->releases.empty() || // releases
|
||||||
std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) &&
|
std::find(m_filter->releases.cbegin(), m_filter->releases.cend(), v.version_type) != m_filter->releases.cend()) &&
|
||||||
checkMcVersions(m_filter->versions, v.mcVersion)); // mcVersions
|
m_filter->checkMcVersions(v.mcVersion)); // mcVersions
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
@ -31,9 +31,9 @@ QHash<ResourceModel*, bool> ResourceModel::s_running_models;
|
|||||||
ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
|
ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
|
||||||
{
|
{
|
||||||
s_running_models.insert(this, true);
|
s_running_models.insert(this, true);
|
||||||
#ifndef LAUNCHER_TEST
|
if (APPLICATION_DYN) {
|
||||||
m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
|
||||||
#endif
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceModel::~ResourceModel()
|
ResourceModel::~ResourceModel()
|
||||||
@ -60,11 +60,15 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant
|
|||||||
return pack->description;
|
return pack->description;
|
||||||
}
|
}
|
||||||
case Qt::DecorationRole: {
|
case Qt::DecorationRole: {
|
||||||
if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
|
if (APPLICATION_DYN) {
|
||||||
icon_or_none.has_value())
|
if (auto icon_or_none = const_cast<ResourceModel*>(this)->getIcon(const_cast<QModelIndex&>(index), pack->logoUrl);
|
||||||
return icon_or_none.value();
|
icon_or_none.has_value())
|
||||||
|
return icon_or_none.value();
|
||||||
|
|
||||||
return APPLICATION->getThemedIcon("screenshot-placeholder");
|
return APPLICATION->getThemedIcon("screenshot-placeholder");
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case Qt::SizeHintRole:
|
case Qt::SizeHintRole:
|
||||||
return QSize(0, 58);
|
return QSize(0, 58);
|
||||||
|
@ -255,7 +255,10 @@ void ResourcePage::updateSelectionButton()
|
|||||||
|
|
||||||
m_ui->resourceSelectionButton->setEnabled(true);
|
m_ui->resourceSelectionButton->setEnabled(true);
|
||||||
if (auto current_pack = getCurrentPack(); current_pack) {
|
if (auto current_pack = getCurrentPack(); current_pack) {
|
||||||
if (!current_pack->isVersionSelected(m_selected_version_index))
|
if (current_pack->versionsLoaded && current_pack->versions.empty()) {
|
||||||
|
m_ui->resourceSelectionButton->setEnabled(false);
|
||||||
|
qWarning() << tr("No version available for the selected pack");
|
||||||
|
} else if (!current_pack->isVersionSelected(m_selected_version_index))
|
||||||
m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
|
m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString()));
|
||||||
else
|
else
|
||||||
m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
|
m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString()));
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include "FlameModel.h"
|
#include "FlameModel.h"
|
||||||
#include <Json.h>
|
#include <Json.h>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
#include "modplatform/ResourceAPI.h"
|
#include "modplatform/ResourceAPI.h"
|
||||||
#include "modplatform/flame/FlameAPI.h"
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
#include "ui/widgets/ProjectItem.h"
|
#include "ui/widgets/ProjectItem.h"
|
||||||
@ -183,34 +184,28 @@ void ListModel::performPaginatedSearch()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
|
ResourceAPI::SortingMethod sort{};
|
||||||
auto searchUrl = QString(
|
sort.index = currentSort + 1;
|
||||||
"https://api.curseforge.com/v1/mods/search?"
|
|
||||||
"gameId=432&"
|
|
||||||
"classId=4471&"
|
|
||||||
"index=%1&"
|
|
||||||
"pageSize=25&"
|
|
||||||
"searchFilter=%2&"
|
|
||||||
"sortField=%3&"
|
|
||||||
"sortOrder=desc")
|
|
||||||
.arg(nextSearchOffset)
|
|
||||||
.arg(currentSearchTerm)
|
|
||||||
.arg(currentSort + 1);
|
|
||||||
|
|
||||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
|
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
|
||||||
|
auto searchUrl = FlameAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort,
|
||||||
|
m_filter->loaders, m_filter->versions, "", m_filter->categoryIds });
|
||||||
|
|
||||||
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), response));
|
||||||
jobPtr = netJob;
|
jobPtr = netJob;
|
||||||
jobPtr->start();
|
jobPtr->start();
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
QObject::connect(netJob.get(), &NetJob::succeeded, this, &ListModel::searchRequestFinished);
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed);
|
QObject::connect(netJob.get(), &NetJob::failed, this, &ListModel::searchRequestFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListModel::searchWithTerm(const QString& term, int sort)
|
void ListModel::searchWithTerm(const QString& term, int sort, std::shared_ptr<ModFilterWidget::Filter> filter, bool filterChanged)
|
||||||
{
|
{
|
||||||
if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
|
if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort && !filterChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentSearchTerm = term;
|
currentSearchTerm = term;
|
||||||
currentSort = sort;
|
currentSort = sort;
|
||||||
|
m_filter = filter;
|
||||||
if (hasActiveSearchJob()) {
|
if (hasActiveSearchJob()) {
|
||||||
jobPtr->abort();
|
jobPtr->abort();
|
||||||
searchState = ResetRequested;
|
searchState = ResetRequested;
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include <net/NetJob.h>
|
#include <net/NetJob.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include "ui/widgets/ModFilterWidget.h"
|
||||||
|
|
||||||
#include <modplatform/flame/FlamePackIndex.h>
|
#include <modplatform/flame/FlamePackIndex.h>
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ class ListModel : public QAbstractListModel {
|
|||||||
void fetchMore(const QModelIndex& parent) override;
|
void fetchMore(const QModelIndex& parent) override;
|
||||||
|
|
||||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||||
void searchWithTerm(const QString& term, int sort);
|
void searchWithTerm(const QString& term, int sort, std::shared_ptr<ModFilterWidget::Filter> filter, bool filterChanged);
|
||||||
|
|
||||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||||
@ -65,6 +66,7 @@ class ListModel : public QAbstractListModel {
|
|||||||
|
|
||||||
QString currentSearchTerm;
|
QString currentSearchTerm;
|
||||||
int currentSort = 0;
|
int currentSort = 0;
|
||||||
|
std::shared_ptr<ModFilterWidget::Filter> m_filter;
|
||||||
int nextSearchOffset = 0;
|
int nextSearchOffset = 0;
|
||||||
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
||||||
Task::Ptr jobPtr;
|
Task::Ptr jobPtr;
|
||||||
|
@ -34,10 +34,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "FlamePage.h"
|
#include "FlamePage.h"
|
||||||
|
#include "Version.h"
|
||||||
|
#include "modplatform/flame/FlamePackIndex.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
|
#include "ui/widgets/ModFilterWidget.h"
|
||||||
#include "ui_FlamePage.h"
|
#include "ui_FlamePage.h"
|
||||||
|
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "FlameModel.h"
|
#include "FlameModel.h"
|
||||||
@ -88,6 +92,7 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
|
|||||||
|
|
||||||
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
|
||||||
ui->packDescription->setMetaEntry("FlamePacks");
|
ui->packDescription->setMetaEntry("FlamePacks");
|
||||||
|
createFilterWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
FlamePage::~FlamePage()
|
FlamePage::~FlamePage()
|
||||||
@ -131,10 +136,25 @@ void FlamePage::openedImpl()
|
|||||||
|
|
||||||
void FlamePage::triggerSearch()
|
void FlamePage::triggerSearch()
|
||||||
{
|
{
|
||||||
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||||
|
ui->packView->clearSelection();
|
||||||
|
ui->packDescription->clear();
|
||||||
|
ui->versionSelectionBox->clear();
|
||||||
|
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(),
|
||||||
|
m_filterWidget->changed());
|
||||||
m_fetch_progress.watch(listModel->activeSearchJob().get());
|
m_fetch_progress.watch(listModel->activeSearchJob().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkVersionFilters(const Flame::IndexedVersion& v, std::shared_ptr<ModFilterWidget::Filter> filter)
|
||||||
|
{
|
||||||
|
if (!filter)
|
||||||
|
return true;
|
||||||
|
return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders
|
||||||
|
(filter->releases.empty() || // releases
|
||||||
|
std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) &&
|
||||||
|
filter->checkMcVersions({ v.mcVersion })); // mcVersions}
|
||||||
|
}
|
||||||
|
|
||||||
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||||
{
|
{
|
||||||
ui->versionSelectionBox->clear();
|
ui->versionSelectionBox->clear();
|
||||||
@ -148,7 +168,7 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
|
|||||||
|
|
||||||
current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>();
|
current = listModel->data(curr, Qt::UserRole).value<Flame::IndexedPack>();
|
||||||
|
|
||||||
if (current.versionsLoaded == false) {
|
if (!current.versionsLoaded || m_filterWidget->changed()) {
|
||||||
qDebug() << "Loading flame modpack versions";
|
qDebug() << "Loading flame modpack versions";
|
||||||
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
auto netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
@ -176,6 +196,16 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
|
|||||||
qWarning() << "Error while reading flame modpack version: " << e.cause();
|
qWarning() << "Error while reading flame modpack version: " << e.cause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto pred = [this](const Flame::IndexedVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); };
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||||
|
current.versions.removeIf(pred);
|
||||||
|
#else
|
||||||
|
for (auto it = current.versions.begin(); it != current.versions.end();)
|
||||||
|
if (pred(*it))
|
||||||
|
it = current.versions.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#endif
|
||||||
for (auto version : current.versions) {
|
for (auto version : current.versions) {
|
||||||
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
||||||
auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion)
|
auto mcVersion = !version.mcVersion.isEmpty() && !version.version.contains(version.mcVersion)
|
||||||
@ -308,3 +338,25 @@ void FlamePage::setSearchTerm(QString term)
|
|||||||
{
|
{
|
||||||
ui->searchEdit->setText(term);
|
ui->searchEdit->setText(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlamePage::createFilterWidget()
|
||||||
|
{
|
||||||
|
auto widget = ModFilterWidget::create(nullptr, false, this);
|
||||||
|
m_filterWidget.swap(widget);
|
||||||
|
auto old = ui->splitter->replaceWidget(0, m_filterWidget.get());
|
||||||
|
// because we replaced the widget we also need to delete it
|
||||||
|
if (old) {
|
||||||
|
delete old;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); });
|
||||||
|
|
||||||
|
connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &FlamePage::triggerSearch);
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
m_categoriesTask = FlameAPI::getCategories(response, ModPlatform::ResourceType::MODPACK);
|
||||||
|
QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() {
|
||||||
|
auto categories = FlameAPI::loadModCategories(response);
|
||||||
|
m_filterWidget->setCategories(categories);
|
||||||
|
});
|
||||||
|
m_categoriesTask->start();
|
||||||
|
}
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include <modplatform/flame/FlamePackIndex.h>
|
#include <modplatform/flame/FlamePackIndex.h>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include "ui/pages/modplatform/ModpackProviderBasePage.h"
|
#include "ui/pages/modplatform/ModpackProviderBasePage.h"
|
||||||
|
#include "ui/widgets/ModFilterWidget.h"
|
||||||
#include "ui/widgets/ProgressWidget.h"
|
#include "ui/widgets/ProgressWidget.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
@ -84,6 +85,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage {
|
|||||||
void triggerSearch();
|
void triggerSearch();
|
||||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||||
void onVersionSelectionChanged(int index);
|
void onVersionSelectionChanged(int index);
|
||||||
|
void createFilterWidget();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::FlamePage* ui = nullptr;
|
Ui::FlamePage* ui = nullptr;
|
||||||
@ -97,4 +99,7 @@ class FlamePage : public QWidget, public ModpackProviderBasePage {
|
|||||||
|
|
||||||
// Used to do instant searching with a delay to cache quick changes
|
// Used to do instant searching with a delay to cache quick changes
|
||||||
QTimer m_search_timer;
|
QTimer m_search_timer;
|
||||||
|
|
||||||
|
unique_qobject_ptr<ModFilterWidget> m_filterWidget;
|
||||||
|
Task::Ptr m_categoriesTask;
|
||||||
};
|
};
|
||||||
|
@ -30,42 +30,59 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="searchEdit">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Search and filter...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QListView" name="packView">
|
<widget class="QLineEdit" name="searchEdit">
|
||||||
<property name="horizontalScrollBarPolicy">
|
<property name="placeholderText">
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
<string>Search and filter...</string>
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>48</width>
|
|
||||||
<height>48</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="ProjectDescriptionPage" name="packDescription">
|
<widget class="QPushButton" name="filterButton">
|
||||||
<property name="openExternalLinks">
|
<property name="text">
|
||||||
<bool>true</bool>
|
<string>Filter</string>
|
||||||
</property>
|
|
||||||
<property name="openLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="widget" native="true"/>
|
||||||
|
<widget class="QListView" name="packView">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>48</width>
|
||||||
|
<height>48</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="ProjectDescriptionPage" name="packDescription">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout">
|
<layout class="QHBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -215,11 +215,11 @@ unique_qobject_ptr<ModFilterWidget> FlameModPage::createFilterWidget()
|
|||||||
void FlameModPage::prepareProviderCategories()
|
void FlameModPage::prepareProviderCategories()
|
||||||
{
|
{
|
||||||
auto response = std::make_shared<QByteArray>();
|
auto response = std::make_shared<QByteArray>();
|
||||||
auto task = FlameAPI::getModCategories(response);
|
m_categoriesTask = FlameAPI::getModCategories(response);
|
||||||
QObject::connect(task.get(), &Task::succeeded, [this, response]() {
|
QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() {
|
||||||
auto categories = FlameAPI::loadModCategories(response);
|
auto categories = FlameAPI::loadModCategories(response);
|
||||||
m_filter_widget->setCategories(categories);
|
m_filter_widget->setCategories(categories);
|
||||||
});
|
});
|
||||||
task->start();
|
m_categoriesTask->start();
|
||||||
};
|
};
|
||||||
} // namespace ResourceDownload
|
} // namespace ResourceDownload
|
||||||
|
@ -100,6 +100,9 @@ class FlameModPage : public ModPage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void prepareProviderCategories() override;
|
virtual void prepareProviderCategories() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Task::Ptr m_categoriesTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FlameResourcePackPage : public ResourcePackResourcePage {
|
class FlameResourcePackPage : public ResourcePackResourcePage {
|
||||||
|
@ -152,33 +152,26 @@ void ModpackListModel::performPaginatedSearch()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} // TODO: Move to standalone API
|
} // TODO: Move to standalone API
|
||||||
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
|
ResourceAPI::SortingMethod sort{};
|
||||||
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
|
sort.name = currentSort;
|
||||||
"/search?"
|
auto searchUrl = ModrinthAPI().getSearchURL({ ModPlatform::ResourceType::MODPACK, nextSearchOffset, currentSearchTerm, sort,
|
||||||
"offset=%1&"
|
m_filter->loaders, m_filter->versions, "", m_filter->categoryIds });
|
||||||
"limit=%2&"
|
|
||||||
"query=%3&"
|
|
||||||
"index=%4&"
|
|
||||||
"facets=[[\"project_type:modpack\"]]")
|
|
||||||
.arg(nextSearchOffset)
|
|
||||||
.arg(m_modpacks_per_page)
|
|
||||||
.arg(currentSearchTerm)
|
|
||||||
.arg(currentSort);
|
|
||||||
|
|
||||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchAllUrl), m_all_response));
|
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
|
||||||
|
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl.value()), m_allResponse));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, this, [this] {
|
||||||
QJsonParseError parse_error_all{};
|
QJsonParseError parseError{};
|
||||||
|
|
||||||
QJsonDocument doc_all = QJsonDocument::fromJson(*m_all_response, &parse_error_all);
|
QJsonDocument doc = QJsonDocument::fromJson(*m_allResponse, &parseError);
|
||||||
if (parse_error_all.error != QJsonParseError::NoError) {
|
if (parseError.error != QJsonParseError::NoError) {
|
||||||
qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset
|
qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parseError.offset
|
||||||
<< " reason: " << parse_error_all.errorString();
|
<< " reason: " << parseError.errorString();
|
||||||
qWarning() << *m_all_response;
|
qWarning() << *m_allResponse;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchRequestFinished(doc_all);
|
searchRequestFinished(doc);
|
||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
|
QObject::connect(netJob.get(), &NetJob::failed, this, &ModpackListModel::searchRequestFailed);
|
||||||
|
|
||||||
@ -220,19 +213,23 @@ static auto sortFromIndex(int index) -> QString
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModpackListModel::searchWithTerm(const QString& term, const int sort)
|
void ModpackListModel::searchWithTerm(const QString& term,
|
||||||
|
const int sort,
|
||||||
|
std::shared_ptr<ModFilterWidget::Filter> filter,
|
||||||
|
bool filterChanged)
|
||||||
{
|
{
|
||||||
if (sort > 5 || sort < 0)
|
if (sort > 5 || sort < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto sort_str = sortFromIndex(sort);
|
auto sort_str = sortFromIndex(sort);
|
||||||
|
|
||||||
if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) {
|
if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str && !filterChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSearchTerm = term;
|
currentSearchTerm = term;
|
||||||
currentSort = sort_str;
|
currentSort = sort_str;
|
||||||
|
m_filter = filter;
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ class ModpackListModel : public QAbstractListModel {
|
|||||||
/* Ask the API for more information */
|
/* Ask the API for more information */
|
||||||
void fetchMore(const QModelIndex& parent) override;
|
void fetchMore(const QModelIndex& parent) override;
|
||||||
void refresh();
|
void refresh();
|
||||||
void searchWithTerm(const QString& term, int sort);
|
void searchWithTerm(const QString& term, int sort, std::shared_ptr<ModFilterWidget::Filter> filter, bool filterChanged);
|
||||||
|
|
||||||
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
[[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
|
||||||
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
[[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
|
||||||
@ -112,12 +112,13 @@ class ModpackListModel : public QAbstractListModel {
|
|||||||
|
|
||||||
QString currentSearchTerm;
|
QString currentSearchTerm;
|
||||||
QString currentSort;
|
QString currentSort;
|
||||||
|
std::shared_ptr<ModFilterWidget::Filter> m_filter;
|
||||||
int nextSearchOffset = 0;
|
int nextSearchOffset = 0;
|
||||||
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
|
||||||
|
|
||||||
Task::Ptr jobPtr;
|
Task::Ptr jobPtr;
|
||||||
|
|
||||||
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
|
std::shared_ptr<QByteArray> m_allResponse = std::make_shared<QByteArray>();
|
||||||
QByteArray m_specific_response;
|
QByteArray m_specific_response;
|
||||||
|
|
||||||
int m_modpacks_per_page = 20;
|
int m_modpacks_per_page = 20;
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ModrinthPage.h"
|
#include "ModrinthPage.h"
|
||||||
|
#include "Version.h"
|
||||||
|
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
#include "ui_ModrinthPage.h"
|
#include "ui_ModrinthPage.h"
|
||||||
|
|
||||||
@ -58,6 +60,7 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
|
|||||||
: QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
|
: QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
createFilterWidget();
|
||||||
|
|
||||||
ui->searchEdit->installEventFilter(this);
|
ui->searchEdit->installEventFilter(this);
|
||||||
m_model = new Modrinth::ModpackListModel(this);
|
m_model = new Modrinth::ModpackListModel(this);
|
||||||
@ -126,6 +129,16 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
|
|||||||
return QObject::eventFilter(watched, event);
|
return QObject::eventFilter(watched, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool checkVersionFilters(const Modrinth::ModpackVersion& v, std::shared_ptr<ModFilterWidget::Filter> filter)
|
||||||
|
{
|
||||||
|
if (!filter)
|
||||||
|
return true;
|
||||||
|
return ((!filter->loaders || !v.loaders || filter->loaders & v.loaders) && // loaders
|
||||||
|
(filter->releases.empty() || // releases
|
||||||
|
std::find(filter->releases.cbegin(), filter->releases.cend(), v.version_type) != filter->releases.cend()) &&
|
||||||
|
filter->checkMcVersions({ v.gameVersion })); // gameVersion}
|
||||||
|
}
|
||||||
|
|
||||||
void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
|
||||||
{
|
{
|
||||||
ui->versionSelectionBox->clear();
|
ui->versionSelectionBox->clear();
|
||||||
@ -190,7 +203,7 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
|
|||||||
} else
|
} else
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
||||||
if (!current.versionsLoaded) {
|
if (!current.versionsLoaded || m_filterWidget->changed()) {
|
||||||
qDebug() << "Loading modrinth modpack versions";
|
qDebug() << "Loading modrinth modpack versions";
|
||||||
|
|
||||||
auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network());
|
||||||
@ -221,6 +234,16 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
|
|||||||
qDebug() << *response;
|
qDebug() << *response;
|
||||||
qWarning() << "Error while reading modrinth modpack version: " << e.cause();
|
qWarning() << "Error while reading modrinth modpack version: " << e.cause();
|
||||||
}
|
}
|
||||||
|
auto pred = [this](const Modrinth::ModpackVersion& v) { return !checkVersionFilters(v, m_filterWidget->getFilter()); };
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||||
|
current.versions.removeIf(pred);
|
||||||
|
#else
|
||||||
|
for (auto it = current.versions.begin(); it != current.versions.end();)
|
||||||
|
if (pred(*it))
|
||||||
|
it = current.versions.erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#endif
|
||||||
for (auto version : current.versions) {
|
for (auto version : current.versions) {
|
||||||
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
|
||||||
auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion)
|
auto mcVersion = !version.gameVersion.isEmpty() && !version.name.contains(version.gameVersion)
|
||||||
@ -338,7 +361,11 @@ void ModrinthPage::suggestCurrent()
|
|||||||
|
|
||||||
void ModrinthPage::triggerSearch()
|
void ModrinthPage::triggerSearch()
|
||||||
{
|
{
|
||||||
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
|
ui->packView->selectionModel()->setCurrentIndex({}, QItemSelectionModel::SelectionFlag::ClearAndSelect);
|
||||||
|
ui->packView->clearSelection();
|
||||||
|
ui->packDescription->clear();
|
||||||
|
ui->versionSelectionBox->clear();
|
||||||
|
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), m_filterWidget->getFilter(), m_filterWidget->changed());
|
||||||
m_fetch_progress.watch(m_model->activeSearchJob().get());
|
m_fetch_progress.watch(m_model->activeSearchJob().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,3 +388,25 @@ QString ModrinthPage::getSerachTerm() const
|
|||||||
{
|
{
|
||||||
return ui->searchEdit->text();
|
return ui->searchEdit->text();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModrinthPage::createFilterWidget()
|
||||||
|
{
|
||||||
|
auto widget = ModFilterWidget::create(nullptr, true, this);
|
||||||
|
m_filterWidget.swap(widget);
|
||||||
|
auto old = ui->splitter->replaceWidget(0, m_filterWidget.get());
|
||||||
|
// because we replaced the widget we also need to delete it
|
||||||
|
if (old) {
|
||||||
|
delete old;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->filterButton, &QPushButton::clicked, this, [this] { m_filterWidget->setHidden(!m_filterWidget->isHidden()); });
|
||||||
|
|
||||||
|
connect(m_filterWidget.get(), &ModFilterWidget::filterChanged, this, &ModrinthPage::triggerSearch);
|
||||||
|
auto response = std::make_shared<QByteArray>();
|
||||||
|
m_categoriesTask = ModrinthAPI::getModCategories(response);
|
||||||
|
QObject::connect(m_categoriesTask.get(), &Task::succeeded, [this, response]() {
|
||||||
|
auto categories = ModrinthAPI::loadCategories(response, "modpack");
|
||||||
|
m_filterWidget->setCategories(categories);
|
||||||
|
});
|
||||||
|
m_categoriesTask->start();
|
||||||
|
}
|
@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
#include "modplatform/modrinth/ModrinthPackManifest.h"
|
||||||
#include "ui/pages/modplatform/ModpackProviderBasePage.h"
|
#include "ui/pages/modplatform/ModpackProviderBasePage.h"
|
||||||
|
#include "ui/widgets/ModFilterWidget.h"
|
||||||
#include "ui/widgets/ProgressWidget.h"
|
#include "ui/widgets/ProgressWidget.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -87,6 +88,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage {
|
|||||||
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
void onSelectionChanged(QModelIndex first, QModelIndex second);
|
||||||
void onVersionSelectionChanged(int index);
|
void onVersionSelectionChanged(int index);
|
||||||
void triggerSearch();
|
void triggerSearch();
|
||||||
|
void createFilterWidget();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::ModrinthPage* ui;
|
Ui::ModrinthPage* ui;
|
||||||
@ -100,4 +102,7 @@ class ModrinthPage : public QWidget, public ModpackProviderBasePage {
|
|||||||
|
|
||||||
// Used to do instant searching with a delay to cache quick changes
|
// Used to do instant searching with a delay to cache quick changes
|
||||||
QTimer m_search_timer;
|
QTimer m_search_timer;
|
||||||
|
|
||||||
|
unique_qobject_ptr<ModFilterWidget> m_filterWidget;
|
||||||
|
Task::Ptr m_categoriesTask;
|
||||||
};
|
};
|
||||||
|
@ -12,42 +12,59 @@
|
|||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="searchEdit">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<property name="placeholderText">
|
|
||||||
<string>Search and filter ...</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QListView" name="packView">
|
<widget class="QLineEdit" name="searchEdit">
|
||||||
<property name="horizontalScrollBarPolicy">
|
<property name="placeholderText">
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
<string>Search and filter...</string>
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>48</width>
|
|
||||||
<height>48</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="ProjectDescriptionPage" name="packDescription">
|
<widget class="QPushButton" name="filterButton">
|
||||||
<property name="openExternalLinks">
|
<property name="text">
|
||||||
<bool>true</bool>
|
<string>Filter</string>
|
||||||
</property>
|
|
||||||
<property name="openLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSplitter" name="splitter">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="widget" native="true"/>
|
||||||
|
<widget class="QListView" name="packView">
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>48</width>
|
||||||
|
<height>48</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
<widget class="ProjectDescriptionPage" name="packDescription">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="openLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout">
|
<layout class="QHBoxLayout">
|
||||||
<item>
|
<item>
|
||||||
|
@ -38,19 +38,6 @@
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="labelPostExitCmd">
|
|
||||||
<property name="text">
|
|
||||||
<string>P&ost-exit command:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>postExitCmdTextBox</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="preLaunchCmdTextBox"/>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="labelPreLaunchCmd">
|
<widget class="QLabel" name="labelPreLaunchCmd">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -61,8 +48,8 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="0" column="1">
|
||||||
<widget class="QLineEdit" name="postExitCmdTextBox"/>
|
<widget class="QLineEdit" name="preLaunchCmdTextBox"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="labelWrapperCmd">
|
<widget class="QLabel" name="labelWrapperCmd">
|
||||||
@ -77,6 +64,19 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QLineEdit" name="wrapperCmdTextBox"/>
|
<widget class="QLineEdit" name="wrapperCmdTextBox"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelPostExitCmd">
|
||||||
|
<property name="text">
|
||||||
|
<string>P&ost-exit command:</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>postExitCmdTextBox</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="postExitCmdTextBox"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -64,10 +64,49 @@ class VersionBasicModel : public QIdentityProxyModel {
|
|||||||
{
|
{
|
||||||
if (role == Qt::DisplayRole)
|
if (role == Qt::DisplayRole)
|
||||||
return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole);
|
return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole);
|
||||||
|
if (role == Qt::UserRole)
|
||||||
|
return QIdentityProxyModel::data(index, BaseVersionList::VersionIdRole);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AllVersionProxyModel : public QSortFilterProxyModel {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
AllVersionProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
|
||||||
|
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override { return QSortFilterProxyModel::rowCount(parent) + 1; }
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.row() == 0) {
|
||||||
|
if (role == Qt::DisplayRole) {
|
||||||
|
return tr("All Versions");
|
||||||
|
}
|
||||||
|
if (role == Qt::UserRole) {
|
||||||
|
return "all";
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex newIndex = QSortFilterProxyModel::index(index.row() - 1, index.column());
|
||||||
|
return QSortFilterProxyModel::data(newIndex, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::ItemFlags flags(const QModelIndex& index) const override
|
||||||
|
{
|
||||||
|
if (index.row() == 0) {
|
||||||
|
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
||||||
|
}
|
||||||
|
return QSortFilterProxyModel::flags(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent)
|
ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWidget* parent)
|
||||||
: QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter())
|
: QTabWidget(parent), ui(new Ui::ModFilterWidget), m_instance(instance), m_filter(new Filter())
|
||||||
{
|
{
|
||||||
@ -76,14 +115,21 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi
|
|||||||
m_versions_proxy = new VersionProxyModel(this);
|
m_versions_proxy = new VersionProxyModel(this);
|
||||||
m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release"));
|
m_versions_proxy->setFilter(BaseVersionList::TypeRole, new ExactFilter("release"));
|
||||||
|
|
||||||
auto proxy = new VersionBasicModel(this);
|
QAbstractProxyModel* proxy = new VersionBasicModel(this);
|
||||||
proxy->setSourceModel(m_versions_proxy);
|
proxy->setSourceModel(m_versions_proxy);
|
||||||
|
|
||||||
if (extended) {
|
if (extended) {
|
||||||
|
if (!m_instance) {
|
||||||
|
ui->environmentGroup->hide();
|
||||||
|
}
|
||||||
ui->versions->setSourceModel(proxy);
|
ui->versions->setSourceModel(proxy);
|
||||||
ui->versions->setSeparator(", ");
|
ui->versions->setSeparator(", ");
|
||||||
|
ui->versions->setDefaultText(tr("All Versions"));
|
||||||
ui->version->hide();
|
ui->version->hide();
|
||||||
} else {
|
} else {
|
||||||
|
auto allVersions = new AllVersionProxyModel(this);
|
||||||
|
allVersions->setSourceModel(proxy);
|
||||||
|
proxy = allVersions;
|
||||||
ui->version->setModel(proxy);
|
ui->version->setModel(proxy);
|
||||||
ui->versions->hide();
|
ui->versions->hide();
|
||||||
ui->showAllVersions->hide();
|
ui->showAllVersions->hide();
|
||||||
@ -162,18 +208,22 @@ void ModFilterWidget::loadVersionList()
|
|||||||
|
|
||||||
void ModFilterWidget::prepareBasicFilter()
|
void ModFilterWidget::prepareBasicFilter()
|
||||||
{
|
{
|
||||||
m_filter->hideInstalled = false;
|
if (m_instance) {
|
||||||
m_filter->side = ""; // or "both"
|
m_filter->hideInstalled = false;
|
||||||
auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value();
|
m_filter->side = ""; // or "both"
|
||||||
ui->neoForge->setChecked(loaders & ModPlatform::NeoForge);
|
auto loaders = m_instance->getPackProfile()->getSupportedModLoaders().value();
|
||||||
ui->forge->setChecked(loaders & ModPlatform::Forge);
|
ui->neoForge->setChecked(loaders & ModPlatform::NeoForge);
|
||||||
ui->fabric->setChecked(loaders & ModPlatform::Fabric);
|
ui->forge->setChecked(loaders & ModPlatform::Forge);
|
||||||
ui->quilt->setChecked(loaders & ModPlatform::Quilt);
|
ui->fabric->setChecked(loaders & ModPlatform::Fabric);
|
||||||
m_filter->loaders = loaders;
|
ui->quilt->setChecked(loaders & ModPlatform::Quilt);
|
||||||
auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft");
|
m_filter->loaders = loaders;
|
||||||
m_filter->versions.emplace_front(def);
|
auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft");
|
||||||
ui->versions->setCheckedItems({ def });
|
m_filter->versions.emplace_front(def);
|
||||||
ui->version->setCurrentIndex(ui->version->findText(def));
|
ui->versions->setCheckedItems({ def });
|
||||||
|
ui->version->setCurrentIndex(ui->version->findText(def));
|
||||||
|
} else {
|
||||||
|
ui->hideInstalled->hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModFilterWidget::onShowAllVersionsChanged()
|
void ModFilterWidget::onShowAllVersionsChanged()
|
||||||
@ -249,7 +299,9 @@ void ModFilterWidget::onHideInstalledFilterChanged()
|
|||||||
void ModFilterWidget::onVersionFilterTextChanged(const QString& version)
|
void ModFilterWidget::onVersionFilterTextChanged(const QString& version)
|
||||||
{
|
{
|
||||||
m_filter->versions.clear();
|
m_filter->versions.clear();
|
||||||
m_filter->versions.emplace_back(version);
|
if (ui->version->currentData(Qt::UserRole) != "all") {
|
||||||
|
m_filter->versions.emplace_back(version);
|
||||||
|
}
|
||||||
m_filter_changed = true;
|
m_filter_changed = true;
|
||||||
emit filterChanged();
|
emit filterChanged();
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,15 @@ class ModFilterWidget : public QTabWidget {
|
|||||||
releases == other.releases && categoryIds == other.categoryIds;
|
releases == other.releases && categoryIds == other.categoryIds;
|
||||||
}
|
}
|
||||||
bool operator!=(const Filter& other) const { return !(*this == other); }
|
bool operator!=(const Filter& other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
bool checkMcVersions(QStringList value)
|
||||||
|
{
|
||||||
|
for (auto mcVersion : versions)
|
||||||
|
if (value.contains(mcVersion.toString()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return versions.empty();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static unique_qobject_ptr<ModFilterWidget> create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr);
|
static unique_qobject_ptr<ModFilterWidget> create(MinecraftInstance* instance, bool extended, QWidget* parent = nullptr);
|
||||||
|
@ -87,6 +87,8 @@ stdenv.mkDerivation {
|
|||||||
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/")
|
(lib.cmakeFeature "CMAKE_INSTALL_PREFIX" "${placeholder "out"}/Applications/")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
doCheck = true;
|
||||||
|
|
||||||
dontWrapQtApps = true;
|
dontWrapQtApps = true;
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
|
@ -57,7 +57,7 @@ Section "Visual Studio Runtime"
|
|||||||
Pop $0
|
Pop $0
|
||||||
${If} $0 == "OK"
|
${If} $0 == "OK"
|
||||||
DetailPrint "Download successful"
|
DetailPrint "Download successful"
|
||||||
ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart\"
|
ExecWait "$INSTDIR\vc_redist\$vc_redist_exe /install /passive /norestart"
|
||||||
${Else}
|
${Else}
|
||||||
DetailPrint "Download failed with error $0"
|
DetailPrint "Download failed with error $0"
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user