Compare commits

..

19 commits

Author SHA1 Message Date
Alexandru Ionut Tripon
a71b8d8fe3
[Backport release-11.x] enable modpack changelog for modrinth page (#5360) 2026-04-11 09:36:29 +03:00
Trial97
6d38b34c00 enable modpack changelog for modrinth page
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit f3ff0a730a)
2026-04-11 05:37:04 +00:00
Alexandru Ionut Tripon
85f19da603
[Backport release-11.x] fix pack upgrade (#5356) 2026-04-10 20:32:09 +03:00
Trial97
239be1ec43 fix pack upgrade
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
(cherry picked from commit b7344af313)
2026-04-10 17:31:48 +00:00
Alexandru Ionut Tripon
34349a6810
[Backport release-11.x] Allow disabling low RAM warning (#5350) 2026-04-10 15:23:20 +03:00
Octol1ttle
fb7e4da4e6 Change LowMemWarning default to always enabled
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4b3aedd5d0)
2026-04-10 09:31:16 +00:00
Octol1ttle
f766cdd847 change(EnsureAvailableMemory): add lenience
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 658a1391f8)
2026-04-10 09:31:16 +00:00
Octol1ttle
789c656463 feat: allow disabling low RAM warning
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit c044ed36af)
2026-04-10 09:31:16 +00:00
Alexandru Ionut Tripon
2d01dfa4f2
[Backport release-11.x] fix McClient (#5344) 2026-04-10 00:50:18 +03:00
Alexandru Ionut Tripon
9231fe8592
[Backport release-11.x] Don't count JAR mods when checking offline libraries (#5343) 2026-04-10 00:49:58 +03:00
Alexandru Ionut Tripon
25fee30f95
[Backport release-11.x] CI/Nix: Bump macOS (#5342) 2026-04-10 00:49:42 +03:00
Alexandru Ionut Tripon
ec6a173608
[Backport release-11.x] fix(PrintInstanceInfo): add break before OS info (#5341) 2026-04-10 00:49:24 +03:00
Alexandru Ionut Tripon
b7d70dc1c1
chore: bump to 11.0.1 (#5340) 2026-04-10 00:49:09 +03:00
Octol1ttle
09f0467e81 refactor: McClient
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 91616ae9b6)
2026-04-09 21:06:40 +00:00
Octol1ttle
fe02ad8524 fix(McClient): do not use unsigned type for response length
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 2fe0569bd6)
2026-04-09 21:06:40 +00:00
Octol1ttle
f66796e806 fix: don't count JAR mods when checking offline libraries
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit ec4484282c)
2026-04-09 20:56:32 +00:00
Octol1ttle
4cd8c343fe fix(CI/nix): bump macOS
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 724c9a4a2c)
2026-04-09 20:47:44 +00:00
Octol1ttle
960e1bac87 fix(PrintInstanceInfo): add break before OS info
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
(cherry picked from commit 4cf8cf7d18)
2026-04-09 20:46:36 +00:00
Trial97
838e7fb8d2 chore: bump to 11.0.1
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2026-04-09 23:33:42 +03:00
167 changed files with 2596 additions and 3601 deletions

View file

@ -23,14 +23,14 @@ body:
- macOS - macOS
- Linux - Linux
- Other - Other
- type: input - type: textarea
attributes: attributes:
label: Version of Prism Launcher label: Version of Prism Launcher
description: The version of Prism Launcher used in the bug report. description: The version of Prism Launcher used in the bug report.
placeholder: Prism Launcher 5.0 placeholder: Prism Launcher 5.0
validations: validations:
required: true required: true
- type: input - type: textarea
attributes: attributes:
label: Version of Qt label: Version of Qt
description: The version of Qt used in the bug report. You can find it in Help -> About Prism Launcher -> About Qt. description: The version of Qt used in the bug report. You can find it in Help -> About Prism Launcher -> About Qt.

View file

@ -69,7 +69,7 @@ runs:
- name: Sign executables - name: Sign executables
if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }}
uses: azure/artifact-signing-action@v2 uses: azure/artifact-signing-action@v1
with: with:
endpoint: https://eus.codesigning.azure.net/ endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: PrismLauncher trusted-signing-account-name: PrismLauncher
@ -142,7 +142,7 @@ runs:
- name: Sign installer - name: Sign installer
if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }}
uses: azure/artifact-signing-action@v2 uses: azure/artifact-signing-action@v1
with: with:
endpoint: https://eus.codesigning.azure.net/ endpoint: https://eus.codesigning.azure.net/
trusted-signing-account-name: PrismLauncher trusted-signing-account-name: PrismLauncher

View file

@ -55,7 +55,7 @@ runs:
# TODO(@getchoo): Get this working on MSYS2! # TODO(@getchoo): Get this working on MSYS2!
- name: Setup ccache - name: Setup ccache
if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }}
uses: hendrikmuhs/ccache-action@v1.2.23 uses: hendrikmuhs/ccache-action@v1.2.22
with: with:
variant: sccache variant: sccache
create-symlink: ${{ runner.os != 'Windows' }} create-symlink: ${{ runner.os != 'Windows' }}

View file

@ -13,7 +13,7 @@ runs:
dpkg-dev \ dpkg-dev \
ninja-build extra-cmake-modules pkg-config scdoc \ ninja-build extra-cmake-modules pkg-config scdoc \
cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \
libxcb-cursor-dev libtomlplusplus-dev libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev
- name: Setup AppImage tooling - name: Setup AppImage tooling
shell: bash shell: bash

View file

@ -91,7 +91,7 @@ runs:
- name: Retrieve ccache cache (MinGW) - name: Retrieve ccache cache (MinGW)
if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }}
uses: actions/cache@v5.0.5 uses: actions/cache@v5.0.4
with: with:
path: '${{ github.workspace }}\.ccache' path: '${{ github.workspace }}\.ccache'
key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }} key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }}

View file

@ -24,7 +24,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v4.5 uses: korthout/backport-action@v4.3.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View file

@ -28,14 +28,19 @@ jobs:
fetch-depth: 0 # Required for diffing later on fetch-depth: 0 # Required for diffing later on
submodules: "true" submodules: "true"
- name: Setup sccache
uses: hendrikmuhs/ccache-action@v1.2.22
with:
variant: sccache
- name: Install Nix - name: Install Nix
uses: cachix/install-nix-action@v31 uses: cachix/install-nix-action@v31
- name: Run source generators - name: Run build
# TODO(@getchoo): Figure out how to make this work with PCH # TODO(@getchoo): Figure out how to make this work with PCH
run: | run: |
nix develop --command bash -c ' nix develop --command bash -c '
cmake -B build -D Launcher_USE_PCH=OFF && cmake --build build --target autogen autorcc cmake -B build -D Launcher_USE_PCH=OFF -D CMAKE_CXX_COMPILER_LAUNCHER=sccache && cmake --build build
' '
# TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed # TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed

View file

@ -33,7 +33,7 @@ jobs:
- arch: arm64 - arch: arm64
os: ubuntu-24.04-arm os: ubuntu-24.04-arm
- arch: amd64 - arch: amd64
os: ubuntu-24.04 os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View file

@ -103,7 +103,7 @@ jobs:
# For PRs # For PRs
- name: Setup Nix Magic Cache - name: Setup Nix Magic Cache
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' }}
uses: DeterminateSystems/magic-nix-cache-action@v14 uses: DeterminateSystems/magic-nix-cache-action@v13
with: with:
diagnostic-endpoint: "" diagnostic-endpoint: ""
use-flakehub: false use-flakehub: false

View file

@ -94,7 +94,7 @@ jobs:
- name: Create release - name: Create release
id: create_release id: create_release
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v2
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref }} tag_name: ${{ github.ref }}

View file

@ -20,7 +20,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31 - uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31
- uses: DeterminateSystems/update-flake-lock@v28 - uses: DeterminateSystems/update-flake-lock@v28
with: with:

View file

@ -13,10 +13,6 @@ endif()
##################################### Set CMake options ##################################### ##################################### Set CMake options #####################################
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOGEN_ORIGIN_DEPENDS OFF)
set(CMAKE_GLOBAL_AUTOGEN_TARGET ON)
set(CMAKE_GLOBAL_AUTORCC_TARGET ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
@ -183,9 +179,9 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.") set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.")
######## Set version numbers ######## ######## Set version numbers ########
set(Launcher_VERSION_MAJOR 12) set(Launcher_VERSION_MAJOR 11)
set(Launcher_VERSION_MINOR 0) set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_PATCH 0) set(Launcher_VERSION_PATCH 1)
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}") set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0") set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")

View file

@ -28,7 +28,7 @@ RUN apt-get --assume-yes --no-install-recommends install \
# Build system # Build system
cmake ninja-build extra-cmake-modules pkg-config \ cmake ninja-build extra-cmake-modules pkg-config \
# Dependencies # Dependencies
cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev scdoc zlib1g-dev \ cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev libvulkan-dev scdoc zlib1g-dev \
# Tooling # Tooling
clang-format clang-tidy git clang-format clang-tidy git

View file

@ -194,10 +194,8 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" }; QStringList MODRINTH_MRPACK_HOSTS{ "cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com" };
QString MODRINTH_DOWNLOAD_HOST = "cdn.modrinth.com";
QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
QString FLAME_DOWNLOAD_HOST = "edge.forgecdn.net";
QString versionString() const; QString versionString() const;
/** /**

8
flake.lock generated
View file

@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1778443072, "lastModified": 1774709303,
"narHash": "sha256-rNDJzV2JTV5SUTwv1cgKZYMdyoUYU9/YfegSaUf3QfY=", "narHash": "sha256-D4ely1FsBcvtj/qSrNhSWpq+CUZKNiKwJIxpxnfy9o4=",
"rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre995699.da5ad661ba4e/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",

View file

@ -578,14 +578,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
} }
{ {
auto migrated = handleDataMigration( bool migrated = false;
dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC",
"polymc.cfg"); if (!migrated)
if (!migrated) { migrated = handleDataMigration(
handleDataMigration(dataPath, dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC",
FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "polymc.cfg");
"MultiMC", "multimc.cfg"); if (!migrated)
} migrated = handleDataMigration(
dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC",
"multimc.cfg");
} }
{ {
@ -777,7 +779,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ModDependenciesDisabled", false); m_settings->registerSetting("ModDependenciesDisabled", false);
m_settings->registerSetting("SkipModpackUpdatePrompt", false); m_settings->registerSetting("SkipModpackUpdatePrompt", false);
m_settings->registerSetting("ShowModIncompat", false); m_settings->registerSetting("ShowModIncompat", false);
m_settings->registerSetting("DownloadGameFilesDuringInstanceCreation", true);
// Minecraft offline player name // Minecraft offline player name
m_settings->registerSetting("LastOfflinePlayerName", ""); m_settings->registerSetting("LastOfflinePlayerName", "");
@ -870,7 +871,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get()); resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get());
} }
m_settings->registerSetting("MetaRefreshOnLaunch", true);
m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("CloseAfterLaunch", false);
m_settings->registerSetting("QuitAfterGameStop", false); m_settings->registerSetting("QuitAfterGameStop", false);
@ -935,6 +935,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
qInfo() << "<> Network done."; qInfo() << "<> Network done.";
} }
// load translations
{
m_translations.reset(new TranslationsModel("translations"));
auto bcp47Name = m_settings->get("Language").toString();
m_translations->selectLanguage(bcp47Name);
qInfo() << "Your language is" << bcp47Name;
qInfo() << "<> Translations loaded.";
}
// Instance icons // Instance icons
{ {
auto setting = APPLICATION->settings()->getSetting("IconsDir"); auto setting = APPLICATION->settings()->getSetting("IconsDir");
@ -1013,13 +1022,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
qInfo() << "<> Cache initialized."; qInfo() << "<> Cache initialized.";
} }
// load translations // now we have network, download translation updates
{ m_translations->downloadIndex();
m_translations.reset(new TranslationsModel("translations"));
m_translations->downloadIndex();
qInfo() << "Your language is" << m_translations->selectedLanguage();
qInfo() << "<> Translations loaded.";
}
// FIXME: what to do with these? // FIXME: what to do with these?
m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory())); m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));

View file

@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel {
* The task returned by this function should reset the model when it's done. * The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list. * \return A pointer to a task that reloads the version list.
*/ */
virtual Task::Ptr getLoadTask(bool forceReload = false) = 0; virtual Task::Ptr getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be //! Checks whether or not the list is loaded. If this returns false, the list should be
// loaded. // loaded.

View file

@ -1206,6 +1206,78 @@ if(WIN32)
) )
endif() endif()
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
ui/setupwizard/AutoJavaWizardPage.ui
ui/setupwizard/LoginWizardPage.ui
ui/pages/global/AccountListPage.ui
ui/pages/global/JavaPage.ui
ui/pages/global/LauncherPage.ui
ui/pages/global/APIPage.ui
ui/pages/global/ProxyPage.ui
ui/pages/global/ExternalToolsPage.ui
ui/pages/instance/ExternalResourcesPage.ui
ui/pages/instance/NotesPage.ui
ui/pages/instance/LogPage.ui
ui/pages/instance/ServersPage.ui
ui/pages/instance/OtherLogsPage.ui
ui/pages/instance/VersionPage.ui
ui/pages/instance/ManagedPackPage.ui
ui/pages/instance/WorldListPage.ui
ui/pages/instance/ScreenshotsPage.ui
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
ui/pages/modplatform/atlauncher/AtlPage.ui
ui/pages/modplatform/CustomPage.ui
ui/pages/modplatform/ResourcePage.ui
ui/pages/modplatform/flame/FlamePage.ui
ui/pages/modplatform/legacy_ftb/Page.ui
ui/pages/modplatform/import_ftb/ImportFTBPage.ui
ui/pages/modplatform/ImportPage.ui
ui/pages/modplatform/OptionalModDialog.ui
ui/pages/modplatform/ftb/FtbPage.ui
ui/pages/modplatform/modrinth/ModrinthPage.ui
ui/pages/modplatform/technic/TechnicPage.ui
ui/widgets/CustomCommands.ui
ui/widgets/EnvironmentVariables.ui
ui/widgets/InfoFrame.ui
ui/widgets/ModFilterWidget.ui
ui/widgets/SubTaskProgressBar.ui
ui/widgets/AppearanceWidget.ui
ui/widgets/MinecraftSettingsWidget.ui
ui/widgets/JavaSettingsWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/CreateShortcutDialog.ui
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
ui/dialogs/NewInstanceDialog.ui
ui/dialogs/NetworkJobFailedDialog.ui
ui/dialogs/NewComponentDialog.ui
ui/dialogs/NewsDialog.ui
ui/dialogs/ProfileSelectDialog.ui
ui/dialogs/ExportInstanceDialog.ui
ui/dialogs/ExportPackDialog.ui
ui/dialogs/ExportToModListDialog.ui
ui/dialogs/IconPickerDialog.ui
ui/dialogs/ImportResourceDialog.ui
ui/dialogs/MSALoginDialog.ui
ui/dialogs/AboutDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
ui/dialogs/BlockedModsDialog.ui
ui/dialogs/ChooseProviderDialog.ui
ui/dialogs/skins/SkinManageDialog.ui
ui/dialogs/ChooseOfflineNameDialog.ui
)
qt_wrap_ui(PRISM_UPDATE_UI
ui/dialogs/UpdateAvailableDialog.ui
)
if (NOT Apple)
set (LAUNCHER_UI ${LAUNCHER_UI} ${PRISM_UPDATE_UI})
endif()
qt_add_resources(LAUNCHER_RESOURCES qt_add_resources(LAUNCHER_RESOURCES
resources/backgrounds/backgrounds.qrc resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc resources/multimc/multimc.qrc
@ -1224,6 +1296,12 @@ qt_add_resources(LAUNCHER_RESOURCES
"${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}" "${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_LogoQRC}"
) )
qt_wrap_ui(PRISMUPDATER_UI
updater/prismupdater/SelectReleaseDialog.ui
ui/widgets/SubTaskProgressBar.ui
ui/dialogs/ProgressDialog.ui
)
######## Windows resource files ######## ######## Windows resource files ########
if(WIN32) if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
@ -1243,7 +1321,7 @@ endif()
####### Targets ######## ####### Targets ########
# Add executable # Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_RESOURCES}) add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
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)
@ -1363,7 +1441,7 @@ endif()
if(Launcher_BUILD_UPDATER) if(Launcher_BUILD_UPDATER)
# Updater # Updater
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES}) add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if(${Launcher_USE_PCH}) if(${Launcher_USE_PCH})
@ -1491,10 +1569,8 @@ if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter
target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter
else() else()
# sfinae-incomplete is a new GCC warning and triggers in Qt headers target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers)
# no-unknown-warning-option so that compilers that don't have sfinae-incomplete don't error target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers)
target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete)
target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unknown-warning-option -Wno-sfinae-incomplete)
endif() endif()
#### The bundle mess! #### #### The bundle mess! ####

View file

@ -36,7 +36,6 @@
*/ */
#include "FileSystem.h" #include "FileSystem.h"
#include <qcontainerfwd.h>
#include <QPair> #include <QPair>
#include "BuildConfig.h" #include "BuildConfig.h"
@ -684,32 +683,6 @@ bool deletePath(QString path)
return err.value() == 0; return err.value() == 0;
} }
bool deleteContents(const QString& path)
{
const QFileInfo info(path);
if (!info.exists()) {
return true;
}
if (!info.isDir()) {
qWarning() << "Attempted to delete contents of non-directory path:" << path;
return false;
}
bool ret = true;
for (const auto& entry : fs::directory_iterator(StringUtils::toStdString(path))) {
std::error_code err;
fs::remove_all(entry.path(), err);
if (err.value() != 0) {
qWarning().nospace() << "Could not delete directory entry " << entry.path() << ": " << QString::fromStdString(err.message());
ret = false;
}
}
return ret;
}
bool trash(QString path, QString* pathInTrash) bool trash(QString path, QString* pathInTrash)
{ {
// FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal
@ -823,33 +796,68 @@ QString NormalizePath(QString path)
} }
} }
namespace { static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
const QString g_badChars = "<>:\"|?*\r\n!"; static const QString BAD_NTFS_CHARS = "<>:\"|?*";
QString removeChars(QString source, QChar replace, const QString& extraChars = "") static const QString BAD_HFS_CHARS = ":";
{
auto badChars = g_badChars;
if (!extraChars.isEmpty()) {
badChars += extraChars;
}
for (auto& c : source) { static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
if (c.unicode() < 0x20 || !c.isPrint() || badChars.contains(c)) {
c = replace;
}
}
return source;
}
} // namespace
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{ {
return removeChars(std::move(string), replaceWith, "\\/"); for (int i = 0; i < string.length(); i++)
if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i)))
string[i] = replaceWith;
return string;
} }
QString RemoveInvalidPathChars(QString string, QChar replaceWith) QString RemoveInvalidPathChars(QString path, QChar replaceWith)
{ {
return removeChars(std::move(string), replaceWith); QString invalidChars;
#ifdef Q_OS_WIN
invalidChars = BAD_WIN_CHARS;
#endif
// the null character is ignored in this check as it was not a problem until now
switch (statFS(path).fsType) {
case FilesystemType::FAT: // similar to NTFS
/* fallthrough */
case FilesystemType::NTFS:
/* fallthrough */
case FilesystemType::REFS: // similar to NTFS(should be available only on windows)
invalidChars += BAD_NTFS_CHARS;
break;
// case FilesystemType::EXT:
// case FilesystemType::EXT_2_OLD:
// case FilesystemType::EXT_2_3_4:
// case FilesystemType::XFS:
// case FilesystemType::BTRFS:
// case FilesystemType::NFS:
// case FilesystemType::ZFS:
case FilesystemType::APFS:
/* fallthrough */
case FilesystemType::HFS:
/* fallthrough */
case FilesystemType::HFSPLUS:
/* fallthrough */
case FilesystemType::HFSX:
invalidChars += BAD_HFS_CHARS;
break;
// case FilesystemType::FUSEBLK:
// case FilesystemType::F2FS:
// case FilesystemType::UNKNOWN:
default:
break;
}
if (invalidChars.size() != 0) {
for (int i = 0; i < path.length(); i++) {
if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) {
path[i] = replaceWith;
}
}
}
return path;
} }
QString DirNameFromString(QString string, QString inDir) QString DirNameFromString(QString string, QString inDir)

View file

@ -291,13 +291,6 @@ bool move(const QString& source, const QString& dest);
*/ */
bool deletePath(QString path); bool deletePath(QString path);
/**
* Delete a folder's contents recursively but not the folder itself.
* @param path The path to the folder.
* @return Whether the deletion was completely successful.
*/
bool deleteContents(const QString& path);
bool removeFiles(QStringList listFile); bool removeFiles(QStringList listFile);
/** /**

View file

@ -18,45 +18,84 @@
#include "HardwareInfo.h" #include "HardwareInfo.h"
#include <QDebug> #include <QCoreApplication>
#include <QStringList> #include <QOffscreenSurface>
#include <QOpenGLFunctions>
#include <QProcessEnvironment>
#include "BuildConfig.h"
#ifndef Q_OS_MACOS
#include <QVulkanInstance>
#include <QVulkanWindow>
#endif
#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX)
namespace { namespace {
QString afterColon(QString str) bool vulkanInfo(QStringList& out)
{ {
return str.remove(0, str.indexOf(':') + 2).trimmed(); if (!QProcessEnvironment::systemEnvironment()
.value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME))
.isEmpty()) {
return false;
}
#ifndef Q_OS_MACOS
QVulkanInstance inst;
if (!inst.create()) {
qWarning() << "Vulkan instance creation failed, VkResult:" << inst.errorCode();
out << "Couldn't get Vulkan device information";
return false;
}
QVulkanWindow window;
window.setVulkanInstance(&inst);
for (auto device : window.availablePhysicalDevices()) {
const auto supportedVulkanVersion = QVersionNumber(VK_API_VERSION_MAJOR(device.apiVersion), VK_API_VERSION_MINOR(device.apiVersion),
VK_API_VERSION_PATCH(device.apiVersion));
out << QString("Found Vulkan device: %1 (API version %2)").arg(device.deviceName).arg(supportedVulkanVersion.toString());
}
#endif
return true;
} }
template <typename F> bool openGlInfo(QStringList& out)
bool readFromOutput(const char* command, F function)
{ {
FILE* file = popen(command, "r"); // NOLINT(*-command-processor) if (!QProcessEnvironment::systemEnvironment()
if (!file) { .value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME))
qWarning().nospace() << "Could not execute command '" << command << "': " << strerror(errno); .isEmpty()) {
return false;
}
QOpenGLContext ctx;
if (!ctx.create()) {
qWarning() << "OpenGL context creation failed";
out << "Couldn't get OpenGL device information";
return false; return false;
} }
constexpr size_t bufferSize = 512; QOffscreenSurface surface;
std::array<char, bufferSize> buffer{}; surface.create();
while (fgets(buffer.data(), bufferSize, file) != nullptr) { ctx.makeCurrent(&surface);
function(buffer.data());
}
const int exitCode = pclose(file); auto* f = ctx.functions();
if (exitCode != 0) { f->initializeOpenGLFunctions();
if (exitCode == -1) {
qWarning().nospace() << "Could not close stream for command '" << command << "': " << strerror(errno);
} else {
qWarning().nospace() << "Command '" << command << "' exited with code " << exitCode;
}
return false; auto toQString = [](const GLubyte* str) { return QString(reinterpret_cast<const char*>(str)); };
} out << "OpenGL driver vendor: " + toQString(f->glGetString(GL_VENDOR));
out << "OpenGL renderer: " + toQString(f->glGetString(GL_RENDERER));
out << "OpenGL driver version: " + toQString(f->glGetString(GL_VERSION));
return true; return true;
} }
} // namespace } // namespace
#ifndef Q_OS_LINUX
QStringList HardwareInfo::gpuInfo()
{
QStringList info;
vulkanInfo(info);
openGlInfo(info);
return info;
}
#endif #endif
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@ -65,11 +104,7 @@ bool readFromOutput(const char* command, F function)
#endif #endif
#include <QSettings> #include <QSettings>
#include <dxgi1_6.h> #include "windows.h"
#include <windows.h>
#include <wrl/client.h>
using Microsoft::WRL::ComPtr;
QString HardwareInfo::cpuInfo() QString HardwareInfo::cpuInfo()
{ {
@ -105,42 +140,16 @@ uint64_t HardwareInfo::availableRamMiB()
return 0; return 0;
} }
QStringList HardwareInfo::gpuInfo()
{
ComPtr<IDXGIFactory6> factory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(hr)) {
qWarning() << "Could not create DXGI factory:" << Qt::hex << hr;
return { "GPU discovery failed: could not create DXGI factory" };
}
UINT i = 0;
ComPtr<IDXGIAdapter> adapter;
QStringList out;
while (factory->EnumAdapterByGpuPreference(i, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(&adapter)) != DXGI_ERROR_NOT_FOUND) {
DXGI_ADAPTER_DESC desc;
hr = adapter->GetDesc(&desc);
if (SUCCEEDED(hr)) {
out << "GPU: " + QString::fromWCharArray(desc.Description); // NOLINT(*-pro-bounds-array-to-pointer-decay, *-no-array-decay)
} else {
qWarning() << "Could not get DXGI adapter description:" << Qt::hex << hr;
}
++i;
}
return out;
}
#elif defined(Q_OS_MACOS) #elif defined(Q_OS_MACOS)
#include "mach/mach.h"
#include "sys/sysctl.h" #include "sys/sysctl.h"
QString HardwareInfo::cpuInfo() QString HardwareInfo::cpuInfo()
{ {
std::array<char, 512> buffer{}; std::array<char, 512> buffer;
size_t bufferSize = buffer.size(); size_t bufferSize = buffer.size();
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) { if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) {
return { buffer.data() }; return QString(buffer.data());
} }
qWarning() << "Could not get CPU model: sysctlbyname"; qWarning() << "Could not get CPU model: sysctlbyname";
@ -149,7 +158,7 @@ QString HardwareInfo::cpuInfo()
uint64_t HardwareInfo::totalRamMiB() uint64_t HardwareInfo::totalRamMiB()
{ {
uint64_t memsize = 0; uint64_t memsize;
size_t memsizeSize = sizeof memsize; size_t memsizeSize = sizeof memsize;
if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) { if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) {
// transforming bytes -> mib // transforming bytes -> mib
@ -162,62 +171,36 @@ uint64_t HardwareInfo::totalRamMiB()
uint64_t HardwareInfo::availableRamMiB() uint64_t HardwareInfo::availableRamMiB()
{ {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
vm_statistics64_data_t vm_stats;
if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast<host_info64_t>(&vm_stats), &count) == KERN_SUCCESS) {
// transforming bytes -> mib
return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024;
}
qWarning() << "Could not get available RAM: host_statistics64";
return 0; return 0;
} }
MacOSHardwareInfo::MemoryPressureLevel MacOSHardwareInfo::memoryPressureLevel()
{
uint32_t level = 0;
size_t levelSize = sizeof level;
if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &levelSize, nullptr, 0) == 0) {
return static_cast<MemoryPressureLevel>(level);
}
qWarning() << "Could not get memory pressure level: sysctlbyname";
return MemoryPressureLevel::Normal;
}
QString MacOSHardwareInfo::memoryPressureLevelName()
{
// The names are internal, users refer to levels by their graph colors in Activity Monitor
switch (memoryPressureLevel()) {
case MemoryPressureLevel::Normal:
return "Green";
case MemoryPressureLevel::Warning:
return "Yellow";
case MemoryPressureLevel::Critical:
return "Red";
default:
Q_ASSERT(false);
return "";
}
}
QStringList HardwareInfo::gpuInfo()
{
QStringList out;
const bool success = readFromOutput("system_profiler SPDisplaysDataType", [&](const QString& str) {
// Chipset Model: Intel HD Graphics 620
if (str.contains("Chipset Model")) {
out << "GPU: " + afterColon(str);
}
});
if (!success) {
return { "GPU discovery failed: could not read from system_profiler" };
}
return out;
}
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
#include <fstream> #include <fstream>
namespace {
QString afterColon(QString& str)
{
return str.remove(0, str.indexOf(':') + 2).trimmed();
}
} // namespace
QString HardwareInfo::cpuInfo() QString HardwareInfo::cpuInfo()
{ {
std::ifstream cpuin("/proc/cpuinfo"); std::ifstream cpuin("/proc/cpuinfo");
for (std::string line; std::getline(cpuin, line);) { for (std::string line; std::getline(cpuin, line);) {
// model name : AMD Ryzen 7 5800X 8-Core Processor // model name : AMD Ryzen 7 5800X 8-Core Processor
if (const QString str = QString::fromStdString(line); str.startsWith("model name")) { if (QString str = QString::fromStdString(line); str.startsWith("model name")) {
return afterColon(str); return afterColon(str);
} }
} }
@ -226,13 +209,12 @@ QString HardwareInfo::cpuInfo()
return "unknown"; return "unknown";
} }
namespace { uint64_t readMemInfo(QString searchTarget)
uint64_t readMemInfo(const QString& searchTarget)
{ {
std::ifstream memin("/proc/meminfo"); std::ifstream memin("/proc/meminfo");
for (std::string line; std::getline(memin, line);) { for (std::string line; std::getline(memin, line);) {
// MemTotal: 16287480 kB // MemTotal: 16287480 kB
if (const QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { if (QString str = QString::fromStdString(line); str.startsWith(searchTarget)) {
bool ok = false; bool ok = false;
const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok); const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok);
if (!ok) { if (!ok) {
@ -248,7 +230,6 @@ uint64_t readMemInfo(const QString& searchTarget)
qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget; qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget;
return 0; return 0;
} }
} // namespace
uint64_t HardwareInfo::totalRamMiB() uint64_t HardwareInfo::totalRamMiB()
{ {
@ -262,50 +243,52 @@ uint64_t HardwareInfo::availableRamMiB()
QStringList HardwareInfo::gpuInfo() QStringList HardwareInfo::gpuInfo()
{ {
bool readingGpuInfo = false; QStringList list;
QString gpu; const bool vulkanSuccess = vulkanInfo(list);
QString driverInUse = "NONE"; const bool openGlSuccess = openGlInfo(list);
QString driversAvailable = "NONE"; if (vulkanSuccess || openGlSuccess) {
QStringList out; return list;
}
const bool success = readFromOutput("lspci -k", [&](const QString& str) { std::array<char, 512> buffer;
FILE* lspci = popen("lspci -k", "r");
if (!lspci) {
return { "Could not detect GPUs: lspci is not present" };
}
bool readingGpuInfo = false;
QString currentModel = "";
while (fgets(buffer.data(), 512, lspci) != nullptr) {
QString str(buffer.data());
// clang-format off // clang-format off
// 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7) // 04:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Ellesmere [Radeon RX 470/480/570/570X/580/580X/590] (rev e7)
// Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB // Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB
// Kernel driver in use: amdgpu // Kernel driver in use: amdgpu
// Kernel modules: amdgpu // Kernel modules: amdgpu
// clang-format on // clang-format on
if (str.contains("VGA compatible controller") || str.contains("3D controller")) { if (str.contains("VGA compatible controller")) {
readingGpuInfo = true; readingGpuInfo = true;
} else if (!str.startsWith('\t')) { } else if (!str.startsWith('\t')) {
if (readingGpuInfo) {
out << QString("GPU: %1 (driver in use: %2; drivers available: %3)").arg(gpu, driverInUse, driversAvailable);
driverInUse = "NONE";
driversAvailable = "NONE";
}
readingGpuInfo = false; readingGpuInfo = false;
} }
if (!readingGpuInfo) { if (!readingGpuInfo) {
return; continue;
} }
const QString value = afterColon(str);
if (str.contains("Subsystem")) { if (str.contains("Subsystem")) {
gpu = value; currentModel = "Found GPU: " + afterColon(str);
} }
if (str.contains("Kernel driver in use")) { if (str.contains("Kernel driver in use")) {
driverInUse = value; currentModel += " (using driver " + afterColon(str);
} }
if (str.contains("Kernel modules")) { if (str.contains("Kernel modules")) {
driversAvailable = value; currentModel += ", available drivers: " + afterColon(str) + ")";
list.append(currentModel);
} }
});
if (!success) {
return { "GPU discovery failed: could not read from lspci" };
} }
pclose(lspci);
return out; return list;
} }
#else #else
@ -320,20 +303,19 @@ QString HardwareInfo::cpuInfo()
uint64_t HardwareInfo::totalRamMiB() uint64_t HardwareInfo::totalRamMiB()
{ {
uint64_t out = 0; char buff[512];
FILE* fp = popen("sysctl hw.physmem", "r");
if (fp != nullptr) {
if (fgets(buff, 512, fp) != nullptr) {
std::string str(buff);
uint64_t mem = std::stoull(str.substr(12, std::string::npos));
const bool success = readFromOutput("sysctl hw.physmem", [&](const QString& str) { // transforming kib -> mib
const uint64_t mem = str.mid(12).toULong(); return mem / 1024;
}
// transforming kib -> mib
out = mem / 1024;
});
if (!success) {
qWarning() << "Could not get total RAM: could not read from sysctl";
return 0;
} }
return out; return 0;
} }
#else #else
@ -348,8 +330,4 @@ uint64_t HardwareInfo::availableRamMiB()
return 0; return 0;
} }
QStringList HardwareInfo::gpuInfo()
{
return { "GPU discovery failed: not implemented for this OS" };
}
#endif #endif

View file

@ -27,16 +27,3 @@ uint64_t totalRamMiB();
uint64_t availableRamMiB(); uint64_t availableRamMiB();
QStringList gpuInfo(); QStringList gpuInfo();
} // namespace HardwareInfo } // namespace HardwareInfo
#ifdef Q_OS_MACOS
namespace MacOSHardwareInfo {
enum class MemoryPressureLevel : uint8_t {
Normal = 1,
Warning = 2,
Critical = 4,
};
MemoryPressureLevel memoryPressureLevel();
QString memoryPressureLevelName();
} // namespace MacOSHardwareInfo
#endif

View file

@ -3,7 +3,6 @@
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include "Application.h"
#include "InstanceTask.h" #include "InstanceTask.h"
#include "minecraft/MinecraftLoadAndCheck.h" #include "minecraft/MinecraftLoadAndCheck.h"
#include "tasks/SequentialTask.h" #include "tasks/SequentialTask.h"
@ -19,7 +18,7 @@ bool InstanceCreationTask::abort()
return m_gameFilesTask->abort(); return m_gameFilesTask->abort();
} }
return InstanceTask::abort(); return true;
} }
void InstanceCreationTask::executeTask() void InstanceCreationTask::executeTask()
@ -39,9 +38,8 @@ void InstanceCreationTask::executeTask()
m_instance = createInstance(); m_instance = createInstance();
if (!m_instance) { if (!m_instance) {
if (m_abort) { if (m_abort)
return; return;
}
qWarning() << "Instance creation failed!"; qWarning() << "Instance creation failed!";
if (!m_error_message.isEmpty()) { if (!m_error_message.isEmpty()) {
@ -65,9 +63,8 @@ void InstanceCreationTask::executeTask()
qDebug() << "Removing old files"; qDebug() << "Removing old files";
for (const QString& path : m_filesToRemove) { for (const QString& path : m_filesToRemove) {
if (!QFile::exists(path)) { if (!QFile::exists(path))
continue; continue;
}
qDebug() << "Removing" << path; qDebug() << "Removing" << path;
@ -84,10 +81,6 @@ void InstanceCreationTask::executeTask()
} }
if (!m_abort) { if (!m_abort) {
if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) {
emitSucceeded();
return;
}
setAbortable(true); setAbortable(true);
setAbortButtonText(tr("Skip")); setAbortButtonText(tr("Skip"));
qDebug() << "Downloading game files"; qDebug() << "Downloading game files";
@ -117,7 +110,7 @@ void InstanceCreationTask::executeTask()
} }
} }
void InstanceCreationTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled) void InstanceCreationTask::scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled)
{ {
if (path.isEmpty()) { if (path.isEmpty()) {
return; return;

View file

@ -38,7 +38,7 @@ class InstanceCreationTask : public InstanceTask {
protected: protected:
void setError(const QString& message) { m_error_message = message; }; void setError(const QString& message) { m_error_message = message; };
void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false); void scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled = false);
protected: protected:
bool m_abort = false; bool m_abort = false;

View file

@ -40,7 +40,6 @@
#include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
#include "net/NetUtils.h"
#include "ui/InstanceWindow.h" #include "ui/InstanceWindow.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/MSALoginDialog.h" #include "ui/dialogs/MSALoginDialog.h"
@ -110,7 +109,7 @@ void LaunchController::decideAccount()
} }
} }
if (!m_accountToUse && accounts->anyAccountIsValid()) { if (!m_accountToUse) {
// If no default account is set, ask the user which one to use. // If no default account is set, ask the user which one to use.
ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox, ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget); m_parentWidget);
@ -134,6 +133,14 @@ LaunchDecision LaunchController::decideLaunchMode()
return LaunchDecision::Continue; return LaunchDecision::Continue;
} }
if (m_wantedLaunchMode == LaunchMode::Normal) {
if (m_accountToUse->shouldRefresh() || m_accountToUse->accountState() == AccountState::Offline) {
// Force account refresh on the account used to launch the instance updating the AccountState
// only on first try and if it is not meant to be offline
m_accountToUse->refresh();
}
}
const auto* accounts = APPLICATION->accounts(); const auto* accounts = APPLICATION->accounts();
MinecraftAccountPtr accountToCheck = nullptr; MinecraftAccountPtr accountToCheck = nullptr;
@ -156,9 +163,7 @@ LaunchDecision LaunchController::decideLaunchMode()
} }
auto state = accountToCheck->accountState(); auto state = accountToCheck->accountState();
const bool needsRefresh = if (state == AccountState::Unchecked || state == AccountState::Errored) {
m_wantedLaunchMode == LaunchMode::Normal && (state == AccountState::Offline || accountToCheck->shouldRefresh());
if (state == AccountState::Unchecked || state == AccountState::Errored || needsRefresh) {
accountToCheck->refresh(); accountToCheck->refresh();
state = AccountState::Working; state = AccountState::Working;
} }
@ -226,14 +231,13 @@ bool LaunchController::askPlayDemo() const
return box.clickedButton() == demoButton; return box.clickedButton() == demoButton;
} }
QString LaunchController::askOfflineName(const QString& playerName, bool* ok) QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const
{ {
if (ok != nullptr) { if (ok != nullptr) {
*ok = false; *ok = false;
} }
QString title, message; QString message;
title = tr("Player name");
switch (m_actualLaunchMode) { switch (m_actualLaunchMode) {
case LaunchMode::Normal: case LaunchMode::Normal:
Q_ASSERT(false); Q_ASSERT(false);
@ -243,14 +247,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
break; break;
case LaunchMode::Offline: case LaunchMode::Offline:
if (m_wantedLaunchMode == LaunchMode::Normal) { if (m_wantedLaunchMode == LaunchMode::Normal) {
auto netErr = m_accountToUse->accountData()->networkError; message = tr("You are not connected to the Internet, launching in offline mode\n\n");
if (Net::isServerError(netErr)) {
title = tr("Auth servers offline");
message = tr("The Minecraft authentication servers are currently unavailable, launching in offline mode.\n\n");
} else {
title = tr("No internet connection");
message = tr("You are not connected to the Internet, launching in offline mode.\n\n");
}
} }
message += tr("Choose your offline mode player name"); message += tr("Choose your offline mode player name");
break; break;
@ -260,7 +257,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
ChooseOfflineNameDialog dialog(message, m_parentWidget); ChooseOfflineNameDialog dialog(message, m_parentWidget);
dialog.setWindowTitle(title); dialog.setWindowTitle(tr("Player name"));
dialog.setUsername(usedname); dialog.setUsername(usedname);
if (dialog.exec() != QDialog::Accepted) { if (dialog.exec() != QDialog::Accepted) {
return {}; return {};
@ -342,11 +339,11 @@ bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account,
if (button == QMessageBox::StandardButton::Yes) { if (button == QMessageBox::StandardButton::Yes) {
auto* accounts = APPLICATION->accounts(); auto* accounts = APPLICATION->accounts();
const bool isDefault = accounts->defaultAccount() == account; const bool isDefault = accounts->defaultAccount() == account;
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId())));
if (account->accountType() == AccountType::MSA) { if (account->accountType() == AccountType::MSA) {
auto newAccount = MSALoginDialog::newAccount(m_parentWidget); auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
if (newAccount != nullptr) { if (newAccount != nullptr) {
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId())));
accounts->addAccount(newAccount); accounts->addAccount(newAccount);
if (isDefault) { if (isDefault) {

View file

@ -78,7 +78,7 @@ class LaunchController : public Task {
void decideAccount(); void decideAccount();
LaunchDecision decideLaunchMode(); LaunchDecision decideLaunchMode();
bool askPlayDemo() const; bool askPlayDemo() const;
QString askOfflineName(const QString& playerName, bool* ok = nullptr); QString askOfflineName(const QString& playerName, bool* ok = nullptr) const;
bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason); bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason);
private slots: private slots:

View file

@ -114,7 +114,7 @@ void LoggedProcess::on_error(QProcess::ProcessError error)
{ {
switch (error) { switch (error) {
case QProcess::FailedToStart: { case QProcess::FailedToStart: {
emit log({ tr("The process failed to start: %1").arg(errorString()) }, MessageLevel::Fatal); emit log({ tr("The process failed to start.") }, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart); changeState(LoggedProcess::FailedToStart);
break; break;
} }

View file

@ -14,26 +14,26 @@
else \ else \
type = Qt::DirectConnection; type = Qt::DirectConnection;
#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE, RET_DEF) \ #define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \
static RET_TYPE NAME() \ static RET_TYPE NAME() \
{ \ { \
RET_TYPE ret = RET_DEF; \ RET_TYPE ret; \
GET_TYPE() \ GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \
return ret; \ return ret; \
} }
#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE) \ #define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1) \ static RET_TYPE NAME(PARAM_1_TYPE p1) \
{ \ { \
RET_TYPE ret = RET_DEF; \ RET_TYPE ret; \
GET_TYPE() \ GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \
return ret; \ return ret; \
} }
#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE, PARAM_2_TYPE) \ #define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \ static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \
{ \ { \
RET_TYPE ret = RET_DEF; \ RET_TYPE ret; \
GET_TYPE() \ GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \
Q_ARG(PARAM_2_TYPE, p2)); \ Q_ARG(PARAM_2_TYPE, p2)); \
@ -53,18 +53,18 @@ class PixmapCache final : public QObject {
static void setInstance(PixmapCache* i) { s_instance = i; } static void setInstance(PixmapCache* i) { s_instance = i; }
public: public:
DEFINE_FUNC_NO_PARAM(cacheLimit, int, -1) DEFINE_FUNC_NO_PARAM(cacheLimit, int)
DEFINE_FUNC_NO_PARAM(clear, bool, false) DEFINE_FUNC_NO_PARAM(clear, bool)
DEFINE_FUNC_TWO_PARAM(find, bool, false, const QString&, QPixmap*) DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*)
DEFINE_FUNC_TWO_PARAM(find, bool, false, const QPixmapCache::Key&, QPixmap*) DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*)
DEFINE_FUNC_TWO_PARAM(insert, bool, false, const QString&, const QPixmap&) DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, {}, const QPixmap&) DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QString&) DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&)
DEFINE_FUNC_ONE_PARAM(remove, bool, false, const QPixmapCache::Key&) DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&)
DEFINE_FUNC_TWO_PARAM(replace, bool, false, const QPixmapCache::Key&, const QPixmap&) DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&)
DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, false, int) DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int)
DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool, false) DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool)
DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, false, int) DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int)
// NOTE: Every function returns something non-void to simplify the macros. // NOTE: Every function returns something non-void to simplify the macros.
private slots: private slots:

View file

@ -19,52 +19,23 @@
#include "ResourceDownloadTask.h" #include "ResourceDownloadTask.h"
#include <utility>
#include "Application.h" #include "Application.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "modplatform/ModIndex.h"
#include "modplatform/helpers/HashUtils.h" #include "modplatform/helpers/HashUtils.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
namespace {
Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason)
{
auto* mcInstance = dynamic_cast<MinecraftInstance*>(instance);
if (!mcInstance) {
return {};
}
auto* profile = mcInstance->getPackProfile();
if (!profile) {
return {};
}
auto loaders = profile->getModLoadersList();
return {
.reason = std::move(reason),
.gameVersion = profile->getComponentVersion("net.minecraft"),
.loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "",
};
}
} // namespace
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version, ModPlatform::IndexedVersion version,
ResourceFolderModel* packs, ResourceFolderModel* packs,
bool isIndexed, bool is_indexed)
QString downloadReason)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
{ {
if (isIndexed) { if (is_indexed) {
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version)); m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource); connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
@ -74,9 +45,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl)); m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()), auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()));
Net::Download::Option::NoOptions,
createModrinthMeta(m_pack_model->instance(), std::move(downloadReason)));
if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) { if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) {
switch (Hashing::algorithmFromString(m_pack_version.hash_type)) { switch (Hashing::algorithmFromString(m_pack_version.hash_type)) {
case Hashing::Algorithm::Md4: case Hashing::Algorithm::Md4:
@ -113,9 +82,8 @@ void ResourceDownloadTask::downloadSucceeded()
auto oldName = std::get<0>(to_delete); auto oldName = std::get<0>(to_delete);
auto oldFilename = std::get<1>(to_delete); auto oldFilename = std::get<1>(to_delete);
if (oldName.isEmpty() || oldFilename == m_pack_version.fileName) { if (oldName.isEmpty() || oldFilename == m_pack_version.fileName)
return; return;
}
m_pack_model->uninstallResource(oldFilename, true); m_pack_model->uninstallResource(oldFilename, true);
@ -127,9 +95,8 @@ void ResourceDownloadTask::downloadSucceeded()
if (oldConfig.exists() && !newConfig.exists()) { if (oldConfig.exists() && !newConfig.exists()) {
bool success = FS::move(oldConfig.filePath(), newConfig.filePath()); bool success = FS::move(oldConfig.filePath(), newConfig.filePath());
if (!success) { if (!success)
emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName())); emit logWarning(tr("Failed to rename shader config from '%1' to '%2'").arg(oldConfig.fileName(), newConfig.fileName()));
}
} }
} }
} }
@ -137,7 +104,7 @@ void ResourceDownloadTask::downloadSucceeded()
void ResourceDownloadTask::downloadFailed(QString reason) void ResourceDownloadTask::downloadFailed(QString reason)
{ {
m_filesNetJob.reset(); m_filesNetJob.reset();
emitFailed(std::move(reason)); emitFailed(reason);
} }
void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
@ -147,7 +114,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
// This indirection is done so that we don't delete a mod before being sure it was // This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully! // downloaded successfully!
void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename) void ResourceDownloadTask::hasOldResource(QString name, QString filename)
{ {
to_delete = { name, filename }; to_delete = { name, filename };
} }

View file

@ -33,8 +33,7 @@ class ResourceDownloadTask : public SequentialTask {
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version, ModPlatform::IndexedVersion version,
ResourceFolderModel* packs, ResourceFolderModel* packs,
bool isIndexed = true, bool is_indexed = true);
QString downloadReason = "standalone");
const QString& getFilename() const { return m_pack_version.fileName; } const QString& getFilename() const { return m_pack_version.fileName; }
const QVariant& getVersionID() const { return m_pack_version.fileId; } const QVariant& getVersionID() const { return m_pack_version.fileId; }
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; } const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
@ -57,5 +56,5 @@ class ResourceDownloadTask : public SequentialTask {
std::tuple<QString, QString> to_delete{ "", "" }; std::tuple<QString, QString> to_delete{ "", "" };
private slots: private slots:
void hasOldResource(const QString& name, const QString& filename); void hasOldResource(QString name, QString filename);
}; };

View file

@ -179,20 +179,13 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
void JavaChecker::error(QProcess::ProcessError err) void JavaChecker::error(QProcess::ProcessError err)
{ {
if (err == QProcess::FailedToStart) { if (err == QProcess::FailedToStart) {
qDebug() << "Java checker has failed to start:" << process->errorString(); qDebug() << "Java checker has failed to start.";
qDebug() << "Process environment:"; qDebug() << "Process environment:";
qDebug() << process->environment(); qDebug() << process->environment();
qDebug() << "Native environment:"; qDebug() << "Native environment:";
qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); qDebug() << QProcessEnvironment::systemEnvironment().toStringList();
killTimer.stop(); killTimer.stop();
emit checkFinished({ m_path, m_id });
Result result = {
m_path,
m_id,
};
result.errorLog = process->errorString();
result.validity = Result::Validity::Errored;
emit checkFinished(result);
} }
emitSucceeded(); emitSucceeded();
} }

View file

@ -51,9 +51,8 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions)
: BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions) : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions)
{} {}
Task::Ptr JavaInstallList::getLoadTask(bool forceReload) Task::Ptr JavaInstallList::getLoadTask()
{ {
Q_UNUSED(forceReload)
load(); load();
return getCurrentTask(); return getCurrentTask();
} }

View file

@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList {
public: public:
explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false); explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false);
Task::Ptr getLoadTask(bool forceReload = false) override; Task::Ptr getLoadTask() override;
bool isLoaded() override; bool isLoaded() override;
const BaseVersion::Ptr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;

View file

@ -82,12 +82,12 @@ QUrl BaseEntity::url() const
return QUrl(metaOverride).resolved(localFilename()); return QUrl(metaOverride).resolved(localFilename());
} }
Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload) Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{ {
if (m_task && m_task->isRunning()) { if (m_task && m_task->isRunning()) {
return m_task; return m_task;
} }
m_task.reset(new BaseEntityLoadTask(this, mode, forceReload)); m_task.reset(new BaseEntityLoadTask(this, mode));
return m_task; return m_task;
} }
@ -107,9 +107,7 @@ BaseEntity::LoadStatus BaseEntity::status() const
return m_load_status; return m_load_status;
} }
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload) BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
: m_entity(parent), m_mode(mode), m_force_reload(forceReload)
{}
void BaseEntityLoadTask::executeTask() void BaseEntityLoadTask::executeTask()
{ {
@ -127,11 +125,9 @@ void BaseEntityLoadTask::executeTask()
} }
// on online the hash needs to match // on online the hash needs to match
const auto& expected = m_entity->m_sha256; hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
const auto& actual = m_entity->m_file_sha256;
hashMatches = expected == actual;
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) { if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual)); throw Exception("mismatched checksum");
} }
// load local file // load local file
@ -153,18 +149,13 @@ void BaseEntityLoadTask::executeTask()
auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline; auto wasLoadedOffline = m_entity->m_load_status != BaseEntity::LoadStatus::NotLoaded && m_mode == Net::Mode::Offline;
// if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match // if has is not present allways fetch from remote(e.g. the main index file), else only fetch if hash doesn't match
auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches; auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches;
if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) { if (wasLoadedOffline || wasLoadedRemote) {
emitSucceeded(); emitSucceeded();
return; return;
} }
m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network())); m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network()));
auto url = m_entity->url(); auto url = m_entity->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename()); auto entry = APPLICATION->metacache()->resolveEntry("meta", m_entity->localFilename());
if (m_force_reload) {
// clear validators so manual refreshes fetch a fresh body
entry->setETag({});
entry->setRemoteChangedTimestamp({});
}
entry->setStale(true); entry->setStale(true);
auto dl = Net::ApiDownload::makeCached(url, entry); auto dl = Net::ApiDownload::makeCached(url, entry);
/* /*

View file

@ -43,7 +43,7 @@ class BaseEntity {
void setSha256(QString sha256); void setSha256(QString sha256);
virtual void parse(const QJsonObject& obj) = 0; virtual void parse(const QJsonObject& obj) = 0;
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false); [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
protected: protected:
QString m_sha256; // the expected sha256 QString m_sha256; // the expected sha256
@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task {
Q_OBJECT Q_OBJECT
public: public:
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload); explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
~BaseEntityLoadTask() override = default; ~BaseEntityLoadTask() override = default;
virtual void executeTask() override; virtual void executeTask() override;
@ -68,7 +68,6 @@ class BaseEntityLoadTask : public Task {
private: private:
BaseEntity* m_entity; BaseEntity* m_entity;
Net::Mode m_mode; Net::Mode m_mode;
bool m_force_reload = false;
NetJob::Ptr m_task; NetJob::Ptr m_task;
}; };
} // namespace Meta } // namespace Meta

View file

@ -15,7 +15,6 @@
#include "Index.h" #include "Index.h"
#include "Application.h"
#include "JsonFormat.h" #include "JsonFormat.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
#include "VersionList.h" #include "VersionList.h"
@ -136,7 +135,7 @@ void Index::connectVersionList(const int row, const VersionList::Ptr& list)
Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force) Task::Ptr Index::loadVersion(const QString& uid, const QString& version, Net::Mode mode, bool force)
{ {
if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) { if (mode == Net::Mode::Offline) {
return get(uid, version)->loadTask(mode); return get(uid, version)->loadTask(mode);
} }

View file

@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList(
setObjectName("Version list: " + uid); setObjectName("Version list: " + uid);
} }
Task::Ptr VersionList::getLoadTask(bool forceReload) Task::Ptr VersionList::getLoadTask()
{ {
auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid)); auto loadTask = makeShared<SequentialTask>(tr("Load meta for %1", "This is for the task name that loads the meta index.").arg(m_uid));
loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload)); loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online));
loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload)); loadTask->addTask(this->loadTask(Net::Mode::Online));
return loadTask; return loadTask;
} }

View file

@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity {
enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole }; enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole };
bool isLoaded() override; bool isLoaded() override;
Task::Ptr getLoadTask(bool forceReload = false) override; Task::Ptr getLoadTask() override;
const BaseVersion::Ptr at(int i) const override; const BaseVersion::Ptr at(int i) const override;
int count() const override; int count() const override;
void sortVersions() override; void sortVersions() override;

View file

@ -149,7 +149,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
if (sha1.size()) { if (sha1.size()) {
auto dl = Net::ApiDownload::makeCached(url, entry, options); auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1; qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl); out.append(dl);
} else { } else {
out.append(Net::ApiDownload::makeCached(url, entry, options)); out.append(Net::ApiDownload::makeCached(url, entry, options));

View file

@ -131,8 +131,7 @@
for (const auto& gpu : gpus) { for (const auto& gpu : gpus) {
QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]); QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]);
bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]); bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]);
bool discrete = qvariant_cast<bool>(gpu.value(QStringLiteral("Discrete"), !defaultGpu)); if (!defaultGpu) {
if (discrete) {
QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]); QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]);
for (int i = 0; i + 1 < envList.size(); i += 2) { for (int i = 0; i + 1 < envList.size(); i += 2) {
env.insert(envList[i], envList[i + 1]); env.insert(envList[i], envList[i + 1]);
@ -893,14 +892,6 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
QStringList out; QStringList out;
out << "Components:";
for (int i = 0; i < m_components->rowCount(); ++i) {
const auto& component = m_components->getComponent(i);
out << indent +
QString("%1) %2 (%3) %4").arg(QString::number(i + 1), component->getName(), component->getID(), component->getVersion());
}
out << emptyLine;
out << "Launcher: " + getLauncher(); out << "Launcher: " + getLauncher();
out << "Main class: " + getMainClass() << emptyLine; out << "Main class: " + getMainClass() << emptyLine;

View file

@ -7,27 +7,30 @@
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion) VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version)
: m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loaderVersion)) : InstanceCreationTask()
, m_version(std::move(version))
, m_using_loader(true)
, m_loader(std::move(loader))
, m_loader_version(std::move(loader_version))
{} {}
std::unique_ptr<MinecraftInstance> VanillaCreationTask::createInstance() std::unique_ptr<MinecraftInstance> VanillaCreationTask::createInstance()
{ {
setStatus(tr("Creating instance from version %1").arg(m_version->name())); setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto inst = std::make_unique<MinecraftInstance>( auto inst = std::make_unique<MinecraftInstance>(m_globalSettings, std::make_unique<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")),
m_globalSettings, std::make_unique<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath); m_stagingPath);
SettingsObject::Lock lock(inst->settings()); SettingsObject::Lock lock(inst->settings());
auto* components = inst->getPackProfile(); auto components = inst->getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true); components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
if (m_using_loader) { if (m_using_loader)
components->setComponentVersion(m_loader, m_loader_version->descriptor()); components->setComponentVersion(m_loader, m_loader_version->descriptor());
}
inst->setName(name()); inst->setName(name());
inst->setIconKey(m_instIcon); inst->setIconKey(m_instIcon);
components->saveNow();
return inst; return inst;
} }

View file

@ -7,8 +7,8 @@
class VanillaCreationTask final : public InstanceCreationTask { class VanillaCreationTask final : public InstanceCreationTask {
Q_OBJECT Q_OBJECT
public: public:
explicit VanillaCreationTask(BaseVersion::Ptr version) : m_version(std::move(version)) {} VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {}
VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion); VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version);
std::unique_ptr<MinecraftInstance> createInstance() override; std::unique_ptr<MinecraftInstance> createInstance() override;

View file

@ -157,8 +157,7 @@ bool WorldList::resetIcon(int row)
return false; return false;
World& m = m_worlds[row]; World& m = m_worlds[row];
if (m.resetIcon()) { if (m.resetIcon()) {
QModelIndex modelIndex = index(row, NameColumn); emit dataChanged(index(row), index(row), { WorldList::IconFileRole });
emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole });
return true; return true;
} }
return false; return false;
@ -427,7 +426,7 @@ void WorldList::loadWorldsAsync()
m_worlds[row].setSize(size); m_worlds[row].setSize(size);
// Notify views // Notify views
QModelIndex modelIndex = index(row, SizeColumn); QModelIndex modelIndex = index(row);
emit dataChanged(modelIndex, modelIndex, { SizeRole }); emit dataChanged(modelIndex, modelIndex, { SizeRole });
} }
}, },

View file

@ -41,7 +41,6 @@
#include <QDateTime> #include <QDateTime>
#include <QMap> #include <QMap>
#include <QNetworkReply>
#include <QVariantMap> #include <QVariantMap>
enum class Validity { None, Assumed, Certain }; enum class Validity { None, Assumed, Certain };
@ -119,6 +118,5 @@ struct AccountData {
// runtime only information (not saved with the account) // runtime only information (not saved with the account)
QString internalId; QString internalId;
QString errorString; QString errorString;
QNetworkReply::NetworkError networkError = QNetworkReply::NoError;
AccountState accountState = AccountState::Unchecked; AccountState accountState = AccountState::Unchecked;
}; };

View file

@ -648,17 +648,9 @@ void AccountList::tryNext()
while (m_refreshQueue.length()) { while (m_refreshQueue.length()) {
auto accountId = m_refreshQueue.front(); auto accountId = m_refreshQueue.front();
m_refreshQueue.pop_front(); m_refreshQueue.pop_front();
bool found = false;
for (int i = 0; i < count(); i++) { for (int i = 0; i < count(); i++) {
auto account = at(i); auto account = at(i);
if (account->internalId() == accountId) { if (account->internalId() == accountId) {
found = true;
if (!account->shouldRefresh()) {
// Account no longer needs refreshing, skip it.
qDebug() << "RefreshSchedule: Skipping account" << account->profileName() << "with internal ID"
<< accountId << "(no longer needs refresh)";
break;
}
m_currentTask = account->refresh(); m_currentTask = account->refresh();
if (m_currentTask) { if (m_currentTask) {
connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded); connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded);
@ -668,12 +660,9 @@ void AccountList::tryNext()
<< accountId; << accountId;
return; return;
} }
break;
} }
} }
if (!found) { qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found.";
qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found.";
}
} }
// if we get here, no account needed refreshing. Schedule refresh in an hour. // if we get here, no account needed refreshing. Schedule refresh in an hour.
m_refreshTimer->start(1000 * 3600); m_refreshTimer->start(1000 * 3600);

View file

@ -56,11 +56,10 @@ void LauncherLoginStep::onRequestDone(QByteArray* response)
qCDebug(authCredentials()) << *response; qCDebug(authCredentials()) << *response;
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString()));
} }
return; return;

View file

@ -113,12 +113,6 @@ DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& d
void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response) void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response)
{ {
if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) {
qWarning() << "Device authorization failed:" << m_request->error() << m_request->errorString();
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: %1").arg(m_request->errorString()));
return;
}
auto rsp = parseDeviceAuthorizationResponse(*response); auto rsp = parseDeviceAuthorizationResponse(*response);
if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) {
qWarning() << "Device authorization failed:" << rsp.error; qWarning() << "Device authorization failed:" << rsp.error;
@ -126,6 +120,12 @@ void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response)
tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description));
return; return;
} }
if (!m_request->wasSuccessful() || m_request->error() != QNetworkReply::NoError) {
qWarning() << "Device authorization failed:" << *response;
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Failed to retrieve device authorization"));
return;
}
if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) { if (rsp.device_code.isEmpty() || rsp.user_code.isEmpty() || rsp.verification_uri.isEmpty() || rsp.expires_in == 0) {
qWarning() << "Device authorization failed: required fields missing"; qWarning() << "Device authorization failed: required fields missing";
emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing")); emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing"));

View file

@ -52,11 +52,10 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response)
qWarning() << " Response:"; qWarning() << " Response:";
qWarning() << QString::fromUtf8(*response); qWarning() << QString::fromUtf8(*response);
if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE,
tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString()));
} }

View file

@ -2,7 +2,7 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonParseError> #include <QJsonParseError>
#include <utility> #include <QNetworkRequest>
#include "Application.h" #include "Application.h"
#include "Logging.h" #include "Logging.h"
@ -12,7 +12,7 @@
#include "net/Upload.h" #include "net/Upload.h"
XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind)
: AuthStep(data), m_token(token), m_relyingParty(std::move(relyingParty)), m_authorizationKind(std::move(authorizationKind)) : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{} {}
QString XboxAuthorizationStep::describe() QString XboxAuthorizationStep::describe()
@ -22,7 +22,7 @@ QString XboxAuthorizationStep::describe()
void XboxAuthorizationStep::perform() void XboxAuthorizationStep::perform()
{ {
const QString xboxAuthTemplate = R"XXX( QString xbox_auth_template = R"XXX(
{ {
"Properties": { "Properties": {
"SandboxId": "RETAIL", "SandboxId": "RETAIL",
@ -34,13 +34,15 @@ void XboxAuthorizationStep::perform()
"TokenType": "JWT" "TokenType": "JWT"
} }
)XXX"; )XXX";
const auto xboxAuthData = xboxAuthTemplate.arg(m_data->userToken.token, m_relyingParty); auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
// http://xboxlive.com // http://xboxlive.com
const QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
auto headers = QList<Net::HeaderPair>{ { .headerName = "Content-Type", .headerValue = "application/json" }, auto headers = QList<Net::HeaderPair>{
{ .headerName = "Accept", .headerValue = "application/json" }, { "Content-Type", "application/json" },
{ .headerName = "x-xbl-contract-version", .headerValue = "1" } }; { "Accept", "application/json" },
auto [request, response] = Net::Upload::makeByteArray(url, xboxAuthData.toUtf8()); { "x-xbl-contract-version", "1" }
};
auto [request, response] = Net::Upload::makeByteArray(url, xbox_auth_data.toUtf8());
m_request = request; m_request = request;
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers)); m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true); m_request->enableAutoRetry(true);
@ -60,14 +62,15 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response)
qCDebug(authCredentials()) << *response; qCDebug(authCredentials()) << *response;
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (Net::isApplicationError(m_request->error())) {
if (processSTSError(*response)) { if (!processSTSError(*response)) {
return; emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Failed to get authorization for %1 services. Error %2.").arg(m_authorizationKind, m_request->error()));
} else {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
} }
emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, emit finished(AccountTaskState::STATE_OFFLINE,
tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString()));
} }
@ -96,8 +99,8 @@ bool XboxAuthorizationStep::processSTSError(const QByteArray& response)
{ {
if (m_request->error() == QNetworkReply::AuthenticationRequiredError) { if (m_request->error() == QNetworkReply::AuthenticationRequiredError) {
QJsonParseError jsonError; QJsonParseError jsonError;
const QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error != QJsonParseError::NoError) { if (jsonError.error) {
qWarning() << "Cannot parse error XSTS response as JSON:" << jsonError.errorString(); qWarning() << "Cannot parse error XSTS response as JSON:" << jsonError.errorString();
emit finished(AccountTaskState::STATE_FAILED_SOFT, emit finished(AccountTaskState::STATE_FAILED_SOFT,
tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())); tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString()));

View file

@ -56,10 +56,9 @@ void XboxUserStep::onRequestDone(QByteArray* response)
{ {
if (m_request->error() != QNetworkReply::NoError) { if (m_request->error() != QNetworkReply::NoError) {
qWarning() << "Reply error:" << m_request->error(); qWarning() << "Reply error:" << m_request->error();
if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString()));
} else { } else {
m_data->networkError = m_request->error();
emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString()));
} }
return; return;

View file

@ -25,72 +25,22 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan
void EnsureAvailableMemory::executeTask() void EnsureAvailableMemory::executeTask()
{ {
#ifdef Q_OS_MACOS
QString text;
switch (MacOSHardwareInfo::memoryPressureLevel()) {
case MacOSHardwareInfo::MemoryPressureLevel::Normal:
emitSucceeded();
return;
case MacOSHardwareInfo::MemoryPressureLevel::Warning:
text =
tr("The system is under increased memory pressure.\n"
"This may lead to lag or slowdowns.\n"
"If possible, close other applications before continuing.\n\n"
"Launch anyway?");
break;
case MacOSHardwareInfo::MemoryPressureLevel::Critical:
text =
tr("Your system is under critical memory pressure.\n"
"This may lead to severe slowdowns, crashes or system instability.\n"
"It is recommended to close other applications or restart your system.\n\n"
"Launch anyway?");
break;
}
bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning,
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
shouldAbort = dialog->exec() == QMessageBox::No;
dialog->deleteLater();
}
const auto message = tr("The system is under high memory pressure");
if (shouldAbort) {
emit logLine(message, MessageLevel::Fatal);
emitFailed(message);
return;
}
emit logLine(message, MessageLevel::Warning);
emitSucceeded();
#else
const uint64_t available = HardwareInfo::availableRamMiB(); const uint64_t available = HardwareInfo::availableRamMiB();
if (available == 0) { const uint64_t min = m_instance->settings()->get("MinMemAlloc").toUInt();
// could not read const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt();
emitSucceeded(); const uint64_t required = std::max(min, max);
return;
}
const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt(); if (static_cast<double>(required) * 0.9 > static_cast<double>(available)) {
const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt();
const uint64_t max = std::max(settingMin, settingMax);
if (static_cast<double>(max) * 0.9 > static_cast<double>(available)) {
bool shouldAbort = false; bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) { if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable( auto* dialog = CustomMessageBox::selectable(
nullptr, tr("Low free memory"), nullptr, tr("Not enough RAM"),
tr("There might not be enough free RAM to launch this instance with the current memory settings.\n\n" tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n"
"Maximum allocated: %1 MiB\nFree: %2 MiB (out of %3 MiB total)\n\n" "Required: %1 MiB\nAvailable: %2 MiB\n\n"
"Launch anyway? This may cause slowdowns in the game and your system.") "Continue anyway? This may cause slowdowns in the game and your system.")
.arg(max) .arg(required)
.arg(available) .arg(available),
.arg(HardwareInfo::totalRamMiB()),
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No); QMessageBox::StandardButton::No);
@ -109,5 +59,4 @@ void EnsureAvailableMemory::executeTask()
} }
emitSucceeded(); emitSucceeded();
#endif
} }

View file

@ -164,9 +164,9 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
switch (state) { switch (state) {
case LoggedProcess::FailedToStart: { case LoggedProcess::FailedToStart: {
//: Error message displayed if instace can't start //: Error message displayed if instace can't start
const char* reason = QT_TR_NOOP("Could not launch Minecraft: %1"); const char* reason = QT_TR_NOOP("Could not launch Minecraft!");
emit logLine(QString(reason).arg(m_process.errorString()), MessageLevel::Fatal); emit logLine(reason, MessageLevel::Fatal);
emitFailed(tr(reason).arg(m_process.errorString())); emitFailed(tr(reason));
return; return;
} }
case LoggedProcess::Aborted: case LoggedProcess::Aborted:

View file

@ -68,12 +68,7 @@ void PrintInstanceInfo::executeTask()
::runPciconf(log); ::runPciconf(log);
#else #else
log << "CPU: " + HardwareInfo::cpuInfo(); log << "CPU: " + HardwareInfo::cpuInfo();
#ifdef Q_OS_MACOS
log << "Memory pressure level: " + MacOSHardwareInfo::memoryPressureLevelName();
#else
log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB()); log << QString("RAM: %1 MiB (available: %2 MiB)").arg(HardwareInfo::totalRamMiB()).arg(HardwareInfo::availableRamMiB());
#endif
#endif #endif
log.append(HardwareInfo::gpuInfo()); log.append(HardwareInfo::gpuInfo());
log << ""; log << "";

View file

@ -216,7 +216,7 @@ static std::pair<Version, Version> map(int format, const QMap<std::pair<int, int
int DataPack::compare(const Resource& other, SortType type) const int DataPack::compare(const Resource& other, SortType type) const
{ {
const auto& cast_other = static_cast<const DataPack&>(other); const auto& cast_other = static_cast<const DataPack&>(other);
if (type == SortType::PackFormat) { if (type == SortType::PACK_FORMAT) {
auto this_ver = packFormat(); auto this_ver = packFormat();
auto other_ver = cast_other.packFormat(); auto other_ver = cast_other.packFormat();

View file

@ -40,26 +40,25 @@
#include <QIcon> #include <QIcon>
#include <QStyle> #include <QStyle>
#include "Version.h"
#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include "minecraft/mod/tasks/LocalDataPackParseTask.h"
DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{ {
m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size", "File Name" }); m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" });
m_columnNamesTranslated = m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") });
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size"), tr("File Name") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
SortType::Date, SortType::Size, SortType::Filename }; QHeaderView::Interactive };
m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, m_columnsHideable = { false, true, false, true, true };
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true, true };
} }
QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
{ {
if (!validateIndex(index)) { if (!validateIndex(index))
return {}; return {};
}
int row = index.row(); int row = index.row();
int column = index.column(); int column = index.column();
@ -68,13 +67,11 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
case Qt::BackgroundRole: case Qt::BackgroundRole:
return rowBackground(row); return rowBackground(row);
case Qt::DisplayRole: case Qt::DisplayRole:
if (column == PackFormatColumn) { switch (column) {
const auto& resource = at(row); case PackFormatColumn: {
return resource.packFormatStr(); const auto& resource = at(row);
} return resource.packFormatStr();
if (column == SizeColumn) { }
const auto& resource = at(row);
return resource.sizeStr();
} }
break; break;
case Qt::DecorationRole: { case Qt::DecorationRole: {
@ -95,8 +92,6 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32); return QSize(32, 32);
} }
break; break;
default:
break;
} }
// map the columns to the base equivilents // map the columns to the base equivilents
@ -114,14 +109,7 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
case ProviderColumn: case ProviderColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn);
break; break;
case FileNameColumn: // FIXME: there is no size column due to an oversight
mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn);
break;
case SizeColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn);
break;
default:
break;
} }
if (mappedIndex.isValid()) { if (mappedIndex.isValid()) {
@ -141,8 +129,6 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case PackFormatColumn: case PackFormatColumn:
case DateColumn: case DateColumn:
case ImageColumn: case ImageColumn:
case SizeColumn:
case FileNameColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return {}; return {};
@ -159,10 +145,6 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
return tr("The data pack format ID, as well as the Minecraft versions it was designed for."); return tr("The data pack format ID, as well as the Minecraft versions it was designed for.");
case DateColumn: case DateColumn:
return tr("The date and time this data pack was last changed (or added)."); return tr("The date and time this data pack was last changed (or added).");
case SizeColumn:
return tr("The size of the data pack.");
case FileNameColumn:
return tr("The file name of the data pack.");
default: default:
return {}; return {};
} }
@ -178,7 +160,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
int DataPackFolderModel::columnCount(const QModelIndex& parent) const int DataPackFolderModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : NumColumns; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Resource* DataPackFolderModel::createResource(const QFileInfo& file) Resource* DataPackFolderModel::createResource(const QFileInfo& file)
@ -188,5 +170,5 @@ Resource* DataPackFolderModel::createResource(const QFileInfo& file)
Task* DataPackFolderModel::createParseTask(Resource& resource) Task* DataPackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalDataPackParseTask(m_nextResolutionTicket, static_cast<DataPack*>(&resource)); return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast<DataPack*>(&resource));
} }

View file

@ -39,24 +39,16 @@
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "DataPack.h" #include "DataPack.h"
#include "ResourcePack.h"
class DataPackFolderModel : public ResourceFolderModel { class DataPackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns : std::uint8_t { enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS };
ActiveColumn = 0,
ImageColumn,
NameColumn,
PackFormatColumn,
DateColumn,
SizeColumn,
FileNameColumn,
NumColumns
};
explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
QString id() const override { return "datapacks"; } virtual QString id() const override { return "datapacks"; }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
@ -64,7 +56,7 @@ class DataPackFolderModel : public ResourceFolderModel {
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override;
[[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(DataPack) RESOURCE_HELPERS(DataPack)
}; };

View file

@ -40,7 +40,6 @@
#include <QDir> #include <QDir>
#include <QRegularExpression> #include <QRegularExpression>
#include <QString> #include <QString>
#include <algorithm>
#include "MTPixmapCache.h" #include "MTPixmapCache.h"
#include "MetadataHandler.h" #include "MetadataHandler.h"
@ -50,34 +49,6 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
namespace {
int compareVersionLists(const QStringList& leftVersions, const QStringList& rightVersions)
{
const qsizetype commonSize = std::min(leftVersions.size(), rightVersions.size());
for (qsizetype i = 0; i < commonSize; i++) {
const auto leftVersion = Version(leftVersions.at(i).trimmed());
const auto rightVersion = Version(rightVersions.at(i).trimmed());
if (leftVersion > rightVersion)
return 1;
if (leftVersion < rightVersion)
return -1;
}
if (leftVersions.size() > rightVersions.size())
return 1;
if (leftVersions.size() < rightVersions.size())
return -1;
return 0;
}
} // namespace
Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
{ {
m_enabled = (file.suffix() != "disabled"); m_enabled = (file.suffix() != "disabled");
@ -90,18 +61,18 @@ void Mod::setDetails(const ModDetails& details)
int Mod::compare(const Resource& other, SortType type) const int Mod::compare(const Resource& other, SortType type) const
{ {
auto cast_other = dynamic_cast<const Mod*>(&other); auto cast_other = dynamic_cast<Mod const*>(&other);
if (!cast_other) if (!cast_other)
return Resource::compare(other, type); return Resource::compare(other, type);
switch (type) { switch (type) {
default: default:
case SortType::Enabled: case SortType::ENABLED:
case SortType::Name: case SortType::NAME:
case SortType::Date: case SortType::DATE:
case SortType::Size: case SortType::SIZE:
return Resource::compare(other, type); return Resource::compare(other, type);
case SortType::Version: { case SortType::VERSION: {
auto this_ver = Version(version()); auto this_ver = Version(version());
auto other_ver = Version(cast_other->version()); auto other_ver = Version(cast_other->version());
if (this_ver > other_ver) if (this_ver > other_ver)
@ -110,38 +81,38 @@ int Mod::compare(const Resource& other, SortType type) const
return -1; return -1;
break; break;
} }
case SortType::Side: { case SortType::SIDE: {
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive); auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return compare_result; return compare_result;
break; break;
} }
case SortType::McVersions: { case SortType::MC_VERSIONS: {
auto compare_result = compareVersionLists(mcVersions(), cast_other->mcVersions()); auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return compare_result; return compare_result;
break; break;
} }
case SortType::Loaders: { case SortType::LOADERS: {
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive); auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return compare_result; return compare_result;
break; break;
} }
case SortType::ReleaseType: { case SortType::RELEASE_TYPE: {
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive); auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
if (compare_result != 0) if (compare_result != 0)
return compare_result; return compare_result;
break; break;
} }
case SortType::RequiredBy: { case SortType::REQUIRED_BY: {
if (requiredByCount() > cast_other->requiredByCount()) if (requiredByCount() > cast_other->requiredByCount())
return 1; return 1;
if (requiredByCount() < cast_other->requiredByCount()) if (requiredByCount() < cast_other->requiredByCount())
return -1; return -1;
break; break;
} }
case SortType::Requires: { case SortType::REQUIRES: {
if (requiresCount() > cast_other->requiresCount()) if (requiresCount() > cast_other->requiresCount())
return 1; return 1;
if (requiresCount() < cast_other->requiresCount()) if (requiresCount() < cast_other->requiresCount())
@ -226,19 +197,14 @@ auto Mod::side() const -> QString
return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide); return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide);
} }
auto Mod::mcVersions() const -> QStringList auto Mod::mcVersions() const -> QString
{ {
if (metadata()) if (metadata())
return metadata()->mcVersions; return metadata()->mcVersions.join(", ");
return {}; return {};
} }
auto Mod::mcVersionsString() const -> QString
{
return mcVersions().join(", ");
}
auto Mod::releaseType() const -> QString auto Mod::releaseType() const -> QString
{ {
if (metadata()) if (metadata())

View file

@ -68,8 +68,7 @@ class Mod : public Resource {
auto issueTracker() const -> QString; auto issueTracker() const -> QString;
auto side() const -> QString; auto side() const -> QString;
auto loaders() const -> QString; auto loaders() const -> QString;
auto mcVersions() const -> QStringList; auto mcVersions() const -> QString;
auto mcVersionsString() const -> QString;
auto releaseType() const -> QString; auto releaseType() const -> QString;
QStringList dependencies() const; QStringList dependencies() const;

View file

@ -51,39 +51,38 @@
#include <QUrl> #include <QUrl>
#include <QUuid> #include <QUuid>
#include <algorithm> #include <algorithm>
#include <set>
#include "minecraft/Component.h"
#include "minecraft/mod/Resource.h" #include "minecraft/mod/Resource.h"
#include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ResourceFolderModel.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "modplatform/ModIndex.h" #include "modplatform/ModIndex.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{ {
m_columnNames = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); "Minecraft Versions", "Release Type", "Requires", "Required By" });
m_columnNamesTranslated = m_column_names_translated =
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"), QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"),
tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By") });
m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Version, SortType::Date, m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE,
SortType::Provider, SortType::Size, SortType::Side, SortType::Loaders, SortType::McVersions, SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS,
SortType::ReleaseType, SortType::Requires, SortType::RequiredBy, SortType::Filename }; SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY };
m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive }; QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true, true }; m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true };
connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished); connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished);
} }
QVariant ModFolderModel::data(const QModelIndex& index, int role) const QVariant ModFolderModel::data(const QModelIndex& index, int role) const
{ {
if (!validateIndex(index)) { if (!validateIndex(index))
return {}; return {};
}
int row = index.row(); int row = index.row();
int column = index.column(); int column = index.column();
@ -110,7 +109,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
return at(row).loaders(); return at(row).loaders();
} }
case McVersionsColumn: { case McVersionsColumn: {
return at(row).mcVersionsString(); return at(row).mcVersions();
} }
case ReleaseTypeColumn: { case ReleaseTypeColumn: {
return at(row).releaseType(); return at(row).releaseType();
@ -121,8 +120,6 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case RequiresColumn: { case RequiresColumn: {
return at(row).requiresCount(); return at(row).requiresCount();
} }
default:
break;
} }
break; break;
case Qt::DecorationRole: { case Qt::DecorationRole: {
@ -158,11 +155,6 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case SizeColumn: case SizeColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn);
break; break;
case FileNameColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn);
break;
default:
break;
} }
if (mappedIndex.isValid()) { if (mappedIndex.isValid()) {
@ -190,7 +182,6 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case SizeColumn: case SizeColumn:
case RequiredByColumn: case RequiredByColumn:
case RequiresColumn: case RequiresColumn:
case FileNameColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return QVariant(); return QVariant();
@ -222,8 +213,6 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
return tr("For each mod, the number of other mods which depend on it."); return tr("For each mod, the number of other mods which depend on it.");
case RequiresColumn: case RequiresColumn:
return tr("For each mod, the number of other mods it depends on."); return tr("For each mod, the number of other mods it depends on.");
case FileNameColumn:
return tr("The file name of the mod.");
default: default:
return QVariant(); return QVariant();
} }
@ -234,12 +223,12 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
int ModFolderModel::columnCount(const QModelIndex& parent) const int ModFolderModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : NumColumns; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ModFolderModel::createParseTask(Resource& resource) Task* ModFolderModel::createParseTask(Resource& resource)
{ {
return new LocalModParseTask(m_nextResolutionTicket, resource.type(), resource.fileinfo()); return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
} }
bool ModFolderModel::isValid() bool ModFolderModel::isValid()
@ -247,37 +236,35 @@ bool ModFolderModel::isValid()
return m_dir.exists() && m_dir.isReadable(); return m_dir.exists() && m_dir.isReadable();
} }
void ModFolderModel::onParseSucceeded(int ticket, const QString& resourceId) void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{ {
auto iter = m_activeParseTasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_activeParseTasks.constEnd()) { if (iter == m_active_parse_tasks.constEnd())
return; return;
}
int row = m_resourcesIndex[resourceId]; int row = m_resources_index[mod_id];
const auto& parseTask = *iter; auto parse_task = *iter;
auto* castTask = static_cast<LocalModParseTask*>(parseTask.get()); auto cast_task = static_cast<LocalModParseTask*>(parse_task.get());
Q_ASSERT(castTask->token() == ticket); Q_ASSERT(cast_task->token() == ticket);
auto resource = find(resourceId); auto resource = find(mod_id);
auto result = castTask->result(); auto result = cast_task->result();
if (result && resource) { if (result && resource) {
auto* mod = static_cast<Mod*>(resource.get()); auto* mod = static_cast<Mod*>(resource.get());
mod->finishResolvingWithDetails(std::move(result->details)); mod->finishResolvingWithDetails(std::move(result->details));
} }
emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn));
} }
namespace { Mod* findById(QSet<Mod*> mods, QString modId)
Mod* findById(QSet<Mod*> mods, const QString& resourceId)
{ {
auto found = std::ranges::find_if(mods, [resourceId](Mod* m) { return m->mod_id() == resourceId; }); auto found = std::find_if(mods.begin(), mods.end(), [modId](Mod* m) { return m->mod_id() == modId; });
return found != mods.end() ? *found : nullptr; return found != mods.end() ? *found : nullptr;
} }
} // namespace
void ModFolderModel::onParseFinished() void ModFolderModel::onParseFinished()
{ {
@ -290,25 +277,25 @@ void ModFolderModel::onParseFinished()
m_requires.clear(); m_requires.clear();
m_requiredBy.clear(); m_requiredBy.clear();
auto findByProjectID = [mods](const QVariant& modId, ModPlatform::ResourceProvider provider) -> Mod* { auto findByProjectID = [mods](QVariant modId, ModPlatform::ResourceProvider provider) -> Mod* {
auto found = std::ranges::find_if(mods, [modId, provider](Mod* m) { auto found = std::find_if(mods.begin(), mods.end(), [modId, provider](Mod* m) {
return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId; return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId;
}); });
return found != mods.end() ? *found : nullptr; return found != mods.end() ? *found : nullptr;
}; };
for (auto* mod : mods) { for (auto mod : mods) {
auto id = mod->mod_id(); auto id = mod->mod_id();
for (const auto& dep : mod->dependencies()) { for (auto dep : mod->dependencies()) {
auto* d = findById(mods, dep); auto d = findById(mods, dep);
if (d) { if (d) {
m_requires[id] << d; m_requires[id] << d;
m_requiredBy[d->mod_id()] << mod; m_requiredBy[d->mod_id()] << mod;
} }
} }
if (mod->metadata()) { if (mod->metadata()) {
for (const auto& dep : mod->metadata()->dependencies) { for (auto dep : mod->metadata()->dependencies) {
if (dep.type == ModPlatform::DependencyType::REQUIRED) { if (dep.type == ModPlatform::DependencyType::REQUIRED) {
auto* d = findByProjectID(dep.addonId, mod->metadata()->provider); auto d = findByProjectID(dep.addonId, mod->metadata()->provider);
if (d) { if (d) {
m_requires[id] << d; m_requires[id] << d;
m_requiredBy[d->mod_id()] << mod; m_requiredBy[d->mod_id()] << mod;
@ -317,31 +304,30 @@ void ModFolderModel::onParseFinished()
} }
} }
} }
for (auto* mod : mods) { for (auto mod : mods) {
auto id = mod->mod_id(); auto id = mod->mod_id();
if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) {
mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiredByCount(m_requiredBy[id].count());
mod->setRequiresCount(m_requires[id].count()); mod->setRequiresCount(m_requires[id].count());
int row = m_resourcesIndex[mod->internalId()]; int row = m_resources_index[mod->internal_id()];
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
} }
} }
} }
namespace { QSet<Mod*> collectMods(QSet<Mod*> mods, QHash<QString, QSet<Mod*>> relation, std::set<QString>& seen, bool shouldBeEnabled)
QSet<Mod*> collectMods(const QSet<Mod*>& mods, QHash<QString, QSet<Mod*>> relation, std::set<QString>& seen, bool shouldBeEnabled)
{ {
QSet<Mod*> affectedList = {}; QSet<Mod*> affectedList = {};
QSet<Mod*> needToCheck = {}; QSet<Mod*> needToCheck = {};
for (auto* mod : mods) { for (auto mod : mods) {
auto id = mod->mod_id(); auto id = mod->mod_id();
if (!seen.contains(id)) { if (seen.count(id) == 0) {
seen.insert(id); seen.insert(id);
for (auto* affected : relation[id]) { for (auto affected : relation[id]) {
auto affectedId = affected->mod_id(); auto affectedId = affected->mod_id();
if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) { if (findById(mods, affectedId) == nullptr && seen.count(affectedId) == 0) {
seen.insert(affectedId);
if (shouldBeEnabled != affected->enabled()) { if (shouldBeEnabled != affected->enabled()) {
affectedList << affected; affectedList << affected;
} }
@ -356,13 +342,11 @@ QSet<Mod*> collectMods(const QSet<Mod*>& mods, QHash<QString, QSet<Mod*>> relati
} }
return affectedList; return affectedList;
} }
} // namespace
QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action) QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action)
{ {
if (indexes.isEmpty()) { if (indexes.isEmpty())
return {}; return {};
}
QModelIndexList affectedList = {}; QModelIndexList affectedList = {};
auto affectedModsList = selectedMods(indexes); auto affectedModsList = selectedMods(indexes);
@ -382,9 +366,9 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes,
return {}; // this function should not be called with TOGGLE return {}; // this function should not be called with TOGGLE
} }
} }
for (auto* affected : affectedMods) { for (auto affected : affectedMods) {
auto affectedId = affected->mod_id(); auto affectedId = affected->mod_id();
auto row = m_resourcesIndex[affected->internalId()]; auto row = m_resources_index[affected->internal_id()];
affectedList << index(row, 0); affectedList << index(row, 0);
} }
return affectedList; return affectedList;
@ -392,9 +376,8 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes,
bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
{ {
if (indexes.isEmpty()) { if (indexes.isEmpty())
return {}; return {};
}
auto indexedModsList = selectedMods(indexes); auto indexedModsList = selectedMods(indexes);
auto indexedMods = QSet(indexedModsList.begin(), indexedModsList.end()); auto indexedMods = QSet(indexedModsList.begin(), indexedModsList.end());
@ -413,7 +396,7 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc
break; break;
} }
case EnableAction::TOGGLE: { case EnableAction::TOGGLE: {
for (auto* mod : indexedMods) { for (auto mod : indexedMods) {
if (mod->enabled()) { if (mod->enabled()) {
toDisable << mod; toDisable << mod;
} else { } else {
@ -428,10 +411,10 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc
auto requiredToDisable = collectMods(toDisable, m_requiredBy, seen, false); auto requiredToDisable = collectMods(toDisable, m_requiredBy, seen, false);
toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); }); toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); });
auto toList = [this](const QSet<Mod*>& mods) { auto toList = [this](QSet<Mod*> mods) {
QModelIndexList list; QModelIndexList list;
for (auto* mod : mods) { for (auto mod : mods) {
auto row = m_resourcesIndex[mod->internalId()]; auto row = m_resources_index[mod->internal_id()];
list << index(row, 0); list << index(row, 0);
} }
return list; return list;
@ -464,8 +447,8 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc
yesButton = tr("Disable Required"); yesButton = tr("Disable Required");
} }
auto* box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, auto box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning,
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No);
box->button(QMessageBox::No)->setText(noButton); box->button(QMessageBox::No)->setText(noButton);
box->button(QMessageBox::Yes)->setText(yesButton); box->button(QMessageBox::Yes)->setText(yesButton);
auto response = box->exec(); auto response = box->exec();
@ -483,23 +466,21 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc
return disableStatus && enableStatus; return disableStatus && enableStatus;
} }
namespace { QStringList reqToList(QSet<Mod*> l)
QStringList reqToList(const QSet<Mod*>& l)
{ {
QStringList req; QStringList req;
for (auto* m : l) { for (auto m : l) {
req << m->name(); req << m->name();
} }
return req; return req;
} }
} // namespace
QStringList ModFolderModel::requiresList(const QString& id) QStringList ModFolderModel::requiresList(QString id)
{ {
return reqToList(m_requires[id]); return reqToList(m_requires[id]);
} }
QStringList ModFolderModel::requiredByList(const QString& id) QStringList ModFolderModel::requiredByList(QString id)
{ {
return reqToList(m_requiredBy[id]); return reqToList(m_requiredBy[id]);
} }
@ -508,7 +489,7 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes)
{ {
auto deleteInvalid = [](QSet<Mod*>& mods) { auto deleteInvalid = [](QSet<Mod*>& mods) {
for (auto it = mods.begin(); it != mods.end();) { for (auto it = mods.begin(); it != mods.end();) {
auto* mod = *it; auto mod = *it;
// the QFileInfo::exists is used instead of mod->fileinfo().exists // the QFileInfo::exists is used instead of mod->fileinfo().exists
// because the later somehow caches that the file exists // because the later somehow caches that the file exists
if (!mod || !QFileInfo::exists(mod->fileinfo().absoluteFilePath())) { if (!mod || !QFileInfo::exists(mod->fileinfo().absoluteFilePath())) {
@ -519,14 +500,14 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes)
} }
}; };
auto rsp = ResourceFolderModel::deleteResources(indexes); auto rsp = ResourceFolderModel::deleteResources(indexes);
for (auto* mod : allMods()) { for (auto mod : allMods()) {
auto id = mod->mod_id(); auto id = mod->mod_id();
deleteInvalid(m_requiredBy[id]); deleteInvalid(m_requiredBy[id]);
deleteInvalid(m_requires[id]); deleteInvalid(m_requires[id]);
if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) {
mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiredByCount(m_requiredBy[id].count());
mod->setRequiresCount(m_requires[id].count()); mod->setRequiresCount(m_requires[id].count());
int row = m_resourcesIndex[mod->internalId()]; int row = m_resources_index[mod->internal_id()];
emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn));
} }
} }

View file

@ -46,6 +46,7 @@
#include "Mod.h" #include "Mod.h"
#include "ResourceFolderModel.h" #include "ResourceFolderModel.h"
#include "minecraft/Component.h"
#include "minecraft/mod/Resource.h" #include "minecraft/mod/Resource.h"
class BaseInstance; class BaseInstance;
@ -58,7 +59,7 @@ class QFileSystemWatcher;
class ModFolderModel : public ResourceFolderModel { class ModFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns : std::uint8_t { enum Columns {
ActiveColumn = 0, ActiveColumn = 0,
ImageColumn, ImageColumn,
NameColumn, NameColumn,
@ -72,12 +73,11 @@ class ModFolderModel : public ResourceFolderModel {
ReleaseTypeColumn, ReleaseTypeColumn,
RequiresColumn, RequiresColumn,
RequiredByColumn, RequiredByColumn,
FileNameColumn, NUM_COLUMNS
NumColumns
}; };
ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
QString id() const override { return "mods"; } virtual QString id() const override { return "mods"; }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
@ -85,7 +85,7 @@ class ModFolderModel : public ResourceFolderModel {
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
[[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; [[nodiscard]] Task* createParseTask(Resource&) override;
bool isValid(); bool isValid();
@ -97,11 +97,11 @@ class ModFolderModel : public ResourceFolderModel {
RESOURCE_HELPERS(Mod) RESOURCE_HELPERS(Mod)
public: public:
QStringList requiresList(const QString& id); QStringList requiresList(QString id);
QStringList requiredByList(const QString& id); QStringList requiredByList(QString id);
private slots: private slots:
void onParseSucceeded(int ticket, const QString& resourceId) override; void onParseSucceeded(int ticket, QString resource_id) override;
void onParseFinished(); void onParseFinished();
private: private:

View file

@ -4,75 +4,71 @@
#include <QFileInfo> #include <QFileInfo>
#include <QRegularExpression> #include <QRegularExpression>
#include <tuple> #include <tuple>
#include <utility>
#include "FileSystem.h" #include "FileSystem.h"
#include "StringUtils.h" #include "StringUtils.h"
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
Resource::Resource(QObject* parent) : QObject(parent), m_size_info(0) {} Resource::Resource(QObject* parent) : QObject(parent) {}
Resource::Resource(QFileInfo fileInfo) : m_size_info(0) Resource::Resource(QFileInfo file_info) : QObject()
{ {
setFile(fileInfo); setFile(file_info);
} }
void Resource::setFile(QFileInfo fileInfo) void Resource::setFile(QFileInfo file_info)
{ {
m_file_info = std::move(fileInfo); m_file_info = file_info;
parseFile(); parseFile();
} }
namespace { static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
{ {
if (file.isDir()) { if (file.isDir()) {
auto dir = QDir(file.absoluteFilePath()); auto dir = QDir(file.absoluteFilePath());
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
auto count = dir.count(); auto count = dir.count();
auto str = QObject::tr("item"); auto str = QObject::tr("item");
if (count != 1) { if (count != 1)
str = QObject::tr("items"); str = QObject::tr("items");
}
return { QString("%1 %2").arg(QString::number(count), str), count }; return { QString("%1 %2").arg(QString::number(count), str), count };
} }
return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; return { StringUtils::humanReadableFileSize(file.size(), true), file.size() };
} }
} // namespace
void Resource::parseFile() void Resource::parseFile()
{ {
QString fileName{ m_file_info.fileName() }; QString file_name{ m_file_info.fileName() };
m_type = ResourceType::UNKNOWN; m_type = ResourceType::UNKNOWN;
m_internal_id = fileName; m_internal_id = file_name;
std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info);
if (m_file_info.isDir()) { if (m_file_info.isDir()) {
m_type = ResourceType::FOLDER; m_type = ResourceType::FOLDER;
m_name = fileName; m_name = file_name;
} else if (m_file_info.isFile()) { } else if (m_file_info.isFile()) {
if (fileName.endsWith(".disabled")) { if (file_name.endsWith(".disabled")) {
fileName.chop(9); file_name.chop(9);
m_enabled = false; m_enabled = false;
} }
if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) { if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) {
m_type = ResourceType::ZIPFILE; m_type = ResourceType::ZIPFILE;
fileName.chop(4); file_name.chop(4);
} else if (fileName.endsWith(".nilmod")) { } else if (file_name.endsWith(".nilmod")) {
m_type = ResourceType::ZIPFILE; m_type = ResourceType::ZIPFILE;
fileName.chop(7); file_name.chop(7);
} else if (fileName.endsWith(".litemod")) { } else if (file_name.endsWith(".litemod")) {
m_type = ResourceType::LITEMOD; m_type = ResourceType::LITEMOD;
fileName.chop(8); file_name.chop(8);
} else { } else {
m_type = ResourceType::SINGLEFILE; m_type = ResourceType::SINGLEFILE;
} }
m_name = fileName; m_name = file_name;
} }
m_changed_date_time = m_file_info.lastModified(); m_changed_date_time = m_file_info.lastModified();
@ -80,45 +76,39 @@ void Resource::parseFile()
auto Resource::name() const -> QString auto Resource::name() const -> QString
{ {
if (metadata()) { if (metadata())
return metadata()->name; return metadata()->name;
}
return m_name; return m_name;
} }
namespace { static void removeThePrefix(QString& string)
void removeThePrefix(QString& string)
{ {
static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
string.remove(s_regex); string.remove(s_regex);
string = string.trimmed(); string = string.trimmed();
} }
} // namespace
auto Resource::provider() const -> QString auto Resource::provider() const -> QString
{ {
if (metadata()) { if (metadata())
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
}
return tr("Unknown"); return tr("Unknown");
} }
auto Resource::homepage() const -> QString auto Resource::homepage() const -> QString
{ {
if (metadata()) { if (metadata())
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
}
return {}; return {};
} }
void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata) void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
{ {
if (status() == ResourceStatus::NoMetadata) { if (status() == ResourceStatus::NO_METADATA)
setStatus(ResourceStatus::Installed); setStatus(ResourceStatus::INSTALLED);
}
m_metadata = metadata; m_metadata = metadata;
} }
@ -143,12 +133,12 @@ void Resource::updateIssues(const BaseInstance* inst)
return; return;
} }
const auto* mcInst = dynamic_cast<const MinecraftInstance*>(inst); auto mcInst = dynamic_cast<const MinecraftInstance*>(inst);
if (mcInst == nullptr) { if (mcInst == nullptr) {
return; return;
} }
auto* profile = mcInst->getPackProfile(); auto profile = mcInst->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft"); QString mcVersion = profile->getComponentVersion("net.minecraft");
if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) { if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) {
@ -161,59 +151,46 @@ int Resource::compare(const Resource& other, SortType type) const
{ {
switch (type) { switch (type) {
default: default:
case SortType::Enabled: case SortType::ENABLED:
if (enabled() && !other.enabled()) { if (enabled() && !other.enabled())
return 1; return 1;
} if (!enabled() && other.enabled())
if (!enabled() && other.enabled()) {
return -1; return -1;
}
break; break;
case SortType::Name: { case SortType::NAME: {
QString thisName{ name() }; QString this_name{ name() };
QString otherName{ other.name() }; QString other_name{ other.name() };
// TODO do we need this? it could result in 0 being returned // TODO do we need this? it could result in 0 being returned
removeThePrefix(thisName); removeThePrefix(this_name);
removeThePrefix(otherName); removeThePrefix(other_name);
return QString::compare(thisName, otherName, Qt::CaseInsensitive); return QString::compare(this_name, other_name, Qt::CaseInsensitive);
} }
case SortType::Date: case SortType::DATE:
if (dateTimeChanged() > other.dateTimeChanged()) { if (dateTimeChanged() > other.dateTimeChanged())
return 1; return 1;
} if (dateTimeChanged() < other.dateTimeChanged())
if (dateTimeChanged() < other.dateTimeChanged()) {
return -1; return -1;
}
break; break;
case SortType::Filename: case SortType::SIZE: {
return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName());
case SortType::Size: {
if (this->type() != other.type()) { if (this->type() != other.type()) {
if (this->type() == ResourceType::FOLDER) { if (this->type() == ResourceType::FOLDER)
return -1; return -1;
} if (other.type() == ResourceType::FOLDER)
if (other.type() == ResourceType::FOLDER) {
return 1; return 1;
}
} }
if (sizeInfo() > other.sizeInfo()) { if (sizeInfo() > other.sizeInfo())
return 1; return 1;
} if (sizeInfo() < other.sizeInfo())
if (sizeInfo() < other.sizeInfo()) {
return -1; return -1;
}
break; break;
} }
case SortType::PROVIDER: {
case SortType::Provider: { auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
auto compareResult = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); if (compare_result != 0)
if (compareResult != 0) { return compare_result;
return compareResult;
}
break; break;
} }
} }
@ -223,20 +200,13 @@ int Resource::compare(const Resource& other, SortType type) const
bool Resource::applyFilter(QRegularExpression filter) const bool Resource::applyFilter(QRegularExpression filter) const
{ {
if (filter.match(name()).hasMatch()) { return filter.match(name()).hasMatch();
return true;
}
if (filter.match(fileinfo().fileName()).hasMatch()) {
return true;
}
return false;
} }
bool Resource::enable(EnableAction action) bool Resource::enable(EnableAction action)
{ {
if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) { if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER)
return false; return false;
}
QString path = m_file_info.absoluteFilePath(); QString path = m_file_info.absoluteFilePath();
QFile file(path); QFile file(path);
@ -255,16 +225,14 @@ bool Resource::enable(EnableAction action)
break; break;
} }
if (m_enabled == enable) { if (m_enabled == enable)
return false; return false;
}
if (enable) { if (enable) {
// m_enabled is false, but there's no '.disabled' suffix. // m_enabled is false, but there's no '.disabled' suffix.
// TODO: Report error? // TODO: Report error?
if (!path.endsWith(".disabled")) { if (!path.endsWith(".disabled"))
return false; return false;
}
path.chop(9); path.chop(9);
} else { } else {
path += ".disabled"; path += ".disabled";
@ -272,9 +240,8 @@ bool Resource::enable(EnableAction action)
path = FS::getUniqueResourceName(path); path = FS::getUniqueResourceName(path);
} }
} }
if (!file.rename(path)) { if (!file.rename(path))
return false; return false;
}
setFile(QFileInfo(path)); setFile(QFileInfo(path));
@ -282,34 +249,33 @@ bool Resource::enable(EnableAction action)
return true; return true;
} }
auto Resource::destroy(const QDir& indexDir, bool preserveMetadata, bool attemptTrash) -> bool auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{ {
m_type = ResourceType::UNKNOWN; m_type = ResourceType::UNKNOWN;
if (!preserveMetadata) { if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(indexDir); destroyMetadata(index_dir);
} }
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
} }
auto Resource::destroyMetadata(const QDir& indexDir) -> void auto Resource::destroyMetadata(const QDir& index_dir) -> void
{ {
if (metadata()) { if (metadata()) {
Metadata::remove(indexDir, metadata()->slug); Metadata::remove(index_dir, metadata()->slug);
} else { } else {
auto n = name(); auto n = name();
Metadata::remove(indexDir, n); Metadata::remove(index_dir, n);
} }
m_metadata = nullptr; m_metadata = nullptr;
} }
bool Resource::isSymLinkUnder(const QString& instPath) const bool Resource::isSymLinkUnder(const QString& instPath) const
{ {
if (isSymLink()) { if (isSymLink())
return true; return true;
}
auto instDir = QDir(instPath); auto instDir = QDir(instPath);
@ -327,9 +293,8 @@ bool Resource::isMoreThanOneHardLink() const
auto Resource::getOriginalFileName() const -> QString auto Resource::getOriginalFileName() const -> QString
{ {
auto fileName = m_file_info.fileName(); auto fileName = m_file_info.fileName();
if (!m_enabled) { if (!m_enabled)
fileName.chop(9); fileName.chop(9);
}
return fileName; return fileName;
} }
@ -359,16 +324,16 @@ QDebug operator<<(QDebug debug, ResourceType type)
QDebug operator<<(QDebug debug, ResourceStatus status) QDebug operator<<(QDebug debug, ResourceStatus status)
{ {
switch (status) { switch (status) {
case ResourceStatus::Installed: case ResourceStatus::INSTALLED:
debug << "INSTALLED"; debug << "INSTALLED";
break; break;
case ResourceStatus::NotInstalled: case ResourceStatus::NOT_INSTALLED:
debug << "NOT_INSTALLED"; debug << "NOT_INSTALLED";
break; break;
case ResourceStatus::NoMetadata: case ResourceStatus::NO_METADATA:
debug << "NO_METADATA"; debug << "NO_METADATA";
break; break;
case ResourceStatus::Unknown: case ResourceStatus::UNKNOWN:
default: default:
debug << "UNKNOWN"; debug << "UNKNOWN";
break; break;

View file

@ -45,7 +45,7 @@
class BaseInstance; class BaseInstance;
enum class ResourceType : std::uint8_t { enum class ResourceType {
UNKNOWN, //!< Indicates an unspecified resource type. UNKNOWN, //!< Indicates an unspecified resource type.
ZIPFILE, //!< The resource is a zip file containing the resource's class files. ZIPFILE, //!< The resource is a zip file containing the resource's class files.
SINGLEFILE, //!< The resource is a single file (not a zip file). SINGLEFILE, //!< The resource is a single file (not a zip file).
@ -55,33 +55,32 @@ enum class ResourceType : std::uint8_t {
QDebug operator<<(QDebug debug, ResourceType type); QDebug operator<<(QDebug debug, ResourceType type);
enum class ResourceStatus : std::uint8_t { enum class ResourceStatus {
Installed, // Both JAR and Metadata are present INSTALLED, // Both JAR and Metadata are present
NotInstalled, // Only the Metadata is present NOT_INSTALLED, // Only the Metadata is present
NoMetadata, // Only the JAR is present NO_METADATA, // Only the JAR is present
Unknown, // Default status UNKNOWN, // Default status
}; };
QDebug operator<<(QDebug debug, ResourceStatus status); QDebug operator<<(QDebug debug, ResourceStatus status);
enum class SortType : std::uint8_t { enum class SortType {
Name, NAME,
Date, DATE,
Version, VERSION,
Enabled, ENABLED,
PackFormat, PACK_FORMAT,
Provider, PROVIDER,
Size, SIZE,
Side, SIDE,
McVersions, MC_VERSIONS,
Loaders, LOADERS,
ReleaseType, RELEASE_TYPE,
Requires, REQUIRES,
RequiredBy, REQUIRED_BY,
Filename,
}; };
enum class EnableAction : std::uint8_t { ENABLE, DISABLE, TOGGLE }; enum class EnableAction { ENABLE, DISABLE, TOGGLE };
/** General class for managed resources. It mirrors a file in disk, with some more info /** General class for managed resources. It mirrors a file in disk, with some more info
* for display and house-keeping purposes. * for display and house-keeping purposes.
@ -93,19 +92,20 @@ class Resource : public QObject {
Q_DISABLE_COPY(Resource) Q_DISABLE_COPY(Resource)
public: public:
using Ptr = shared_qobject_ptr<Resource>; using Ptr = shared_qobject_ptr<Resource>;
using WeakPtr = QPointer<Resource>;
Resource(QObject* parent = nullptr); Resource(QObject* parent = nullptr);
Resource(QFileInfo fileInfo); Resource(QFileInfo file_info);
Resource(const QString& filePath) : Resource(QFileInfo(filePath)) {} Resource(QString file_path) : Resource(QFileInfo(file_path)) {}
~Resource() override = default; ~Resource() override = default;
void setFile(QFileInfo fileInfo); void setFile(QFileInfo file_info);
void parseFile(); void parseFile();
auto fileinfo() const -> QFileInfo { return m_file_info; } auto fileinfo() const -> QFileInfo { return m_file_info; }
auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; }
auto internalId() const -> QString { return m_internal_id; } auto internal_id() const -> QString { return m_internal_id; }
auto type() const -> ResourceType { return m_type; } auto type() const -> ResourceType { return m_type; }
bool enabled() const { return m_enabled; } bool enabled() const { return m_enabled; }
auto getOriginalFileName() const -> QString; auto getOriginalFileName() const -> QString;
@ -138,7 +138,7 @@ class Resource : public QObject {
* = 0: 'this' is equal to 'other' * = 0: 'this' is equal to 'other'
* < 0: 'this' comes before 'other' * < 0: 'this' comes before 'other'
*/ */
virtual int compare(const Resource& other, SortType type = SortType::Name) const; virtual int compare(const Resource& other, SortType type = SortType::NAME) const;
/** Returns whether the given filter should filter out 'this' (false), /** Returns whether the given filter should filter out 'this' (false),
* or if such filter includes the Resource (true). * or if such filter includes the Resource (true).
@ -163,9 +163,9 @@ class Resource : public QObject {
} }
// Delete all files of this resource. // Delete all files of this resource.
auto destroy(const QDir& indexDir, bool preserveMetadata = false, bool attemptTrash = true) -> bool; auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
// Delete the metadata only. // Delete the metadata only.
auto destroyMetadata(const QDir& indexDir) -> void; auto destroyMetadata(const QDir& index_dir) -> void;
auto isSymLink() const -> bool { return m_file_info.isSymLink(); } auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
@ -195,7 +195,7 @@ class Resource : public QObject {
ResourceType m_type = ResourceType::UNKNOWN; ResourceType m_type = ResourceType::UNKNOWN;
/* Installation status of the resource. */ /* Installation status of the resource. */
ResourceStatus m_status = ResourceStatus::Unknown; ResourceStatus m_status = ResourceStatus::UNKNOWN;
std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr; std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr;

View file

@ -11,7 +11,6 @@
#include <QStyle> #include <QStyle>
#include <QThreadPool> #include <QThreadPool>
#include <QUrl> #include <QUrl>
#include <algorithm>
#include <utility> #include <utility>
#include "Application.h" #include "Application.h"
@ -28,10 +27,10 @@
#include "tasks/Task.h" #include "tasks/Task.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_isIndexed(isIndexed) : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
{ {
if (createDir) { if (create_dir) {
FS::ensureFolderPathExists(m_dir.absolutePath()); FS::ensureFolderPathExists(m_dir.absolutePath());
} }
@ -50,75 +49,70 @@ ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance
ResourceFolderModel::~ResourceFolderModel() ResourceFolderModel::~ResourceFolderModel()
{ {
while (!QThreadPool::globalInstance()->waitForDone(100)) { while (!QThreadPool::globalInstance()->waitForDone(100))
QCoreApplication::processEvents(); QCoreApplication::processEvents();
}
} }
bool ResourceFolderModel::startWatching(const QStringList& paths) bool ResourceFolderModel::startWatching(const QStringList& paths)
{ {
// Remove orphaned metadata next time // Remove orphaned metadata next time
m_firstFolderLoad = true; m_first_folder_load = true;
if (m_isWatching) { if (m_is_watching)
return false; return false;
}
auto couldntBeWatched = m_watcher.addPaths(paths); auto couldnt_be_watched = m_watcher.addPaths(paths);
for (const auto& path : paths) { for (auto path : paths) {
if (couldntBeWatched.contains(path)) { if (couldnt_be_watched.contains(path))
qDebug() << "Failed to start watching" << path; qDebug() << "Failed to start watching" << path;
} else { else
qDebug() << "Started watching" << path; qDebug() << "Started watching" << path;
}
} }
update(); update();
m_isWatching = !m_isWatching; m_is_watching = !m_is_watching;
return m_isWatching; return m_is_watching;
} }
bool ResourceFolderModel::stopWatching(const QStringList& paths) bool ResourceFolderModel::stopWatching(const QStringList& paths)
{ {
if (!m_isWatching) { if (!m_is_watching)
return false; return false;
}
auto couldntBeStopped = m_watcher.removePaths(paths); auto couldnt_be_stopped = m_watcher.removePaths(paths);
for (const auto& path : paths) { for (auto path : paths) {
if (couldntBeStopped.contains(path)) { if (couldnt_be_stopped.contains(path))
qDebug() << "Failed to stop watching" << path; qDebug() << "Failed to stop watching" << path;
} else { else
qDebug() << "Stopped watching" << path; qDebug() << "Stopped watching" << path;
}
} }
m_isWatching = !m_isWatching; m_is_watching = !m_is_watching;
return !m_isWatching; return !m_is_watching;
} }
bool ResourceFolderModel::installResource(QString originalPath) bool ResourceFolderModel::installResource(QString original_path)
{ {
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
originalPath = FS::NormalizePath(originalPath); original_path = FS::NormalizePath(original_path);
QFileInfo fileInfo(originalPath); QFileInfo file_info(original_path);
if (!fileInfo.exists() || !fileInfo.isReadable()) { if (!file_info.exists() || !file_info.isReadable()) {
qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path;
return false; return false;
} }
qDebug() << "Installing:" << fileInfo.absoluteFilePath(); qDebug() << "Installing:" << file_info.absoluteFilePath();
Resource resource(fileInfo); Resource resource(file_info);
if (!resource.valid()) { if (!resource.valid()) {
qWarning() << originalPath << "is not a valid resource. Ignoring it."; qWarning() << original_path << "is not a valid resource. Ignoring it.";
return false; return false;
} }
auto newPath = FS::NormalizePath(m_dir.filePath(fileInfo.fileName())); auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName()));
if (originalPath == newPath) { if (original_path == new_path) {
qWarning() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense...";
return false; return false;
} }
@ -126,47 +120,45 @@ bool ResourceFolderModel::installResource(QString originalPath)
case ResourceType::SINGLEFILE: case ResourceType::SINGLEFILE:
case ResourceType::ZIPFILE: case ResourceType::ZIPFILE:
case ResourceType::LITEMOD: { case ResourceType::LITEMOD: {
if (QFile::exists(newPath) || QFile::exists(newPath + QString(".disabled"))) { if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) {
if (!FS::deletePath(newPath)) { if (!FS::deletePath(new_path)) {
qCritical() << "Cleaning up new location (" << newPath << ") was unsuccessful!"; qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!";
return false; return false;
} }
qDebug() << newPath << "has been deleted."; qDebug() << new_path << "has been deleted.";
} }
if (!QFile::copy(originalPath, newPath)) { if (!QFile::copy(original_path, new_path)) {
qCritical() << "Copy from" << originalPath << "to" << newPath << "has failed."; qCritical() << "Copy from" << original_path << "to" << new_path << "has failed.";
return false; return false;
} }
FS::updateTimestamp(newPath); FS::updateTimestamp(new_path);
QFileInfo newPathFileInfo(newPath); QFileInfo new_path_file_info(new_path);
resource.setFile(newPathFileInfo); resource.setFile(new_path_file_info);
if (!m_isWatching) { if (!m_is_watching)
return update(); return update();
}
return true; return true;
} }
case ResourceType::FOLDER: { case ResourceType::FOLDER: {
if (QFile::exists(newPath)) { if (QFile::exists(new_path)) {
qDebug() << "Ignoring folder '" << originalPath << "', it would merge with" << newPath; qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path;
return false; return false;
} }
if (!FS::copy(originalPath, newPath)()) { if (!FS::copy(original_path, new_path)()) {
qWarning() << "Copy of folder from" << originalPath << "to" << newPath << "has (potentially partially) failed."; qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed.";
return false; return false;
} }
QFileInfo newpathInfo(newPath); QFileInfo newpathInfo(new_path);
resource.setFile(newpathInfo); resource.setFile(newpathInfo);
if (!m_isWatching) { if (!m_is_watching)
return update(); return update();
}
return true; return true;
} }
@ -176,24 +168,24 @@ bool ResourceFolderModel::installResource(QString originalPath)
return false; return false;
} }
void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers) void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
{ {
auto install = [this, path] { installResource(path); }; auto install = [this, path] { installResource(std::move(path)); };
if (vers.addonId.isValid()) { if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{ ModPlatform::IndexedPack pack{
.addonId = vers.addonId, vers.addonId,
.provider = ModPlatform::ResourceProvider::FLAME, ModPlatform::ResourceProvider::FLAME,
}; };
auto [job, response] = FlameAPI().getProject(vers.addonId.toString()); auto [job, response] = FlameAPI().getProject(vers.addonId.toString());
connect(job.get(), &Task::failed, this, install); connect(job.get(), &Task::failed, this, install);
connect(job.get(), &Task::aborted, this, install); connect(job.get(), &Task::aborted, this, install);
connect(job.get(), &Task::succeeded, [response, this, &vers, install, &pack] { connect(job.get(), &Task::succeeded, [response, this, &vers, install, &pack] {
QJsonParseError parseError{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parseError.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at" << parseError.offset qWarning() << "Error while parsing JSON response for mod info at" << parse_error.offset
<< "reason:" << parseError.errorString(); << "reason:" << parse_error.errorString();
qDebug() << *response; qDebug() << *response;
return; return;
} }
@ -204,9 +196,9 @@ void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path,
qDebug() << doc; qDebug() << doc;
qWarning() << "Error while reading mod info:" << e.cause(); qWarning() << "Error while reading mod info:" << e.cause();
} }
LocalResourceUpdateTask updateMetadata(indexDir(), pack, vers); LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
connect(&updateMetadata, &Task::finished, this, install); connect(&update_metadata, &Task::finished, this, install);
updateMetadata.start(); update_metadata.start();
}); });
job->start(); job->start();
@ -215,7 +207,7 @@ void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path,
} }
} }
bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preserveMetadata) bool ResourceFolderModel::uninstallResource(const QString& file_name, bool preserve_metadata)
{ {
for (auto& resource : m_resources) { for (auto& resource : m_resources) {
auto resourceFileInfo = resource->fileinfo(); auto resourceFileInfo = resource->fileinfo();
@ -224,8 +216,8 @@ bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preser
resourceFileName.chop(9); resourceFileName.chop(9);
} }
if (resourceFileName == fileName) { if (resourceFileName == file_name) {
auto res = resource->destroy(indexDir(), preserveMetadata, false); auto res = resource->destroy(indexDir(), preserve_metadata, false);
update(); update();
@ -237,16 +229,14 @@ bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preser
bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
{ {
if (indexes.isEmpty()) { if (indexes.isEmpty())
return true; return true;
}
for (auto i : indexes) { for (auto i : indexes) {
if (i.column() != 0) { if (i.column() != 0)
continue; continue;
}
const auto& resource = m_resources.at(i.row()); auto& resource = m_resources.at(i.row());
resource->destroy(indexDir()); resource->destroy(indexDir());
} }
@ -257,16 +247,14 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
{ {
if (indexes.isEmpty()) { if (indexes.isEmpty())
return; return;
}
for (auto i : indexes) { for (auto i : indexes) {
if (i.column() != 0) { if (i.column() != 0)
continue; continue;
}
const auto& resource = m_resources.at(i.row()); auto& resource = m_resources.at(i.row());
resource->destroyMetadata(indexDir()); resource->destroyMetadata(indexDir());
} }
@ -283,36 +271,33 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
->exec(); ->exec();
if (response != QMessageBox::Yes) { if (response != QMessageBox::Yes)
return false; return false;
}
} }
if (indexes.isEmpty()) { if (indexes.isEmpty())
return true; return true;
}
bool succeeded = true; bool succeeded = true;
for (const auto& idx : indexes) { for (auto const& idx : indexes) {
if (!validateIndex(idx) || idx.column() != 0) { if (!validateIndex(idx) || idx.column() != 0)
continue; continue;
}
int row = idx.row(); int row = idx.row();
auto& resource = m_resources[row]; auto& resource = m_resources[row];
// Preserve the row, but change its ID // Preserve the row, but change its ID
auto oldId = resource->internalId(); auto old_id = resource->internal_id();
if (!resource->enable(action)) { if (!resource->enable(action)) {
succeeded = false; succeeded = false;
continue; continue;
} }
auto newId = resource->internalId(); auto new_id = resource->internal_id();
m_resourcesIndex.remove(oldId); m_resources_index.remove(old_id);
m_resourcesIndex[newId] = row; m_resources_index[new_id] = row;
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
} }
@ -327,25 +312,24 @@ bool ResourceFolderModel::update()
QMutexLocker lock(&s_update_task_mutex); QMutexLocker lock(&s_update_task_mutex);
// Already updating, so we schedule a future update and return. // Already updating, so we schedule a future update and return.
if (m_currentUpdateTask) { if (m_current_update_task) {
m_scheduledUpdate = true; m_scheduled_update = true;
return false; return false;
} }
m_currentUpdateTask.reset(createUpdateTask()); m_current_update_task.reset(createUpdateTask());
if (!m_currentUpdateTask) { if (!m_current_update_task)
return false; return false;
}
connect(m_currentUpdateTask.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
Qt::ConnectionType::QueuedConnection); Qt::ConnectionType::QueuedConnection);
connect(m_currentUpdateTask.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
connect( connect(
m_currentUpdateTask.get(), &Task::finished, this, m_current_update_task.get(), &Task::finished, this,
[this] { [this] {
m_currentUpdateTask.reset(); m_current_update_task.reset();
if (m_scheduledUpdate) { if (m_scheduled_update) {
m_scheduledUpdate = false; m_scheduled_update = false;
update(); update();
} else { } else {
emit updateFinished(); emit updateFinished();
@ -356,16 +340,16 @@ bool ResourceFolderModel::update()
Task::Ptr preUpdate{ createPreUpdateTask() }; Task::Ptr preUpdate{ createPreUpdateTask() };
if (preUpdate != nullptr) { if (preUpdate != nullptr) {
auto* task = new SequentialTask("ResourceFolderModel::update"); auto task = new SequentialTask("ResourceFolderModel::update");
task->addTask(preUpdate); task->addTask(preUpdate);
task->addTask(m_currentUpdateTask); task->addTask(m_current_update_task);
connect(task, &Task::finished, [task] { task->deleteLater(); }); connect(task, &Task::finished, [task] { task->deleteLater(); });
QThreadPool::globalInstance()->start(task); QThreadPool::globalInstance()->start(task);
} else { } else {
QThreadPool::globalInstance()->start(m_currentUpdateTask.get()); QThreadPool::globalInstance()->start(m_current_update_task.get());
} }
return true; return true;
@ -378,25 +362,24 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res)
} }
Task::Ptr task{ createParseTask(*res) }; Task::Ptr task{ createParseTask(*res) };
if (!task) { if (!task)
return; return;
}
int ticket = m_nextResolutionTicket.fetch_add(1); int ticket = m_next_resolution_ticket.fetch_add(1);
res->setResolving(true, ticket); res->setResolving(true, ticket);
m_activeParseTasks.insert(ticket, task); m_active_parse_tasks.insert(ticket, task);
connect( connect(
task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internalId()); }, task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); },
Qt::ConnectionType::QueuedConnection); Qt::ConnectionType::QueuedConnection);
connect( connect(
task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internalId()); }, task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); },
Qt::ConnectionType::QueuedConnection); Qt::ConnectionType::QueuedConnection);
connect( connect(
task.get(), &Task::finished, this, task.get(), &Task::finished, this,
[this, ticket] { [this, ticket] {
m_activeParseTasks.remove(ticket); m_active_parse_tasks.remove(ticket);
emit parseFinished(); emit parseFinished();
}, },
Qt::ConnectionType::QueuedConnection); Qt::ConnectionType::QueuedConnection);
@ -411,45 +394,44 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res)
void ResourceFolderModel::onUpdateSucceeded() void ResourceFolderModel::onUpdateSucceeded()
{ {
auto updateResults = static_cast<ResourceFolderLoadTask*>(m_currentUpdateTask.get())->result(); auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
auto& newResources = updateResults->resources; auto& new_resources = update_results->resources;
auto currentList = m_resourcesIndex.keys(); auto current_list = m_resources_index.keys();
QSet<QString> currentSet(currentList.begin(), currentList.end()); QSet<QString> current_set(current_list.begin(), current_list.end());
auto newList = newResources.keys(); auto new_list = new_resources.keys();
QSet<QString> newSet(newList.begin(), newList.end()); QSet<QString> new_set(new_list.begin(), new_list.end());
applyUpdates(currentSet, newSet, newResources); applyUpdates(current_set, new_set, new_resources);
} }
void ResourceFolderModel::onParseSucceeded(int ticket, const QString& resourceId) void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
{ {
auto iter = m_activeParseTasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return; return;
}
int row = m_resourcesIndex[resourceId]; int row = m_resources_index[resource_id];
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
} }
Task* ResourceFolderModel::createUpdateTask() Task* ResourceFolderModel::createUpdateTask()
{ {
auto indexDir2 = indexDir(); auto index_dir = indexDir();
auto* task = new ResourceFolderLoadTask(dir(), indexDir2, m_isIndexed, m_firstFolderLoad, auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
[this](const QFileInfo& file) { return createResource(file); }); [this](const QFileInfo& file) { return createResource(file); });
m_firstFolderLoad = false; m_first_folder_load = false;
return task; return task;
} }
bool ResourceFolderModel::hasPendingParseTasks() const bool ResourceFolderModel::hasPendingParseTasks() const
{ {
return !m_activeParseTasks.isEmpty(); return !m_active_parse_tasks.isEmpty();
} }
void ResourceFolderModel::directoryChanged(const QString& /*path*/) void ResourceFolderModel::directoryChanged(QString path)
{ {
update(); update();
} }
@ -464,9 +446,8 @@ Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
{ {
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
auto flags = defaultFlags | Qt::ItemIsDropEnabled; auto flags = defaultFlags | Qt::ItemIsDropEnabled;
if (index.isValid()) { if (index.isValid())
flags |= Qt::ItemIsUserCheckable; flags |= Qt::ItemIsUserCheckable;
}
return flags; return flags;
} }
@ -477,25 +458,21 @@ QStringList ResourceFolderModel::mimeTypes() const
return types; return types;
} }
bool ResourceFolderModel::dropMimeData(const QMimeData* data, bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
Qt::DropAction action,
int /*row*/,
int /*column*/,
const QModelIndex& /*parent*/)
{ {
if (action == Qt::IgnoreAction) { if (action == Qt::IgnoreAction) {
return true; return true;
} }
// check if the action is supported // check if the action is supported
if ((data == nullptr) || !(action & supportedDropActions())) { if (!data || !(action & supportedDropActions())) {
return false; return false;
} }
// files dropped from outside? // files dropped from outside?
if (data->hasUrls()) { if (data->hasUrls()) {
auto urls = data->urls(); auto urls = data->urls();
for (const auto& url : urls) { for (auto url : urls) {
// only local files may be dropped... // only local files may be dropped...
if (!url.isLocalFile()) { if (!url.isLocalFile()) {
continue; continue;
@ -511,12 +488,14 @@ bool ResourceFolderModel::dropMimeData(const QMimeData* data,
bool ResourceFolderModel::validateIndex(const QModelIndex& index) const bool ResourceFolderModel::validateIndex(const QModelIndex& index) const
{ {
if (!index.isValid()) { if (!index.isValid())
return false; return false;
}
int row = index.row(); int row = index.row();
return row >= 0 && row < m_resources.size(); if (row < 0 || row >= m_resources.size())
return false;
return true;
} }
// HACK: all subclasses need to call this to have the whole row painted // HACK: all subclasses need to call this to have the whole row painted
@ -525,15 +504,15 @@ QBrush ResourceFolderModel::rowBackground(int row) const
{ {
if (APPLICATION->settings()->get("ShowModIncompat").toBool() && m_resources[row]->hasIssues()) { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && m_resources[row]->hasIssues()) {
return { QColor(255, 0, 0, 40) }; return { QColor(255, 0, 0, 40) };
} else {
return {};
} }
return {};
} }
QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
{ {
if (!validateIndex(index)) { if (!validateIndex(index))
return {}; return {};
}
int row = index.row(); int row = index.row();
int column = index.column(); int column = index.column();
@ -551,13 +530,11 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return m_resources[row]->provider(); return m_resources[row]->provider();
case SizeColumn: case SizeColumn:
return m_resources[row]->sizeStr(); return m_resources[row]->sizeStr();
case FileNameColumn:
return m_resources[row]->fileinfo().fileName();
default: default:
return {}; return {};
} }
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
QString tooltip = m_resources[row]->internalId(); QString tooltip = m_resources[row]->internal_id();
if (column == NameColumn) { if (column == NameColumn) {
if (APPLICATION->settings()->get("ShowModIncompat").toBool()) { if (APPLICATION->settings()->get("ShowModIncompat").toBool()) {
@ -568,7 +545,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
if (at(row).isSymLinkUnder(instDirPath())) { if (at(row).isSymLinkUnder(instDirPath())) {
tooltip += tooltip +=
m_resources[row]->internalId() + m_resources[row]->internal_id() +
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
"\nCanonical Path: %1") "\nCanonical Path: %1")
.arg(at(row).fileinfo().canonicalFilePath()); .arg(at(row).fileinfo().canonicalFilePath());
@ -585,8 +562,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
if (column == NameColumn) { if (column == NameColumn) {
if (APPLICATION->settings()->get("ShowModIncompat").toBool() && at(row).hasIssues()) { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && at(row).hasIssues()) {
return QIcon::fromTheme("status-bad"); return QIcon::fromTheme("status-bad");
} } else if (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()) {
if (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()) {
return QIcon::fromTheme("status-yellow"); return QIcon::fromTheme("status-yellow");
} }
} }
@ -594,9 +570,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
return {}; return {};
} }
case Qt::CheckStateRole: case Qt::CheckStateRole:
if (column == ActiveColumn) { if (column == ActiveColumn)
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
}
return {}; return {};
default: default:
return {}; return {};
@ -606,9 +581,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role) bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{ {
int row = index.row(); int row = index.row();
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) { if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false; return false;
}
if (role == Qt::CheckStateRole) { if (role == Qt::CheckStateRole) {
return setResourceEnabled({ index }, EnableAction::TOGGLE); return setResourceEnabled({ index }, EnableAction::TOGGLE);
@ -627,7 +601,6 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
case DateColumn: case DateColumn:
case ProviderColumn: case ProviderColumn:
case SizeColumn: case SizeColumn:
case FileNameColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return {}; return {};
@ -645,8 +618,6 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
return tr("The source provider of the resource."); return tr("The source provider of the resource.");
case SizeColumn: case SizeColumn:
return tr("The size of the resource."); return tr("The size of the resource.");
case FileNameColumn:
return tr("The file name of the resource.");
default: default:
return {}; return {};
} }
@ -667,22 +638,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
void ResourceFolderModel::saveColumns(QTreeView* tree) void ResourceFolderModel::saveColumns(QTreeView* tree)
{ {
const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id());
const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id());
const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id());
auto stateSetting = m_instance->settings()->getSetting(stateSettingName); auto stateSetting = m_instance->settings()->getSetting(stateSettingName);
stateSetting->set(QString::fromUtf8(tree->header()->saveState().toBase64())); stateSetting->set(QString::fromUtf8(tree->header()->saveState().toBase64()));
// neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false
auto* settings = m_instance->settings(); auto settings = m_instance->settings();
if (!settings->get(overrideSettingName).toBool()) { if (!settings->get(overrideSettingName).toBool()) {
settings = APPLICATION->settings(); settings = APPLICATION->settings();
} }
auto visibility = Json::toMap(settings->get(visibilitySettingName).toString()); auto visibility = Json::toMap(settings->get(visibilitySettingName).toString());
for (auto i = 0; i < m_columnNames.size(); ++i) { for (auto i = 0; i < m_column_names.size(); ++i) {
if (m_columnsHideable[i]) { if (m_columnsHideable[i]) {
auto name = m_columnNames[i]; auto name = m_column_names[i];
visibility[name] = !tree->isColumnHidden(i); visibility[name] = !tree->isColumnHidden(i);
} }
} }
@ -691,24 +662,24 @@ void ResourceFolderModel::saveColumns(QTreeView* tree)
void ResourceFolderModel::loadColumns(QTreeView* tree) void ResourceFolderModel::loadColumns(QTreeView* tree)
{ {
const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id());
const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id());
const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id());
auto stateSetting = m_instance->settings()->getOrRegisterSetting(stateSettingName, ""); auto stateSetting = m_instance->settings()->getOrRegisterSetting(stateSettingName, "");
tree->header()->restoreState(QByteArray::fromBase64(stateSetting->get().toString().toUtf8())); tree->header()->restoreState(QByteArray::fromBase64(stateSetting->get().toString().toUtf8()));
auto setVisible = [this, tree](const QVariant& value) { auto setVisible = [this, tree](QVariant value) {
auto visibility = Json::toMap(value.toString()); auto visibility = Json::toMap(value.toString());
for (auto i = 0; i < m_columnNames.size(); ++i) { for (auto i = 0; i < m_column_names.size(); ++i) {
if (m_columnsHideable[i]) { if (m_columnsHideable[i]) {
auto name = m_columnNames[i]; auto name = m_column_names[i];
tree->setColumnHidden(i, !visibility.value(name, false).toBool()); tree->setColumnHidden(i, !visibility.value(name, false).toBool());
} }
} }
}; };
const auto defaultValue = Json::fromMap({ auto const defaultValue = Json::fromMap({
{ "Image", true }, { "Image", true },
{ "Version", true }, { "Version", true },
{ "Last Modified", true }, { "Last Modified", true },
@ -716,7 +687,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree)
{ "Pack Format", true }, { "Pack Format", true },
}); });
// neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false // neither passthrough nor override settings works for this usecase as I need to only set the global when the gate is false
auto* settings = m_instance->settings(); auto settings = m_instance->settings();
if (!settings->getOrRegisterSetting(overrideSettingName, false)->get().toBool()) { if (!settings->getOrRegisterSetting(overrideSettingName, false)->get().toBool()) {
settings = APPLICATION->settings(); settings = APPLICATION->settings();
} }
@ -725,7 +696,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree)
// allways connect the signal in case the setting is toggled on and off // allways connect the signal in case the setting is toggled on and off
auto gSetting = APPLICATION->settings()->getOrRegisterSetting(visibilitySettingName, defaultValue); auto gSetting = APPLICATION->settings()->getOrRegisterSetting(visibilitySettingName, defaultValue);
connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, const QVariant& value) { connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, QVariant value) {
if (!m_instance->settings()->get(overrideSettingName).toBool()) { if (!m_instance->settings()->get(overrideSettingName).toBool()) {
setVisible(value); setVisible(value);
} }
@ -734,11 +705,11 @@ void ResourceFolderModel::loadColumns(QTreeView* tree)
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
{ {
auto* menu = new QMenu(tree); auto menu = new QMenu(tree);
{ // action to decide if the visibility is per instance or not { // action to decide if the visibility is per instance or not
auto* act = new QAction(tr("Override Columns Visibility"), menu); auto act = new QAction(tr("Override Columns Visibility"), menu);
const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id());
act->setCheckable(true); act->setCheckable(true);
act->setChecked(m_instance->settings()->getOrRegisterSetting(overrideSettingName, false)->get().toBool()); act->setChecked(m_instance->settings()->getOrRegisterSetting(overrideSettingName, false)->get().toBool());
@ -754,10 +725,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
for (int col = 0; col < columnCount(); ++col) { for (int col = 0; col < columnCount(); ++col) {
// Skip creating actions for columns that should not be hidden // Skip creating actions for columns that should not be hidden
if (!m_columnsHideable.at(col)) { if (!m_columnsHideable.at(col))
continue; continue;
} auto act = new QAction(menu);
auto* act = new QAction(menu);
setupHeaderAction(act, col); setupHeaderAction(act, col);
act->setCheckable(true); act->setCheckable(true);
@ -766,9 +736,8 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled) { connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled) {
tree->setColumnHidden(col, !toggled); tree->setColumnHidden(col, !toggled);
for (int c = 0; c < columnCount(); ++c) { for (int c = 0; c < columnCount(); ++c) {
if (m_columnResizeModes.at(c) == QHeaderView::ResizeToContents) { if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
tree->resizeColumnToContents(c); tree->resizeColumnToContents(c);
}
} }
saveColumns(tree); saveColumns(tree);
}); });
@ -786,43 +755,41 @@ QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* pare
SortType ResourceFolderModel::columnToSortKey(size_t column) const SortType ResourceFolderModel::columnToSortKey(size_t column) const
{ {
Q_ASSERT(m_columnSortKeys.size() == columnCount()); Q_ASSERT(m_column_sort_keys.size() == columnCount());
return m_columnSortKeys.at(column); return m_column_sort_keys.at(column);
} }
/* Standard Proxy Model for createFilterProxyModel */ /* Standard Proxy Model for createFilterProxyModel */
bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, [[maybe_unused]] const QModelIndex& source_parent) const
{ {
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel()); auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
if (!model) { if (!model)
return true; return true;
}
const auto& resource = model->at(sourceRow); const auto& resource = model->at(source_row);
return resource.applyFilter(filterRegularExpression()); return resource.applyFilter(filterRegularExpression());
} }
bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
{ {
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel()); auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
if (!model || !sourceLeft.isValid() || !sourceRight.isValid() || sourceLeft.column() != sourceRight.column()) { if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) {
return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); return QSortFilterProxyModel::lessThan(source_left, source_right);
} }
// we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and
// proceed. // proceed.
auto columnSortKey = model->columnToSortKey(sourceLeft.column()); auto column_sort_key = model->columnToSortKey(source_left.column());
const auto& resourceLeft = model->at(sourceLeft.row()); auto const& resource_left = model->at(source_left.row());
const auto& resourceRight = model->at(sourceRight.row()); auto const& resource_right = model->at(source_right.row());
auto compareResult = resourceLeft.compare(resourceRight, columnSortKey); auto compare_result = resource_left.compare(resource_right, column_sort_key);
if (compareResult == 0) { if (compare_result == 0)
return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); return QSortFilterProxyModel::lessThan(source_left, source_right);
}
return compareResult < 0; return compare_result < 0;
} }
QString ResourceFolderModel::instDirPath() const QString ResourceFolderModel::instDirPath() const
@ -830,51 +797,50 @@ QString ResourceFolderModel::instDirPath() const
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
} }
void ResourceFolderModel::onParseFailed(int ticket, const QString& resourceId) void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
{ {
auto iter = m_activeParseTasks.constFind(ticket); auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id))
return; return;
}
auto removedIndex = m_resourcesIndex[resourceId]; auto removed_index = m_resources_index[resource_id];
auto removedIt = m_resources.begin() + removedIndex; auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removedIt != m_resources.end()); Q_ASSERT(removed_it != m_resources.end());
beginRemoveRows(QModelIndex(), removedIndex, removedIndex); beginRemoveRows(QModelIndex(), removed_index, removed_index);
m_resources.erase(removedIt); m_resources.erase(removed_it);
// update index // update index
m_resourcesIndex.clear(); m_resources_index.clear();
int idx = 0; int idx = 0;
for (const auto& mod : qAsConst(m_resources)) { for (auto const& mod : qAsConst(m_resources)) {
m_resourcesIndex[mod->internalId()] = idx; m_resources_index[mod->internal_id()] = idx;
idx++; idx++;
} }
endRemoveRows(); endRemoveRows();
} }
void ResourceFolderModel::applyUpdates(QSet<QString>& currentSet, QSet<QString>& newSet, QMap<QString, Resource::Ptr>& newResources) void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources)
{ {
// see if the kept resources changed in some way // see if the kept resources changed in some way
{ {
QSet<QString> keptSet = currentSet; QSet<QString> kept_set = current_set;
keptSet.intersect(newSet); kept_set.intersect(new_set);
for (const auto& kept : keptSet) { for (auto const& kept : kept_set) {
auto rowIt = m_resourcesIndex.constFind(kept); auto row_it = m_resources_index.constFind(kept);
Q_ASSERT(rowIt != m_resourcesIndex.constEnd()); Q_ASSERT(row_it != m_resources_index.constEnd());
auto row = rowIt.value(); auto row = row_it.value();
auto& newResource = newResources[kept]; auto& new_resource = new_resources[kept];
const auto& currentResource = m_resources.at(row); auto const& current_resource = m_resources.at(row);
if (newResource->dateTimeChanged() == currentResource->dateTimeChanged()) { if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
// no significant change // no significant change
bool hadIssues = !currentResource->hasIssues(); bool hadIssues = !current_resource->hasIssues();
currentResource->updateIssues(m_instance); current_resource->updateIssues(m_instance);
if (hadIssues != currentResource->hasIssues()) { if (hadIssues != current_resource->hasIssues()) {
emit dataChanged(index(row, 0), index(row, columnCount({}) - 1)); emit dataChanged(index(row, 0), index(row, columnCount({}) - 1));
} }
continue; continue;
@ -882,16 +848,16 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& currentSet, QSet<QString>&
// If the resource is resolving, but something about it changed, we don't want to // If the resource is resolving, but something about it changed, we don't want to
// continue the resolving. // continue the resolving.
if (currentResource->isResolving()) { if (current_resource->isResolving()) {
auto ticket = currentResource->resolutionTicket(); auto ticket = current_resource->resolutionTicket();
if (m_activeParseTasks.contains(ticket)) { if (m_active_parse_tasks.contains(ticket)) {
auto* task = (*m_activeParseTasks.find(ticket)).get(); auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort(); task->abort();
} }
} }
m_resources[row].reset(newResource); m_resources[row].reset(new_resource);
newResource->updateIssues(m_instance); new_resource->updateIssues(m_instance);
resolveResource(m_resources.at(row)); resolveResource(m_resources.at(row));
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
@ -900,47 +866,46 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& currentSet, QSet<QString>&
// remove resources no longer present // remove resources no longer present
{ {
QSet<QString> removedSet = currentSet; QSet<QString> removed_set = current_set;
removedSet.subtract(newSet); removed_set.subtract(new_set);
QList<int> removedRows; QList<int> removed_rows;
for (const auto& removed : removedSet) { for (auto& removed : removed_set)
removedRows.append(m_resourcesIndex[removed]); removed_rows.append(m_resources_index[removed]);
}
std::ranges::sort(removedRows, std::greater()); std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
for (auto& removedIndex : removedRows) { for (auto& removed_index : removed_rows) {
auto removedIt = m_resources.begin() + removedIndex; auto removed_it = m_resources.begin() + removed_index;
Q_ASSERT(removedIt != m_resources.end()); Q_ASSERT(removed_it != m_resources.end());
if ((*removedIt)->isResolving()) { if ((*removed_it)->isResolving()) {
auto ticket = (*removedIt)->resolutionTicket(); auto ticket = (*removed_it)->resolutionTicket();
if (m_activeParseTasks.contains(ticket)) { if (m_active_parse_tasks.contains(ticket)) {
auto* task = (*m_activeParseTasks.find(ticket)).get(); auto task = (*m_active_parse_tasks.find(ticket)).get();
task->abort(); task->abort();
} }
} }
beginRemoveRows(QModelIndex(), removedIndex, removedIndex); beginRemoveRows(QModelIndex(), removed_index, removed_index);
m_resources.erase(removedIt); m_resources.erase(removed_it);
endRemoveRows(); endRemoveRows();
} }
} }
// add new resources to the end // add new resources to the end
{ {
QSet<QString> addedSet = newSet; QSet<QString> added_set = new_set;
addedSet.subtract(currentSet); added_set.subtract(current_set);
// When you have a Qt build with assertions turned on, proceeding here will abort the application // When you have a Qt build with assertions turned on, proceeding here will abort the application
if (addedSet.size() > 0) { if (added_set.size() > 0) {
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()), beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
static_cast<int>(m_resources.size() + addedSet.size() - 1)); static_cast<int>(m_resources.size() + added_set.size() - 1));
for (const auto& added : addedSet) { for (auto& added : added_set) {
auto res = newResources[added]; auto res = new_resources[added];
res->updateIssues(m_instance); res->updateIssues(m_instance);
m_resources.append(res); m_resources.append(res);
resolveResource(m_resources.last()); resolveResource(m_resources.last());
@ -952,10 +917,10 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& currentSet, QSet<QString>&
// update index // update index
{ {
m_resourcesIndex.clear(); m_resources_index.clear();
int idx = 0; int idx = 0;
for (const auto& mod : qAsConst(m_resources)) { for (auto const& mod : qAsConst(m_resources)) {
m_resourcesIndex[mod->internalId()] = idx; m_resources_index[mod->internal_id()] = idx;
idx++; idx++;
} }
} }
@ -963,19 +928,17 @@ void ResourceFolderModel::applyUpdates(QSet<QString>& currentSet, QSet<QString>&
Resource::Ptr ResourceFolderModel::find(QString id) Resource::Ptr ResourceFolderModel::find(QString id)
{ {
auto iter = auto iter =
std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](const Resource::Ptr& r) { return r->internalId() == id; }); std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; });
if (iter == m_resources.constEnd()) { if (iter == m_resources.constEnd())
return nullptr; return nullptr;
}
return *iter; return *iter;
} }
QList<Resource*> ResourceFolderModel::allResources() QList<Resource*> ResourceFolderModel::allResources()
{ {
QList<Resource*> result; QList<Resource*> result;
result.reserve(m_resources.size()); result.reserve(m_resources.size());
for (const Resource ::Ptr& resource : m_resources) { for (const Resource ::Ptr& resource : m_resources)
result.append((resource.get())); result.append((resource.get()));
}
return result; return result;
} }
@ -983,9 +946,8 @@ QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& i
{ {
QList<Resource*> result; QList<Resource*> result;
for (const QModelIndex& index : indexes) { for (const QModelIndex& index : indexes) {
if (index.column() != 0) { if (index.column() != 0)
continue; continue;
}
result.append(&at(index.row())); result.append(&at(index.row()));
} }
return result; return result;

View file

@ -61,7 +61,7 @@ class QSortFilterProxyModel;
class ResourceFolderModel : public QAbstractListModel { class ResourceFolderModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: public:
ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
~ResourceFolderModel() override; ~ResourceFolderModel() override;
virtual QString id() const { return "resource"; } virtual QString id() const { return "resource"; }
@ -93,13 +93,13 @@ class ResourceFolderModel : public QAbstractListModel {
*/ */
virtual bool installResource(QString path); virtual bool installResource(QString path);
virtual void installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers); virtual void installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
/** Uninstall (i.e. remove all data about it) a resource, given its file name. /** Uninstall (i.e. remove all data about it) a resource, given its file name.
* *
* Returns whether the removal was successful. * Returns whether the removal was successful.
*/ */
virtual bool uninstallResource(const QString& fileName, bool preserveMetadata = false); virtual bool uninstallResource(const QString& file_name, bool preserve_metadata = false);
virtual bool deleteResources(const QModelIndexList&); virtual bool deleteResources(const QModelIndexList&);
virtual void deleteMetadata(const QModelIndexList&); virtual void deleteMetadata(const QModelIndexList&);
@ -125,7 +125,7 @@ class ResourceFolderModel : public QAbstractListModel {
Resource::Ptr find(QString id); Resource::Ptr find(QString id);
const QDir& dir() const { return m_dir; } QDir const& dir() const { return m_dir; }
/** Checks whether there's any parse tasks being done. /** Checks whether there's any parse tasks being done.
* *
@ -137,12 +137,12 @@ class ResourceFolderModel : public QAbstractListModel {
/* Qt behavior */ /* Qt behavior */
/* Basic columns */ /* Basic columns */
enum Columns : std::uint8_t { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NumColumns }; enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
QStringList columnNames(bool translated = true) const { return translated ? m_columnNamesTranslated : m_columnNames; } QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); } int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NumColumns; } int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }
Qt::DropActions supportedDropActions() const override; Qt::DropActions supportedDropActions() const override;
@ -171,19 +171,18 @@ class ResourceFolderModel : public QAbstractListModel {
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
SortType columnToSortKey(size_t column) const; SortType columnToSortKey(size_t column) const;
QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_columnResizeModes; } QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
class ProxyModel : public QSortFilterProxyModel { class ProxyModel : public QSortFilterProxyModel {
public: public:
explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {}
protected: protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const override; bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
}; };
QString instDirPath() const; QString instDirPath() const;
BaseInstance* instance() const { return m_instance; }
signals: signals:
void updateFinished(); void updateFinished();
@ -207,7 +206,7 @@ class ResourceFolderModel : public QAbstractListModel {
* This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
* in the background, so it slowly updates the UI as tasks get done. * in the background, so it slowly updates the UI as tasks get done.
*/ */
[[nodiscard]] virtual Task* createParseTask(Resource& /*unused*/) { return nullptr; } [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; }
/** Standard implementation of the model update logic. /** Standard implementation of the model update logic.
* *
@ -215,10 +214,10 @@ class ResourceFolderModel : public QAbstractListModel {
* to act only on those disparities. * to act only on those disparities.
* *
*/ */
void applyUpdates(QSet<QString>& currentSet, QSet<QString>& newSet, QMap<QString, Resource::Ptr>& newResources); void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
protected slots: protected slots:
void directoryChanged(const QString&); void directoryChanged(QString);
/** Called when the update task is successful. /** Called when the update task is successful.
* *
@ -234,40 +233,39 @@ class ResourceFolderModel : public QAbstractListModel {
* This is just a simple reference implementation. You probably want to override it with your own logic in a subclass * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass
* if the resource is complex and has more stuff to parse. * if the resource is complex and has more stuff to parse.
*/ */
virtual void onParseSucceeded(int ticket, const QString& resourceId); virtual void onParseSucceeded(int ticket, QString resource_id);
virtual void onParseFailed(int ticket, const QString& resourceId); virtual void onParseFailed(int ticket, QString resource_id);
protected: protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key. // Represents the relationship between a column's index (represented by the list index), and it's sorting key.
// As such, the order in with they appear is very important! // As such, the order in with they appear is very important!
QList<SortType> m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Date, QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
SortType::Provider, SortType::Size, SortType::Filename }; QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
QStringList m_columnNames = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
QStringList m_columnNamesTranslated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QList<QHeaderView::ResizeMode> m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; QList<bool> m_columnsHideable = { false, false, true, true, true };
QList<bool> m_columnsHideable = { false, false, true, true, true, true };
QDir m_dir; QDir m_dir;
BaseInstance* m_instance; BaseInstance* m_instance;
QFileSystemWatcher m_watcher; QFileSystemWatcher m_watcher;
bool m_isWatching = false; bool m_is_watching = false;
bool m_isIndexed; bool m_is_indexed;
bool m_firstFolderLoad = true; bool m_first_folder_load = true;
Task::Ptr m_currentUpdateTask = nullptr; Task::Ptr m_current_update_task = nullptr;
bool m_scheduledUpdate = false; bool m_scheduled_update = false;
QList<Resource::Ptr> m_resources; QList<Resource::Ptr> m_resources;
// Represents the relationship between a resource's internal ID and it's row position on the model. // Represents the relationship between a resource's internal ID and it's row position on the model.
QMap<QString, int> m_resourcesIndex; QMap<QString, int> m_resources_index;
// Runs off-thread // Runs off-thread
ConcurrentTask m_resourceResolver; ConcurrentTask m_resourceResolver;
bool m_resourceResolverRunning = false; bool m_resourceResolverRunning = false;
QMap<int, Task::Ptr> m_activeParseTasks; QMap<int, Task::Ptr> m_active_parse_tasks;
std::atomic<int> m_nextResolutionTicket = 0; std::atomic<int> m_next_resolution_ticket = 0;
}; };

View file

@ -39,26 +39,27 @@
#include <QIcon> #include <QIcon>
#include <QStyle> #include <QStyle>
#include "Version.h"
#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include "minecraft/mod/tasks/LocalDataPackParseTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, isIndexed, createDir, parent) : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{ {
m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" });
m_columnNamesTranslated = QStringList( m_column_names_translated =
{ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") });
m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT,
SortType::Date, SortType::Provider, SortType::Size, SortType::Filename }; SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true, true, true }; m_columnsHideable = { false, true, false, true, true, true, true };
} }
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
{ {
if (!validateIndex(index)) { if (!validateIndex(index))
return {}; return {};
}
int row = index.row(); int row = index.row();
int column = index.column(); int column = index.column();
@ -91,8 +92,6 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32); return QSize(32, 32);
} }
break; break;
default:
break;
} }
// map the columns to the base equivilents // map the columns to the base equivilents
@ -113,11 +112,6 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
case SizeColumn: case SizeColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn);
break; break;
case FileNameColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn);
break;
default:
break;
} }
if (mappedIndex.isValid()) { if (mappedIndex.isValid()) {
@ -139,7 +133,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case ImageColumn: case ImageColumn:
case ProviderColumn: case ProviderColumn:
case SizeColumn: case SizeColumn:
case FileNameColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return {}; return {};
@ -160,8 +153,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
return tr("The source provider of the resource pack."); return tr("The source provider of the resource pack.");
case SizeColumn: case SizeColumn:
return tr("The size of the resource pack."); return tr("The size of the resource pack.");
case FileNameColumn:
return tr("The file name of the resource pack.");
default: default:
return {}; return {};
} }
@ -177,10 +168,10 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : NumColumns; return parent.isValid() ? 0 : NUM_COLUMNS;
} }
Task* ResourcePackFolderModel::createParseTask(Resource& resource) Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalDataPackParseTask(m_nextResolutionTicket, dynamic_cast<ResourcePack*>(&resource)); return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast<ResourcePack*>(&resource));
} }

View file

@ -7,19 +7,9 @@
class ResourcePackFolderModel : public ResourceFolderModel { class ResourcePackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns : std::uint8_t { enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
ActiveColumn = 0,
ImageColumn,
NameColumn,
PackFormatColumn,
DateColumn,
ProviderColumn,
SizeColumn,
FileNameColumn,
NumColumns
};
explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
QString id() const override { return "resourcepacks"; } QString id() const override { return "resourcepacks"; }
@ -29,7 +19,7 @@ class ResourcePackFolderModel : public ResourceFolderModel {
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
[[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(ResourcePack) RESOURCE_HELPERS(ResourcePack)
}; };

View file

@ -18,7 +18,7 @@ class ShaderPackFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource& resource) override [[nodiscard]] Task* createParseTask(Resource& resource) override
{ {
return new LocalShaderPackParseTask(m_nextResolutionTicket, static_cast<ShaderPack&>(resource)); return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
} }
QDir indexDir() const override { return m_dir; } QDir indexDir() const override { return m_dir; }

View file

@ -36,30 +36,28 @@
#include "TexturePackFolderModel.h" #include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{ {
m_columnNames = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
m_columnNamesTranslated = m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Date, m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
SortType::Provider, SortType::Size, SortType::Filename }; QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, m_columnsHideable = { false, true, false, true, true, true };
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
m_columnsHideable = { false, true, false, true, true, true, true };
} }
Task* TexturePackFolderModel::createParseTask(Resource& resource) Task* TexturePackFolderModel::createParseTask(Resource& resource)
{ {
return new LocalTexturePackParseTask(m_nextResolutionTicket, static_cast<TexturePack&>(resource)); return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
} }
QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
{ {
if (!validateIndex(index)) { if (!validateIndex(index))
return {}; return {};
}
int row = index.row(); int row = index.row();
int column = index.column(); int column = index.column();
@ -78,8 +76,6 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32); return QSize(32, 32);
} }
break; break;
default:
break;
} }
// map the columns to the base equivilents // map the columns to the base equivilents
@ -100,11 +96,6 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
case SizeColumn: case SizeColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn);
break; break;
case FileNameColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn);
break;
default:
break;
} }
if (mappedIndex.isValid()) { if (mappedIndex.isValid()) {
@ -125,7 +116,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case ImageColumn: case ImageColumn:
case ProviderColumn: case ProviderColumn:
case SizeColumn: case SizeColumn:
case FileNameColumn:
return columnNames().at(section); return columnNames().at(section);
default: default:
return {}; return {};
@ -142,8 +132,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
return tr("The source provider of the texture pack."); return tr("The source provider of the texture pack.");
case SizeColumn: case SizeColumn:
return tr("The size of the texture pack."); return tr("The size of the texture pack.");
case FileNameColumn:
return tr("The file name of the texture pack.");
default: default:
return {}; return {};
} }
@ -157,5 +145,5 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
{ {
return parent.isValid() ? 0 : NumColumns; return parent.isValid() ? 0 : NUM_COLUMNS;
} }

View file

@ -44,20 +44,11 @@ class TexturePackFolderModel : public ResourceFolderModel {
Q_OBJECT Q_OBJECT
public: public:
enum Columns : std::uint8_t { enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
ActiveColumn = 0,
ImageColumn,
NameColumn,
DateColumn,
ProviderColumn,
SizeColumn,
FileNameColumn,
NumColumns
};
explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
QString id() const override { return "texturepacks"; } virtual QString id() const override { return "texturepacks"; }
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
@ -65,7 +56,7 @@ class TexturePackFolderModel : public ResourceFolderModel {
int columnCount(const QModelIndex& parent) const override; int columnCount(const QModelIndex& parent) const override;
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
[[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; [[nodiscard]] Task* createParseTask(Resource&) override;
RESOURCE_HELPERS(TexturePack) RESOURCE_HELPERS(TexturePack)
}; };

View file

@ -41,28 +41,26 @@
#include "minecraft/mod/MetadataHandler.h" #include "minecraft/mod/MetadataHandler.h"
#include <QThread> #include <QThread>
#include <utility>
ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resourceDir, ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& indexDir, const QDir& index_dir,
bool isIndexed, bool is_indexed,
bool cleanOrphan, bool clean_orphan,
std::function<Resource*(const QFileInfo&)> createFunction) std::function<Resource*(const QFileInfo&)> create_function)
: Task(false) : Task(false)
, m_resource_dir(resourceDir) , m_resource_dir(resource_dir)
, m_index_dir(indexDir) , m_index_dir(index_dir)
, m_is_indexed(isIndexed) , m_is_indexed(is_indexed)
, m_clean_orphan(cleanOrphan) , m_clean_orphan(clean_orphan)
, m_create_func(std::move(createFunction)) , m_create_func(create_function)
, m_result(new Result()) , m_result(new Result())
, m_thread_to_spawn_into(thread()) , m_thread_to_spawn_into(thread())
{} {}
void ResourceFolderLoadTask::executeTask() void ResourceFolderLoadTask::executeTask()
{ {
if (thread() != m_thread_to_spawn_into) { if (thread() != m_thread_to_spawn_into)
connect(this, &Task::finished, this->thread(), &QThread::quit); connect(this, &Task::finished, this->thread(), &QThread::quit);
}
if (m_is_indexed) { if (m_is_indexed) {
// Read metadata first // Read metadata first
@ -73,7 +71,7 @@ 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 != nullptr) && app->checkQSavePath(filePath)) { if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
continue; continue;
} }
auto newFilePath = FS::getUniqueResourceName(filePath); auto newFilePath = FS::getUniqueResourceName(filePath);
@ -85,29 +83,29 @@ void ResourceFolderLoadTask::executeTask()
Resource* resource = m_create_func(entry); Resource* resource = m_create_func(entry);
if (resource->enabled()) { if (resource->enabled()) {
if (m_result->resources.contains(resource->internalId())) { if (m_result->resources.contains(resource->internal_id())) {
m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
// Delete the object we just created, since a valid one is already in the mods list. // Delete the object we just created, since a valid one is already in the mods list.
delete resource; delete resource;
} else { } else {
m_result->resources[resource->internalId()].reset(resource); m_result->resources[resource->internal_id()].reset(resource);
m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
} }
} else { } else {
QString choppedId = resource->internalId().chopped(9); QString chopped_id = resource->internal_id().chopped(9);
if (m_result->resources.contains(choppedId)) { if (m_result->resources.contains(chopped_id)) {
m_result->resources[resource->internalId()].reset(resource); m_result->resources[resource->internal_id()].reset(resource);
auto metadata = m_result->resources[choppedId]->metadata(); auto metadata = m_result->resources[chopped_id]->metadata();
if (metadata) { if (metadata) {
resource->setMetadata(*metadata); resource->setMetadata(*metadata);
m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
m_result->resources.remove(choppedId); m_result->resources.remove(chopped_id);
} }
} else { } else {
m_result->resources[resource->internalId()].reset(resource); m_result->resources[resource->internal_id()].reset(resource);
m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
} }
} }
} }
@ -118,41 +116,38 @@ void ResourceFolderLoadTask::executeTask()
QMutableMapIterator iter(m_result->resources); QMutableMapIterator iter(m_result->resources);
while (iter.hasNext()) { while (iter.hasNext()) {
auto resource = iter.next().value(); auto resource = iter.next().value();
if (resource->status() == ResourceStatus::NotInstalled) { if (resource->status() == ResourceStatus::NOT_INSTALLED) {
resource->destroy(m_index_dir, false, false); resource->destroy(m_index_dir, false, false);
iter.remove(); iter.remove();
} }
} }
} }
for (const auto& mod : m_result->resources) { for (auto mod : m_result->resources)
mod->moveToThread(m_thread_to_spawn_into); mod->moveToThread(m_thread_to_spawn_into);
}
if (m_aborted) { if (m_aborted)
emit finished(); emit finished();
} else { else
emitSucceeded(); emitSucceeded();
}
} }
void ResourceFolderLoadTask::getFromMetadata() void ResourceFolderLoadTask::getFromMetadata()
{ {
m_index_dir.refresh(); m_index_dir.refresh();
for (const auto& entry : m_index_dir.entryList(QDir::Files)) { for (auto entry : m_index_dir.entryList(QDir::Files)) {
if (!entry.endsWith(".pw.toml")) { if (!entry.endsWith(".pw.toml")) {
continue; continue;
} }
auto metadata = Metadata::get(m_index_dir, entry); auto metadata = Metadata::get(m_index_dir, entry);
if (!metadata.isValid()) { if (!metadata.isValid())
continue; continue;
}
auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename)));
resource->setMetadata(metadata); resource->setMetadata(metadata);
resource->setStatus(ResourceStatus::NotInstalled); resource->setStatus(ResourceStatus::NOT_INSTALLED);
m_result->resources[resource->internalId()].reset(resource); m_result->resources[resource->internal_id()].reset(resource);
} }
} }

View file

@ -41,7 +41,7 @@
#include <QObject> #include <QObject>
#include <QRunnable> #include <QRunnable>
#include <memory> #include <memory>
#include "minecraft/mod/Resource.h" #include "minecraft/mod/Mod.h"
#include "tasks/Task.h" #include "tasks/Task.h"
class ResourceFolderLoadTask : public Task { class ResourceFolderLoadTask : public Task {
@ -54,11 +54,11 @@ class ResourceFolderLoadTask : public Task {
ResultPtr result() const { return m_result; } ResultPtr result() const { return m_result; }
public: public:
ResourceFolderLoadTask(const QDir& resourceDir, ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& indexDir, const QDir& index_dir,
bool isIndexed, bool is_indexed,
bool cleanOrphan, bool clean_orphan,
std::function<Resource*(const QFileInfo&)> createFunction); std::function<Resource*(const QFileInfo&)> create_function);
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override bool abort() override
@ -76,7 +76,7 @@ class ResourceFolderLoadTask : public Task {
QDir m_resource_dir, m_index_dir; QDir m_resource_dir, m_index_dir;
bool m_is_indexed; bool m_is_indexed;
bool m_clean_orphan; bool m_clean_orphan;
std::function<Resource*(const QFileInfo&)> m_create_func; std::function<Resource*(QFileInfo const&)> m_create_func;
ResultPtr m_result; ResultPtr m_result;
std::atomic<bool> m_aborted = false; std::atomic<bool> m_aborted = false;

View file

@ -121,7 +121,7 @@ bool SkinList::update()
auto folderContents = m_dir.entryInfoList(); auto folderContents = m_dir.entryInfoList();
// if there are any untracked files... // if there are any untracked files...
for (QFileInfo entry : folderContents) { for (QFileInfo entry : folderContents) {
if (!entry.isFile() || entry.suffix() != "png") if (!entry.isFile() && entry.suffix() != "png")
continue; continue;
SkinModel w(entry.absoluteFilePath()); SkinModel w(entry.absoluteFilePath());

View file

@ -15,15 +15,15 @@ class CheckUpdateTask : public Task {
std::vector<Version>& mcVersions, std::vector<Version>& mcVersions,
QList<ModPlatform::ModLoaderType> loadersList, QList<ModPlatform::ModLoaderType> loadersList,
ResourceFolderModel* resourceModel) ResourceFolderModel* resourceModel)
: m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) : Task(), m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel)
{} {}
struct Update { struct Update {
QString name; QString name;
QString oldHash; QString old_hash;
QString oldVersion; QString old_version;
QString newVersion; QString new_version;
std::optional<ModPlatform::IndexedVersionType> newVersionType; std::optional<ModPlatform::IndexedVersionType> new_version_type;
QString changelog; QString changelog;
ModPlatform::ResourceProvider provider; ModPlatform::ResourceProvider provider;
shared_qobject_ptr<ResourceDownloadTask> download; shared_qobject_ptr<ResourceDownloadTask> download;
@ -31,19 +31,19 @@ class CheckUpdateTask : public Task {
public: public:
Update(QString name, Update(QString name,
QString oldH, QString old_h,
QString oldV, QString old_v,
QString newV, QString new_v,
std::optional<ModPlatform::IndexedVersionType> newVType, std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog, QString changelog,
ModPlatform::ResourceProvider p, ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t, shared_qobject_ptr<ResourceDownloadTask> t,
bool enabled = true) bool enabled = true)
: name(std::move(name)) : name(std::move(name))
, oldHash(std::move(oldH)) , old_hash(std::move(old_h))
, oldVersion(std::move(oldV)) , old_version(std::move(old_v))
, newVersion(std::move(newV)) , new_version(std::move(new_v))
, newVersionType(newVType) , new_version_type(std::move(new_v_type))
, changelog(std::move(changelog)) , changelog(std::move(changelog))
, provider(p) , provider(p)
, download(std::move(t)) , download(std::move(t))
@ -54,11 +54,14 @@ class CheckUpdateTask : public Task {
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); } auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); } auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
bool abort() override = 0;
protected slots: protected slots:
void executeTask() override = 0; void executeTask() override = 0;
signals: signals:
void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {}); void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
protected: protected:
QList<Resource*>& m_resources; QList<Resource*>& m_resources;

View file

@ -99,7 +99,7 @@ void EnsureMetadataTask::executeTask()
} }
// They already have the right metadata :o // They already have the right metadata :o
if (resource->status() != ResourceStatus::NoMetadata && resource->metadata() && resource->metadata()->provider == m_provider) { if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
qDebug() << "Resource" << resource->name() << "already has metadata!"; qDebug() << "Resource" << resource->name() << "already has metadata!";
emitReady(resource); emitReady(resource);
continue; continue;
@ -263,7 +263,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
Task::Ptr EnsureMetadataTask::modrinthProjectsTask() Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
{ {
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (const auto& data : m_tempVersions) for (auto const& data : m_tempVersions)
addonIds.insert(data.addonId.toString(), data.hash); addonIds.insert(data.addonId.toString(), data.hash);
Task::Ptr proj_task; Task::Ptr proj_task;
@ -404,7 +404,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
Task::Ptr EnsureMetadataTask::flameProjectsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask()
{ {
QHash<QString, QString> addonIds; QHash<QString, QString> addonIds;
for (const auto& hash : m_resources.keys()) { for (auto const& hash : m_resources.keys()) {
if (m_tempVersions.contains(hash)) { if (m_tempVersions.contains(hash)) {
auto data = m_tempVersions.find(hash).value(); auto data = m_tempVersions.find(hash).value();

View file

@ -32,7 +32,6 @@ class QIODevice;
namespace ModPlatform { namespace ModPlatform {
enum class ModLoaderType : std::uint16_t { enum class ModLoaderType : std::uint16_t {
None = 0U,
NeoForge = 1U << 0U, NeoForge = 1U << 0U,
Forge = 1U << 1U, Forge = 1U << 1U,
Cauldron = 1U << 2U, Cauldron = 1U << 2U,

View file

@ -148,9 +148,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback<QVe
return netJob; return netJob;
} }
Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks, bool askRetry) const Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback<ModPlatform::IndexedPack::Ptr>&& callbacks) const
{ {
auto [job, response] = getProject(args.pack->addonId.toString(), askRetry); auto [job, response] = getProject(args.pack->addonId.toString());
QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] { QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] {
auto pack = args.pack; auto pack = args.pack;
@ -284,7 +284,7 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const
return verStr; return verStr;
} }
std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool askRetry) const std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId) const
{ {
auto project_url_optional = getInfoURL(addonId); auto project_url_optional = getInfoURL(addonId);
if (!project_url_optional.has_value()) if (!project_url_optional.has_value())
@ -293,7 +293,6 @@ std::pair<Task::Ptr, QByteArray*> ResourceAPI::getProject(QString addonId, bool
auto project_url = project_url_optional.value(); auto project_url = project_url_optional.value();
auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::GetProject").arg(addonId), APPLICATION->network());
netJob->setAskRetry(askRetry);
auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url)); auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url));
netJob->addNetAction(action); netJob->addNetAction(action);

View file

@ -115,10 +115,10 @@ class ResourceAPI {
public slots: public slots:
virtual Task::Ptr searchProjects(SearchArgs&&, Callback<QList<ModPlatform::IndexedPack::Ptr>>&&) const; virtual Task::Ptr searchProjects(SearchArgs&&, Callback<QList<ModPlatform::IndexedPack::Ptr>>&&) const;
virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId, bool askRetry = true) const; virtual std::pair<Task::Ptr, QByteArray*> getProject(QString addonId) const;
virtual std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const = 0; virtual std::pair<Task::Ptr, QByteArray*> getProjects(QStringList addonIds) const = 0;
virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&, bool askRetry = true) const; virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback<ModPlatform::IndexedPack::Ptr>&&) const;
Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const; Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback<QVector<ModPlatform::IndexedVersion>>&& callbacks) const;
virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback<ModPlatform::IndexedVersion>&&) const; virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback<ModPlatform::IndexedVersion>&&) const;

View file

@ -38,7 +38,6 @@
#include <QtConcurrent> #include <QtConcurrent>
#include <algorithm> #include <algorithm>
#include <utility>
#include "FileSystem.h" #include "FileSystem.h"
#include "Json.h" #include "Json.h"
@ -60,28 +59,18 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "ui/dialogs/BlockedModsDialog.h" #include "ui/dialogs/BlockedModsDialog.h"
namespace {
bool isPathTraversal(const QString& basePath, const QString& entryName)
{
auto safeName = FS::RemoveInvalidPathChars(entryName);
auto fullPath = FS::PathCombine(basePath, safeName);
auto baseUrl = QUrl::fromLocalFile(basePath);
return !baseUrl.isParentOf(QUrl::fromLocalFile(fullPath));
}
Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version)
{
return APPLICATION->metadataIndex()->getLoadedVersion(uid, version);
}
} // namespace
namespace ATLauncher { namespace ATLauncher {
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version);
PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packName, QString version, InstallMode installMode) PackInstallTask::PackInstallTask(UserInteractionSupport* support, QString packName, QString version, InstallMode installMode)
: m_support(support), m_install_mode(installMode), m_pack_name(packName), m_version_name(std::move(version))
{ {
m_support = support;
m_pack_name = packName;
static const QRegularExpression s_regex("[^A-Za-z0-9]"); static const QRegularExpression s_regex("[^A-Za-z0-9]");
m_pack_safe_name = packName.replace(s_regex, ""); m_pack_safe_name = packName.replace(s_regex, "");
m_version_name = version;
m_install_mode = installMode;
} }
bool PackInstallTask::abort() bool PackInstallTask::abort()
@ -118,10 +107,11 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr)
QByteArray response = std::move(*responsePtr); QByteArray response = std::move(*responsePtr);
jobPtr.reset(); jobPtr.reset();
QJsonParseError parseError{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(response, &parseError); QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if (parseError.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ATLauncher at" << parseError.offset << "reason:" << parseError.errorString(); qWarning() << "Error while parsing JSON response from ATLauncher at" << parse_error.offset
<< "reason:" << parse_error.errorString();
qWarning() << response; qWarning() << response;
return; return;
} }
@ -138,7 +128,7 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr)
// Derived from the installation mode // Derived from the installation mode
QString message; QString message;
bool resetDirectory = false; bool resetDirectory;
switch (m_install_mode) { switch (m_install_mode) {
case InstallMode::Reinstall: case InstallMode::Reinstall:
@ -158,9 +148,8 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr)
} }
// Display message if one exists // Display message if one exists
if (!message.isEmpty()) { if (!message.isEmpty())
m_support->displayMessage(message); m_support->displayMessage(message);
}
auto ver = getComponentVersion("net.minecraft", m_version.minecraft); auto ver = getComponentVersion("net.minecraft", m_version.minecraft);
if (!ver) { if (!ver) {
@ -184,7 +173,7 @@ void PackInstallTask::onDownloadFailed(QString reason)
{ {
qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId(); qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId();
jobPtr.reset(); jobPtr.reset();
emitFailed(std::move(reason)); emitFailed(reason);
} }
void PackInstallTask::onDownloadAborted() void PackInstallTask::onDownloadAborted()
@ -213,30 +202,26 @@ void PackInstallTask::deleteExistingFiles()
keeps.files.append(VersionKeep{ "root", "servers.dat" }); keeps.files.append(VersionKeep{ "root", "servers.dat" });
// Merge with version deletes and keeps // Merge with version deletes and keeps
for (const auto& item : m_version.deletes.files) { for (const auto& item : m_version.deletes.files)
deletes.files.append(item); deletes.files.append(item);
} for (const auto& item : m_version.deletes.folders)
for (const auto& item : m_version.deletes.folders) {
deletes.folders.append(item); deletes.folders.append(item);
} for (const auto& item : m_version.keeps.files)
for (const auto& item : m_version.keeps.files) {
keeps.files.append(item); keeps.files.append(item);
} for (const auto& item : m_version.keeps.folders)
for (const auto& item : m_version.keeps.folders) {
keeps.folders.append(item); keeps.folders.append(item);
}
auto getPathForBase = [this](const QString& base) { auto getPathForBase = [this](const QString& base) {
auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft"); auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft");
if (base == "root") { if (base == "root") {
return minecraftPath; return minecraftPath;
} } else if (base == "config") {
if (base == "config") {
return FS::PathCombine(minecraftPath, "config"); return FS::PathCombine(minecraftPath, "config");
} else {
qWarning() << "Unrecognised base path" << base;
return minecraftPath;
} }
qWarning() << "Unrecognised base path" << base;
return minecraftPath;
}; };
auto convertToSystemPath = [](const QString& path) { auto convertToSystemPath = [](const QString& path) {
@ -246,22 +231,24 @@ void PackInstallTask::deleteExistingFiles()
}; };
auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) { auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) {
if (std::ranges::any_of(keeps.files, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { for (const auto& item : keeps.files) {
auto basePath = getPathForBase(item.base); auto basePath = getPathForBase(item.base);
auto targetPath = convertToSystemPath(item.target); auto targetPath = convertToSystemPath(item.target);
auto path = FS::PathCombine(basePath, targetPath); auto path = FS::PathCombine(basePath, targetPath);
return fullPath == path;
})) { if (fullPath == path) {
return true; return true;
}
} }
if (std::ranges::any_of(keeps.folders, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { for (const auto& item : keeps.folders) {
auto basePath = getPathForBase(item.base); auto basePath = getPathForBase(item.base);
auto targetPath = convertToSystemPath(item.target); auto targetPath = convertToSystemPath(item.target);
auto path = FS::PathCombine(basePath, targetPath); auto path = FS::PathCombine(basePath, targetPath);
return fullPath.startsWith(path);
})) { if (fullPath.startsWith(path)) {
return true; return true;
}
} }
return false; return false;
@ -275,9 +262,8 @@ void PackInstallTask::deleteExistingFiles()
auto targetPath = convertToSystemPath(item.target); auto targetPath = convertToSystemPath(item.target);
auto fullPath = FS::PathCombine(basePath, targetPath); auto fullPath = FS::PathCombine(basePath, targetPath);
if (shouldKeep(fullPath)) { if (shouldKeep(fullPath))
continue; continue;
}
filesToDelete.insert(fullPath); filesToDelete.insert(fullPath);
} }
@ -291,9 +277,8 @@ void PackInstallTask::deleteExistingFiles()
while (it.hasNext()) { while (it.hasNext()) {
auto path = it.next(); auto path = it.next();
if (shouldKeep(path)) { if (shouldKeep(path))
continue; continue;
}
filesToDelete.insert(path); filesToDelete.insert(path);
} }
@ -305,7 +290,7 @@ void PackInstallTask::deleteExistingFiles()
} }
} }
QString PackInstallTask::getDirForModType(ModType type, const QString& raw) QString PackInstallTask::getDirForModType(ModType type, QString raw)
{ {
switch (type) { switch (type) {
// Mod types that can either be ignored at this stage, or ignored // Mod types that can either be ignored at this stage, or ignored
@ -353,7 +338,7 @@ QString PackInstallTask::getDirForModType(ModType type, const QString& raw)
return Q_NULLPTR; return Q_NULLPTR;
} }
QString PackInstallTask::getVersionForLoader(const QString& uid) QString PackInstallTask::getVersionForLoader(QString uid)
{ {
if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) { if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) {
auto vlist = APPLICATION->metadataIndex()->get(uid); auto vlist = APPLICATION->metadataIndex()->get(uid);
@ -374,19 +359,16 @@ QString PackInstallTask::getVersionForLoader(const QString& uid)
// filtering for those loaders. // filtering for those loaders.
if (m_version.loader.type != "fabric") { if (m_version.loader.type != "fabric") {
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { return req.uid == "net.minecraft"; }); auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { return req.uid == "net.minecraft"; });
if (iter == reqs.end()) { if (iter == reqs.end())
continue; continue;
} if (iter->equalsVersion != m_version.minecraft)
if (iter->equalsVersion != m_version.minecraft) {
continue; continue;
}
} }
if (m_version.loader.recommended) { if (m_version.loader.recommended) {
// first recommended build we find, we use. // first recommended build we find, we use.
if (!version->isRecommended()) { if (!version->isRecommended())
continue; continue;
}
} }
return version->descriptor(); return version->descriptor();
@ -394,8 +376,7 @@ QString PackInstallTask::getVersionForLoader(const QString& uid)
emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type)); emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type));
return Q_NULLPTR; return Q_NULLPTR;
} } else if (m_version.loader.choose) {
if (m_version.loader.choose) {
// Fabric Loader doesn't depend on a given Minecraft version. // Fabric Loader doesn't depend on a given Minecraft version.
if (m_version.loader.type == "fabric") { if (m_version.loader.type == "fabric") {
return m_support->chooseVersion(vlist, Q_NULLPTR); return m_support->chooseVersion(vlist, Q_NULLPTR);
@ -439,8 +420,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library)
if (name == QString("guava")) { if (name == QString("guava")) {
return "com.google.guava:guava:" + version; return "com.google.guava:guava:" + version;
} } else if (name == QString("commons-lang3")) {
if (name == QString("commons-lang3")) {
return "org.apache.commons:commons-lang3:" + version; return "org.apache.commons:commons-lang3:" + version;
} }
} }
@ -448,7 +428,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library)
return "org.multimc.atlauncher:" + library.md5 + ":1"; return "org.multimc.atlauncher:" + library.md5 + ":1";
} }
bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, PackProfile* profile) bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile* profile)
{ {
if (m_version.libraries.isEmpty()) { if (m_version.libraries.isEmpty()) {
return true; return true;
@ -473,18 +453,18 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
} }
auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); auto id = QUuid::createUuid().toString(QUuid::WithoutBraces);
auto targetId = "org.multimc.atlauncher." + id; auto target_id = "org.multimc.atlauncher." + id;
auto patchDir = FS::PathCombine(instanceRoot, "patches"); auto patchDir = FS::PathCombine(instanceRoot, "patches");
if (!FS::ensureFolderPathExists(patchDir)) { if (!FS::ensureFolderPathExists(patchDir)) {
return false; return false;
} }
auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
auto f = std::make_shared<VersionFile>(); auto f = std::make_shared<VersionFile>();
f->name = m_pack_name + " " + m_version_name + " (libraries)"; f->name = m_pack_name + " " + m_version_name + " (libraries)";
const static QMap<QString, QString> s_liteLoaderMap = { const static QMap<QString, QString> liteLoaderMap = {
{ "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" }, { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" },
{ "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" }, { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" },
{ "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" }, { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" },
@ -504,8 +484,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
for (const auto& lib : m_version.libraries) { for (const auto& lib : m_version.libraries) {
// If the library is LiteLoader, we need to ignore it and handle it separately. // If the library is LiteLoader, we need to ignore it and handle it separately.
if (s_liteLoaderMap.contains(lib.md5)) { if (liteLoaderMap.contains(lib.md5)) {
auto ver = getComponentVersion("com.mumfrey.liteloader", s_liteLoaderMap.value(lib.md5)); auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5));
if (ver) { if (ver) {
componentsToInstall.insert("com.mumfrey.liteloader", ver); componentsToInstall.insert("com.mumfrey.liteloader", ver);
continue; continue;
@ -522,9 +502,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
libExempt = Version(libSpecifier.version()) >= Version(existingLib.version()); libExempt = Version(libSpecifier.version()) >= Version(existingLib.version());
} }
} }
if (libExempt) { if (libExempt)
continue; continue;
}
auto library = std::make_shared<Library>(); auto library = std::make_shared<Library>();
library->setRawName(libName); library->setRawName(libName);
@ -557,11 +536,11 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) });
return true; return true;
} }
bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfile* profile) bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* profile)
{ {
if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
return true; return true;
@ -592,13 +571,13 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
} }
auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); auto id = QUuid::createUuid().toString(QUuid::WithoutBraces);
auto targetId = "org.multimc.atlauncher." + id; auto target_id = "org.multimc.atlauncher." + id;
auto patchDir = FS::PathCombine(instanceRoot, "patches"); auto patchDir = FS::PathCombine(instanceRoot, "patches");
if (!FS::ensureFolderPathExists(patchDir)) { if (!FS::ensureFolderPathExists(patchDir)) {
return false; return false;
} }
auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
QStringList mainClasses; QStringList mainClasses;
QStringList tweakers; QStringList tweakers;
@ -625,9 +604,8 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
for (auto arg : args) { for (auto arg : args) {
if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") { if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
auto tweakClass = arg.remove("--tweakClass="); auto tweakClass = arg.remove("--tweakClass=");
if (tweakers.contains(tweakClass)) { if (tweakers.contains(tweakClass))
continue; continue;
}
f->addTweakers.append(tweakClass); f->addTweakers.append(tweakClass);
} }
@ -646,7 +624,7 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close(); file.close();
profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) });
return true; return true;
} }
@ -676,7 +654,7 @@ void PackInstallTask::installConfigs()
connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { connect(jobPtr.get(), &NetJob::failed, [this](QString reason) {
abortable = false; abortable = false;
jobPtr.reset(); jobPtr.reset();
emitFailed(std::move(reason)); emitFailed(reason);
}); });
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) { connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
abortable = true; abortable = true;
@ -733,17 +711,15 @@ void PackInstallTask::downloadMods()
jarmods.clear(); jarmods.clear();
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
QList<VersionMod> blockedMods; QList<VersionMod> blocked_mods;
for (const auto& mod : m_version.mods) { for (const auto& mod : m_version.mods) {
// skip non-client mods // skip non-client mods
if (!mod.client) { if (!mod.client)
continue; continue;
}
// skip optional mods that were not selected // skip optional mods that were not selected
if (mod.optional && !selectedMods.contains(mod.name)) { if (mod.optional && !selectedMods.contains(mod.name))
continue; continue;
}
QString url; QString url;
switch (mod.download) { switch (mod.download) {
@ -751,7 +727,7 @@ void PackInstallTask::downloadMods()
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
break; break;
case DownloadType::Browser: { case DownloadType::Browser: {
blockedMods.append(mod); blocked_mods.append(mod);
continue; continue;
} }
case DownloadType::Direct: case DownloadType::Direct:
@ -787,9 +763,8 @@ void PackInstallTask::downloadMods()
jobPtr->addNetAction(dl); jobPtr->addNetAction(dl);
} else { } else {
auto relpath = getDirForModType(mod.type, mod.type_raw); auto relpath = getDirForModType(mod.type, mod.type_raw);
if (relpath == Q_NULLPTR) { if (relpath == Q_NULLPTR)
continue; continue;
}
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName); auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true); entry->setStale(true);
@ -823,51 +798,49 @@ void PackInstallTask::downloadMods()
modsToCopy[entry->getFullPath()] = path; modsToCopy[entry->getFullPath()] = path;
} }
} }
if (!blockedMods.isEmpty()) { if (!blocked_mods.isEmpty()) {
QList<BlockedMod> mods; QList<BlockedMod> mods;
for (const auto& mod : blockedMods) { for (auto mod : blocked_mods) {
BlockedMod blockedMod; BlockedMod blocked_mod;
blockedMod.name = mod.file; blocked_mod.name = mod.file;
blockedMod.websiteUrl = mod.url; blocked_mod.websiteUrl = mod.url;
blockedMod.hash = mod.md5; blocked_mod.hash = mod.md5;
blockedMod.matched = false; blocked_mod.matched = false;
blockedMod.localPath = ""; blocked_mod.localPath = "";
mods.append(blockedMod); mods.append(blocked_mod);
} }
qWarning() << "Blocked mods found, displaying mod list"; qWarning() << "Blocked mods found, displaying mod list";
BlockedModsDialog messageDialog(nullptr, tr("Blocked mods found"), BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>" tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."), "You will need to manually download them and add them to the instance."),
mods, "md5"); mods, "md5");
messageDialog.setModal(true); message_dialog.setModal(true);
if (messageDialog.exec() != 0) { if (message_dialog.exec()) {
qDebug() << "Post dialog blocked mods list:" << mods; qDebug() << "Post dialog blocked mods list:" << mods;
for (const auto& blocked : mods) { for (auto blocked : mods) {
if (!blocked.matched) { if (!blocked.matched) {
qDebug() << blocked.name << "was not matched to a local file, skipping copy"; qDebug() << blocked.name << "was not matched to a local file, skipping copy";
continue; continue;
} }
auto modIter = auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(),
std::ranges::find_if(blockedMods, [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; });
if (modIter == blockedMods.end()) { if (modIter == blocked_mods.end())
continue; continue;
} auto mod = *modIter;
const auto& mod = *modIter;
if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) { if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
modsToExtract.insert(blocked.localPath, mod); modsToExtract.insert(blocked.localPath, mod);
} else if (mod.type == ModType::Decomp) { } else if (mod.type == ModType::Decomp) {
modsToDecomp.insert(blocked.localPath, mod); modsToDecomp.insert(blocked.localPath, mod);
} else { } else {
auto relpath = getDirForModType(mod.type, mod.type_raw); auto relpath = getDirForModType(mod.type, mod.type_raw);
if (relpath == Q_NULLPTR) { if (relpath == Q_NULLPTR)
continue; continue;
}
auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
@ -945,8 +918,8 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
setStatus(tr("Extracting mods...")); setStatus(tr("Extracting mods..."));
for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) {
const auto& modPath = iter.key(); auto& modPath = iter.key();
const auto& mod = iter.value(); auto& mod = iter.value();
QString extractToDir; QString extractToDir;
if (mod.type == ModType::Extract) { if (mod.type == ModType::Extract) {
@ -965,10 +938,6 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
folderToExtract = mod.extractFolder; folderToExtract = mod.extractFolder;
static const QRegularExpression s_regex("^/"); static const QRegularExpression s_regex("^/");
folderToExtract.remove(s_regex); folderToExtract.remove(s_regex);
if (isPathTraversal(extractToPath, folderToExtract)) {
qWarning() << "Blocked path traversal in" << mod.extractFolder;
return false;
}
} }
qDebug() << "Extracting " + mod.file + " to " + extractToDir; qDebug() << "Extracting " + mod.file + " to " + extractToDir;
@ -979,18 +948,13 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
} }
for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) {
const auto& modPath = iter.key(); auto& modPath = iter.key();
const auto& mod = iter.value(); auto& mod = iter.value();
auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw); auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw);
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile); auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile);
if (isPathTraversal(extractToPath, mod.decompFile)) {
qWarning() << "Blocked path traversal in decompFile" << mod.decompFile;
return false;
}
qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir; qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir;
if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) {
qWarning() << "Failed to extract" << mod.decompFile; qWarning() << "Failed to extract" << mod.decompFile;
@ -999,8 +963,8 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
} }
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
const auto& from = iter.key(); auto& from = iter.key();
const auto& to = iter.value(); auto& to = iter.value();
// If the file already exists, assume the mod is the correct copy - and remove // If the file already exists, assume the mod is the correct copy - and remove
// the copy from the Configs.zip // the copy from the Configs.zip
@ -1030,7 +994,7 @@ void PackInstallTask::install()
MinecraftInstance instance(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath); MinecraftInstance instance(m_globalSettings, std::make_unique<INISettingsObject>(instanceConfigPath), m_stagingPath);
{ {
SettingsObject::Lock lock(instance.settings()); SettingsObject::Lock lock(instance.settings());
auto* components = instance.getPackProfile(); auto components = instance.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
// Use a component to add libraries BEFORE Minecraft // Use a component to add libraries BEFORE Minecraft
@ -1045,23 +1009,20 @@ void PackInstallTask::install()
// Loader // Loader
if (m_version.loader.type == QString("forge")) { if (m_version.loader.type == QString("forge")) {
auto version = getVersionForLoader("net.minecraftforge"); auto version = getVersionForLoader("net.minecraftforge");
if (version == Q_NULLPTR) { if (version == Q_NULLPTR)
return; return;
}
components->setComponentVersion("net.minecraftforge", version); components->setComponentVersion("net.minecraftforge", version);
} else if (m_version.loader.type == QString("neoforge")) { } else if (m_version.loader.type == QString("neoforge")) {
auto version = getVersionForLoader("net.neoforged"); auto version = getVersionForLoader("net.neoforged");
if (version == Q_NULLPTR) { if (version == Q_NULLPTR)
return; return;
}
components->setComponentVersion("net.neoforged", version); components->setComponentVersion("net.neoforged", version);
} else if (m_version.loader.type == QString("fabric")) { } else if (m_version.loader.type == QString("fabric")) {
auto version = getVersionForLoader("net.fabricmc.fabric-loader"); auto version = getVersionForLoader("net.fabricmc.fabric-loader");
if (version == Q_NULLPTR) { if (version == Q_NULLPTR)
return; return;
}
components->setComponentVersion("net.fabricmc.fabric-loader", version); components->setComponentVersion("net.fabricmc.fabric-loader", version);
} else if (m_version.loader.type != QString()) { } else if (m_version.loader.type != QString()) {
@ -1094,4 +1055,9 @@ void PackInstallTask::install()
emitSucceeded(); emitSucceeded();
} }
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version)
{
return APPLICATION->metadataIndex()->getLoadedVersion(uid, version);
}
} // namespace ATLauncher } // namespace ATLauncher

View file

@ -44,13 +44,14 @@
#include "minecraft/MinecraftInstance.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h" #include "minecraft/PackProfile.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "settings/INISettingsObject.h"
#include <cstdint> #include <memory>
#include <optional> #include <optional>
namespace ATLauncher { namespace ATLauncher {
enum class InstallMode : std::uint8_t { enum class InstallMode {
Install, Install,
Reinstall, Reinstall,
Update, Update,
@ -85,13 +86,13 @@ class PackInstallTask : public InstanceTask {
QString packName, QString packName,
QString version, QString version,
InstallMode installMode = InstallMode::Install); InstallMode installMode = InstallMode::Install);
~PackInstallTask() override { delete m_support; } virtual ~PackInstallTask() { delete m_support; }
bool canAbort() const override { return true; } bool canAbort() const override { return true; }
bool abort() override; bool abort() override;
protected: protected:
void executeTask() override; virtual void executeTask() override;
private slots: private slots:
void onDownloadSucceeded(QByteArray* responsePtr); void onDownloadSucceeded(QByteArray* responsePtr);
@ -102,12 +103,12 @@ class PackInstallTask : public InstanceTask {
void onModsExtracted(); void onModsExtracted();
private: private:
QString getDirForModType(ModType type, const QString& raw); QString getDirForModType(ModType type, QString raw);
QString getVersionForLoader(const QString& uid); QString getVersionForLoader(QString uid);
static QString detectLibrary(const VersionLibrary& library); QString detectLibrary(const VersionLibrary& library);
bool createLibrariesComponent(const QString& instanceRoot, PackProfile* profile); bool createLibrariesComponent(QString instanceRoot, PackProfile* profile);
bool createPackComponent(const QString& instanceRoot, PackProfile* profile); bool createPackComponent(QString instanceRoot, PackProfile* profile);
void deleteExistingFiles(); void deleteExistingFiles();
void installConfigs(); void installConfigs();

View file

@ -79,7 +79,6 @@ class FlameAPI : public ResourceAPI {
case ModPlatform::LegacyFabric: case ModPlatform::LegacyFabric:
case ModPlatform::Ornithe: case ModPlatform::Ornithe:
case ModPlatform::Rift: case ModPlatform::Rift:
case ModPlatform::None:
break; // not supported break; // not supported
} }
return 0; return 0;

View file

@ -18,6 +18,8 @@
#include "net/NetJob.h" #include "net/NetJob.h"
#include "tasks/Task.h" #include "tasks/Task.h"
static FlameAPI api;
bool FlameCheckUpdate::abort() bool FlameCheckUpdate::abort()
{ {
bool result = false; bool result = false;
@ -37,7 +39,7 @@ void FlameCheckUpdate::executeTask()
{ {
setStatus(tr("Preparing resources for CurseForge...")); setStatus(tr("Preparing resources for CurseForge..."));
auto* netJob = new NetJob("Get latest versions", APPLICATION->network()); auto netJob = new NetJob("Get latest versions", APPLICATION->network());
connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods); connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods);
connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress); connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress);
@ -46,10 +48,9 @@ void FlameCheckUpdate::executeTask()
for (auto* resource : m_resources) { for (auto* resource : m_resources) {
auto project = std::make_shared<ModPlatform::IndexedPack>(); auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = resource->metadata()->project_id.toString(); project->addonId = resource->metadata()->project_id.toString();
auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions }); auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions });
if (!versionsUrlOptional.has_value()) { if (!versionsUrlOptional.has_value())
continue; continue;
}
auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value()); auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value());
@ -62,11 +63,11 @@ void FlameCheckUpdate::executeTask()
void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response) void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response)
{ {
QJsonParseError parseError{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parseError.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from latest mod version at" << parseError.offset qWarning() << "Error while parsing JSON response from latest mod version at" << parse_error.offset
<< "reason:" << parseError.errorString(); << "reason:" << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
return; return;
} }
@ -87,104 +88,100 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray*
qCritical() << e.what(); qCritical() << e.what();
qDebug() << doc; qDebug() << doc;
} }
auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty());
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
if (!latestVer.has_value() || !latestVer->addonId.isValid()) { if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
QString reason; QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr) { if (dynamic_cast<Mod*>(resource) != nullptr)
reason = reason =
tr("No valid version found for this resource. It's probably unavailable for the current game " tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader."); "version / mod loader.");
} else { else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
}
emit checkFailed(resource, reason); emit checkFailed(resource, reason);
return; return;
} }
if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) { if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
m_blocked[resource] = latestVer->fileId.toString(); m_blocked[resource] = latest_ver->fileId.toString();
return; return;
} }
if (!latestVer->hash.isEmpty() && if (!latest_ver->hash.isEmpty() &&
(resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NotInstalled)) { (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto oldVersion = resource->metadata()->version_number; auto old_version = resource->metadata()->version_number;
if (oldVersion.isEmpty()) { if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NotInstalled) { if (resource->status() == ResourceStatus::NOT_INSTALLED)
oldVersion = tr("Not installed"); old_version = tr("Not installed");
} else { else
oldVersion = tr("Unknown"); old_version = tr("Unknown");
}
} }
auto downloadTask = makeShared<ResourceDownloadTask>(pack, latestVer.value(), m_resourceModel, true, "update"); auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resourceModel);
m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type, m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()), api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled()); ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latestVer.value())); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
} }
void FlameCheckUpdate::collectBlockedMods() void FlameCheckUpdate::collectBlockedMods()
{ {
QStringList addonIds; QStringList addonIds;
QHash<QString, Resource*> quickSearch; QHash<QString, Resource*> quickSearch;
for (const auto& resource : m_blocked.keys()) { for (auto const& resource : m_blocked.keys()) {
auto addonId = resource->metadata()->project_id.toString(); auto addonId = resource->metadata()->project_id.toString();
addonIds.append(addonId); addonIds.append(addonId);
quickSearch[addonId] = resource; quickSearch[addonId] = resource;
} }
Task::Ptr projTask; Task::Ptr projTask;
QByteArray* response = nullptr; QByteArray* response;
if (addonIds.isEmpty()) { if (addonIds.isEmpty()) {
emitSucceeded(); emitSucceeded();
return; return;
} } else if (addonIds.size() == 1) {
if (addonIds.size() == 1) { std::tie(projTask, response) = api.getProject(*addonIds.begin());
std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin());
} else { } else {
std::tie(projTask, response) = FlameAPI().getProjects(addonIds); std::tie(projTask, response) = api.getProjects(addonIds);
} }
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
QJsonParseError parseError{}; QJsonParseError parse_error{};
auto doc = QJsonDocument::fromJson(*response, &parseError); auto doc = QJsonDocument::fromJson(*response, &parse_error);
if (parseError.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset
<< "reason:" << parseError.errorString(); << "reason:" << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
return; return;
} }
try { try {
QJsonArray entries; QJsonArray entries;
if (addonIds.size() == 1) { if (addonIds.size() == 1)
entries = { Json::requireObject(Json::requireObject(doc), "data") }; entries = { Json::requireObject(Json::requireObject(doc), "data") };
} else { else
entries = Json::requireArray(Json::requireObject(doc), "data"); entries = Json::requireArray(Json::requireObject(doc), "data");
}
for (auto entry : entries) { for (auto entry : entries) {
auto entryObj = Json::requireObject(entry); auto entry_obj = Json::requireObject(entry);
auto id = QString::number(Json::requireInteger(entryObj, "id")); auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto* resource = quickSearch.find(id).value(); auto resource = quickSearch.find(id).value();
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
try { try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
FlameMod::loadIndexedPack(pack, entryObj); FlameMod::loadIndexedPack(pack, entry_obj);
auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
recoverUrl); recover_url);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qDebug() << e.cause(); qDebug() << e.cause();
qDebug() << entries; qDebug() << entries;

View file

@ -23,9 +23,7 @@
namespace ExportToModList { namespace ExportToModList {
enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM };
enum OptionalDataValue { None = 0, Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 };
Q_DECLARE_FLAGS(OptionalData, OptionalDataValue)
QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData); QString exportToModList(QList<Mod*> mods, Formats format, OptionalData extraData);
QString exportToModList(QList<Mod*> mods, QString lineTemplate); QString exportToModList(QList<Mod*> mods, QString lineTemplate);
} // namespace ExportToModList } // namespace ExportToModList

View file

@ -63,12 +63,12 @@ void PackInstallTask::copySettings()
instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString());
} }
auto* components = instance.getPackProfile(); auto components = instance.getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
auto modloader = m_pack.loaderType; auto modloader = m_pack.loaderType;
if (modloader.has_value()) { if (modloader.has_value())
switch (modloader.value()) { switch (modloader.value()) {
case ModPlatform::NeoForge: { case ModPlatform::NeoForge: {
components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true); components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true);
@ -86,16 +86,28 @@ void PackInstallTask::copySettings()
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true); components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true);
break; break;
} }
default: case ModPlatform::Cauldron:
break;
case ModPlatform::LiteLoader:
break;
case ModPlatform::DataPack:
break;
case ModPlatform::Babric:
break;
case ModPlatform::BTA:
break;
case ModPlatform::LegacyFabric:
break;
case ModPlatform::Ornithe:
break;
case ModPlatform::Rift:
break; break;
} }
}
components->saveNow(); components->saveNow();
instance.setName(name()); instance.setName(name());
if (m_instIcon == "default") { if (m_instIcon == "default")
m_instIcon = "ftb_logo"; m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
} }
emitSucceeded(); emitSucceeded();

View file

@ -53,7 +53,7 @@ void ModrinthCheckUpdate::executeTask()
setStatus(tr("Preparing resources for Modrinth...")); setStatus(tr("Preparing resources for Modrinth..."));
setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1); setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1);
auto hashingTask = auto hashing_task =
makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
bool startHasing = false; bool startHasing = false;
for (auto* resource : m_resources) { for (auto* resource : m_resources) {
@ -63,11 +63,10 @@ void ModrinthCheckUpdate::executeTask()
// need to generate a new hash if the current one is innadequate // need to generate a new hash if the current one is innadequate
// (though it will rarely happen, if at all) // (though it will rarely happen, if at all)
if (resource->metadata()->hash_format != m_hashType) { if (resource->metadata()->hash_format != m_hashType) {
auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
connect(hashTask.get(), &Hashing::Hasher::resultsReady, connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); });
[this, resource](const QString& hash) { m_mappings.insert(hash, resource); }); connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); hashing_task->addTask(hash_task);
hashingTask->addTask(hashTask);
startHasing = true; startHasing = true;
} else { } else {
m_mappings.insert(hash, resource); m_mappings.insert(hash, resource);
@ -75,9 +74,9 @@ void ModrinthCheckUpdate::executeTask()
} }
if (startHasing) { if (startHasing) {
connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader);
m_job = hashingTask; m_job = hashing_task;
hashingTask->start(); hashing_task->start();
} else { } else {
checkNextLoader(); checkNextLoader();
} }
@ -121,14 +120,14 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
setStatus(tr("Parsing the API response from Modrinth...")); setStatus(tr("Parsing the API response from Modrinth..."));
setProgress(m_progress + 1, m_progressTotal); setProgress(m_progress + 1, m_progressTotal);
QJsonParseError parseError{}; QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parseError.error != QJsonParseError::NoError) { if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset
<< "reason:" << parseError.errorString(); << "reason:" << parse_error.errorString();
qWarning() << *response; qWarning() << *response;
emitFailed(parseError.errorString()); emitFailed(parse_error.errorString());
return; return;
} }
@ -139,11 +138,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
const QString hash = iter.key(); const QString hash = iter.key();
Resource* resource = iter.value(); Resource* resource = iter.value();
auto projectObj = doc[hash].toObject(); auto project_obj = doc[hash].toObject();
// If the returned project is empty, but we have Modrinth metadata, // If the returned project is empty, but we have Modrinth metadata,
// it means this specific version is not available // it means this specific version is not available
if (projectObj.isEmpty()) { if (project_obj.isEmpty()) {
qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash; qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash;
++iter; ++iter;
continue; continue;
@ -151,11 +150,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
// Sometimes a version may have multiple files, one with "forge" and one with "fabric", // Sometimes a version may have multiple files, one with "forge" and one with "fabric",
// so we may want to filter it // so we may want to filter it
QString loaderFilter; QString loader_filter;
if (loader.has_value() && loader != 0) { if (loader.has_value() && loader != 0) {
auto modLoaders = ModPlatform::modLoaderTypesToList(*loader); auto modLoaders = ModPlatform::modLoaderTypesToList(*loader);
if (!modLoaders.isEmpty()) { if (!modLoaders.isEmpty()) {
loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first()); loader_filter = ModPlatform::getModLoaderAsString(modLoaders.first());
} }
} }
@ -165,9 +164,9 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case) // - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
// Such is the pain of having arbitrary files for a given version .-. // Such is the pain of having arbitrary files for a given version .-.
auto projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter); auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter);
if (projectVer.downloadUrl.isEmpty()) { if (project_ver.downloadUrl.isEmpty()) {
qCritical() << "Modrinth mod without download url!" << projectVer.fileName; qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
++iter; ++iter;
continue; continue;
} }
@ -178,22 +177,21 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio
pack->slug = resource->metadata()->slug; pack->slug = resource->metadata()->slug;
pack->addonId = resource->metadata()->project_id; pack->addonId = resource->metadata()->project_id;
pack->provider = ModPlatform::ResourceProvider::MODRINTH; pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NotInstalled)) { if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
auto downloadTask = makeShared<ResourceDownloadTask>(pack, projectVer, m_resourceModel, true, "update"); auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resourceModel);
QString oldVersion = resource->metadata()->version_number; QString old_version = resource->metadata()->version_number;
if (oldVersion.isEmpty()) { if (old_version.isEmpty()) {
if (resource->status() == ResourceStatus::NotInstalled) { if (resource->status() == ResourceStatus::NOT_INSTALLED)
oldVersion = tr("Not installed"); old_version = tr("Not installed");
} else { else
oldVersion = tr("Unknown"); old_version = tr("Unknown");
}
} }
m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type, m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type,
projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled()); project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled());
} }
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, projectVer)); m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
iter = m_mappings.erase(iter); iter = m_mappings.erase(iter);
} }
@ -213,22 +211,20 @@ void ModrinthCheckUpdate::checkNextLoader()
if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades
getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize); getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize);
return; return;
} } else if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader
if (m_loadersList.isEmpty() && m_loaderIdx == 0) { // this are other resources no need to check more than once with empty loader
getUpdateModsForLoader(); getUpdateModsForLoader();
return; return;
} }
for (auto* resource : m_mappings) { for (auto resource : m_mappings) {
QString reason; QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr) { if (dynamic_cast<Mod*>(resource) != nullptr)
reason = reason =
tr("No valid version found for this resource. It's probably unavailable for the current game " tr("No valid version found for this resource. It's probably unavailable for the current game "
"version / mod loader."); "version / mod loader.");
} else { else
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
}
emit checkFailed(resource, reason); emit checkFailed(resource, reason);
} }

View file

@ -16,10 +16,7 @@
#include "net/ChecksumValidator.h" #include "net/ChecksumValidator.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiHeaderProxy.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include "modplatform/ModIndex.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
@ -32,119 +29,114 @@
bool ModrinthCreationTask::abort() bool ModrinthCreationTask::abort()
{ {
if (!canAbort()) { if (!canAbort())
return false; return false;
}
if (m_task) { if (m_task)
m_task->abort(); m_task->abort();
}
return InstanceCreationTask::abort(); return InstanceCreationTask::abort();
} }
bool ModrinthCreationTask::updateInstance() bool ModrinthCreationTask::updateInstance()
{ {
auto* instanceList = APPLICATION->instances(); auto instance_list = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack? // FIXME: How to handle situations when there's more than one install already for a given modpack?
BaseInstance* inst = nullptr; BaseInstance* inst;
if (auto originalId = originalInstanceID(); !originalId.isEmpty()) { if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instanceList->getInstanceById(originalId); inst = instance_list->getInstanceById(original_id);
Q_ASSERT(inst); Q_ASSERT(inst);
} else { } else {
inst = instanceList->getInstanceByManagedName(originalName()); inst = instance_list->getInstanceByManagedName(originalName());
if (!inst) { if (!inst) {
inst = instanceList->getInstanceById(originalName()); inst = instance_list->getInstanceById(originalName());
if (!inst) { if (!inst)
return false; return false;
}
} }
} }
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(indexPath, m_files, true, false)) { if (!parseManifest(index_path, m_files, true, false))
return false; return false;
}
auto versionName = inst->getManagedPackVersionName(); auto version_name = inst->getManagedPackVersionName();
m_root_path = QFileInfo(inst->gameRoot()).fileName(); m_root_path = QFileInfo(inst->gameRoot()).fileName();
auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : ""; auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
if (shouldConfirmUpdate()) { if (shouldConfirmUpdate()) {
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr); auto should_update = askIfShouldUpdate(m_parent, version_str);
if (shouldUpdate == ShouldUpdate::SkipUpdating) { if (should_update == ShouldUpdate::SkipUpdating)
return false; return false;
} if (should_update == ShouldUpdate::Cancel) {
if (shouldUpdate == ShouldUpdate::Cancel) {
m_abort = true; m_abort = true;
return false; return false;
} }
} }
// Remove repeated files, we don't need to download them! // Remove repeated files, we don't need to download them!
QDir oldInstDir(inst->instanceRoot()); QDir old_inst_dir(inst->instanceRoot());
QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack")); QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack"));
QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json")); QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json"));
QFileInfo oldIndexFile(oldIndexPath); QFileInfo old_index_file(old_index_path);
if (oldIndexFile.exists()) { if (old_index_file.exists()) {
std::vector<File> oldFiles; std::vector<File> old_files;
parseManifest(oldIndexPath, oldFiles, false, false); parseManifest(old_index_path, old_files, false, false);
// Let's remove all duplicated, identical resources! // Let's remove all duplicated, identical resources!
auto filesIterator = m_files.begin(); auto files_iterator = m_files.begin();
begin: begin:
while (filesIterator != m_files.end()) { while (files_iterator != m_files.end()) {
const auto& file = *filesIterator; auto const& file = *files_iterator;
auto oldFilesIterator = oldFiles.begin(); auto old_files_iterator = old_files.begin();
while (oldFilesIterator != oldFiles.end()) { while (old_files_iterator != old_files.end()) {
const auto& oldFile = *oldFilesIterator; auto const& old_file = *old_files_iterator;
if (oldFile.hash == file.hash) { if (old_file.hash == file.hash) {
qDebug() << "Removed file at" << file.path << "from list of downloads"; qDebug() << "Removed file at" << file.path << "from list of downloads";
filesIterator = m_files.erase(filesIterator); files_iterator = m_files.erase(files_iterator);
oldFilesIterator = oldFiles.erase(oldFilesIterator); old_files_iterator = old_files.erase(old_files_iterator);
goto begin; // Sorry :c goto begin; // Sorry :c
} }
oldFilesIterator++; old_files_iterator++;
} }
filesIterator++; files_iterator++;
} }
QDir oldMinecraftDir(inst->gameRoot()); QDir old_minecraft_dir(inst->gameRoot());
// Some files were removed from the old version, and some will be downloaded in an updated version, // Some files were removed from the old version, and some will be downloaded in an updated version,
// so we're fine removing them! // so we're fine removing them!
if (!oldFiles.empty()) { if (!old_files.empty()) {
for (const auto& file : oldFiles) { for (auto const& file : old_files) {
scheduleToDelete(m_parent, oldMinecraftDir, file.path, true); scheduleToDelete(m_parent, old_minecraft_dir, file.path, true);
} }
} }
// We will remove all the previous overrides, to prevent duplicate files! // We will remove all the previous overrides, to prevent duplicate files!
// TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides?
// FIXME: We may want to do something about disabled mods. // FIXME: We may want to do something about disabled mods.
auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder); auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (const auto& entry : oldOverrides) { for (const auto& entry : old_overrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry); scheduleToDelete(m_parent, old_minecraft_dir, entry);
} }
auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder); auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder);
for (const auto& entry : oldClientOverrides) { for (const auto& entry : old_client_overrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry); scheduleToDelete(m_parent, old_minecraft_dir, entry);
} }
} else { } else {
// We don't have an old index file, so we may duplicate stuff! // We don't have an old index file, so we may duplicate stuff!
auto* dialog = CustomMessageBox::selectable(m_parent, tr("No index file."), auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
tr("We couldn't find a suitable index file for the older version. This may cause some " tr("We couldn't find a suitable index file for the older version. This may cause some "
"of the files to be duplicated. Do you want to continue?"), "of the files to be duplicated. Do you want to continue?"),
QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel);
if (dialog->exec() == QDialog::DialogCode::Rejected) { if (dialog->exec() == QDialog::DialogCode::Rejected) {
m_abort = true; m_abort = true;
@ -166,40 +158,39 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
{ {
QEventLoop loop; QEventLoop loop;
QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack")); QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack"));
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) { if (m_files.empty() && !parseManifest(index_path, m_files, true, true))
return nullptr; return nullptr;
}
// Keep index file in case we need it some other time (like when changing versions) // Keep index file in case we need it some other time (like when changing versions)
QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json")); QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json"));
FS::ensureFilePathExists(newIndexPlace); FS::ensureFilePathExists(new_index_place);
FS::move(indexPath, newIndexPlace); FS::move(index_path, new_index_place);
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
auto overridePath = FS::PathCombine(m_stagingPath, "overrides"); auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) { if (QFile::exists(override_path)) {
// Create a list of overrides in "overrides.txt" inside mrpack/ // Create a list of overrides in "overrides.txt" inside mrpack/
Override::createOverrides("overrides", parentFolder, overridePath); Override::createOverrides("overrides", parent_folder, override_path);
// Apply the overrides // Apply the overrides
if (!FS::move(overridePath, mcPath)) { if (!FS::move(override_path, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + "overrides"); setError(tr("Could not rename the overrides folder:\n") + "overrides");
return nullptr; return nullptr;
} }
} }
// Do client overrides // Do client overrides
auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides"); auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(clientOverridePath)) { if (QFile::exists(client_override_path)) {
// Create a list of overrides in "client-overrides.txt" inside mrpack/ // Create a list of overrides in "client-overrides.txt" inside mrpack/
Override::createOverrides("client-overrides", parentFolder, clientOverridePath); Override::createOverrides("client-overrides", parent_folder, client_override_path);
// Apply the overrides // Apply the overrides
if (!FS::overrideFolder(mcPath, clientOverridePath)) { if (!FS::overrideFolder(mcPath, client_override_path)) {
setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
return nullptr; return nullptr;
} }
@ -209,27 +200,18 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
auto instanceSettings = std::make_unique<INISettingsObject>(configPath); auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath); auto instance = std::make_unique<MinecraftInstance>(m_globalSettings, std::move(instanceSettings), m_stagingPath);
auto* components = instance->getPackProfile(); auto components = instance->getPackProfile();
components->buildingFromScratch(); components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_minecraft_version, true); components->setComponentVersion("net.minecraft", m_minecraft_version, true);
QString loader; if (!m_fabric_version.isEmpty())
if (!m_fabric_version.isEmpty()) {
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric); if (!m_quilt_version.isEmpty())
}
if (!m_quilt_version.isEmpty()) {
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt); if (!m_forge_version.isEmpty())
}
if (!m_forge_version.isEmpty()) {
components->setComponentVersion("net.minecraftforge", m_forge_version); components->setComponentVersion("net.minecraftforge", m_forge_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge); if (!m_neoForge_version.isEmpty())
}
if (!m_neoForge_version.isEmpty()) {
components->setComponentVersion("net.neoforged", m_neoForge_version); components->setComponentVersion("net.neoforged", m_neoForge_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge);
}
if (m_instIcon != "default") { if (m_instIcon != "default") {
instance->setIconKey(m_instIcon); instance->setIconKey(m_instIcon);
@ -238,35 +220,34 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
} }
// Don't add managed info to packs without an ID (most likely imported from ZIP) // Don't add managed info to packs without an ID (most likely imported from ZIP)
if (!m_managed_id.isEmpty()) { if (!m_managed_id.isEmpty())
instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version()); instance->setManagedPack("modrinth", m_managed_id, m_managed_name, m_managed_version_id, version());
} else { else
instance->setManagedPack("modrinth", "", name(), "", ""); instance->setManagedPack("modrinth", "", name(), "", "");
}
instance->setName(name()); instance->setName(name());
instance->saveNow(); instance->saveNow();
auto downloadMods = makeShared<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network()); auto downloadMods = makeShared<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network());
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path); auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath); auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
// TODO make this work with other sorts of resource // TODO make this work with other sorts of resource
QHash<QString, Resource*> resources; QHash<QString, Resource*> resources;
for (auto& file : m_files) { for (auto& file : m_files) {
auto fileName = file.path; auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName); fileName = FS::RemoveInvalidPathChars(fileName);
auto filePath = FS::PathCombine(rootModpackPath, fileName); auto file_path = FS::PathCombine(root_modpack_path, fileName);
if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) { if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
// This means we somehow got out of the root folder, so abort here to prevent exploits // This means we somehow got out of the root folder, so abort here to prevent exploits
setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.") setError(tr("One of the files has a path that leads to an arbitrary location (%1). This is a security risk and isn't allowed.")
.arg(fileName)); .arg(fileName));
return nullptr; return nullptr;
} }
if (fileName.startsWith("mods/")) { if (fileName.startsWith("mods/")) {
auto* mod = new Mod(filePath); auto mod = new Mod(file_path);
ModDetails d; ModDetails d;
d.mod_id = filePath; d.mod_id = file_path;
mod->setDetails(d); mod->setDetails(d);
resources[file.hash.toHex()] = mod; resources[file.hash.toHex()] = mod;
} }
@ -274,39 +255,29 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return nullptr; return nullptr;
} }
qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
Net::ModrinthDownloadMeta meta{
.reason = m_instance.has_value() ? "update" : "modpack",
.gameVersion = m_minecraft_version,
.loader = loader,
};
QUrl downloadUrl = file.downloads.dequeue();
auto dl = Net::ApiDownload::makeFile(downloadUrl, filePath, Net::Download::Option::NoOptions, meta);
dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
downloadMods->addNetAction(dl); downloadMods->addNetAction(dl);
if (!file.downloads.empty()) { if (!file.downloads.empty()) {
// FIXME: This really needs to be put into a ConcurrentTask of // FIXME: This really needs to be put into a ConcurrentTask of
// MultipleOptionsTask's , once those exist :) // MultipleOptionsTask's , once those exist :)
auto param = dl.toWeakRef(); auto param = dl.toWeakRef();
connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] { connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] {
QUrl fallbackUrl = file.downloads.dequeue(); auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta);
ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash));
downloadMods->addNetAction(ndl); downloadMods->addNetAction(ndl);
if (auto shared = param.lock()) { if (auto shared = param.lock())
shared->succeeded(); shared->succeeded();
}
}); });
} }
} }
bool endedWell = false; bool ended_well = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; }); connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) { connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) {
endedWell = false; ended_well = false;
setError(reason); setError(reason);
}); });
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
@ -322,8 +293,8 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
loop.exec(); loop.exec();
if (!endedWell) { if (!ended_well) {
for (auto* resource : resources) { for (auto resource : resources) {
delete resource; delete resource;
} }
return nullptr; return nullptr;
@ -332,7 +303,7 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
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);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; }); connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
@ -344,38 +315,40 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask; m_task = ensureMetadataTask;
ensureMetaLoop.exec(); ensureMetaLoop.exec();
for (auto* resource : resources) { for (auto resource : resources) {
delete resource; delete resource;
} }
resources.clear(); resources.clear();
// Update information of the already installed instance, if any. // Update information of the already installed instance, if any.
if (m_instance && endedWell) { if (m_instance && ended_well) {
setAbortable(false); setAbortable(false);
auto* inst = m_instance.value(); auto inst = m_instance.value();
// Only change the name if it didn't use a custom name, so that the previous custom name // Only change the name if it didn't use a custom name, so that the previous custom name
// is preserved, but if we're using the original one, we update the version string. // is preserved, but if we're using the original one, we update the version string.
// NOTE: This needs to come before the copyManagedPack call! // NOTE: This needs to come before the copyManagedPack call!
if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) { if (inst->name().contains(inst->getManagedPackVersionName()) && inst->name() != instance->name()) {
if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange) { if (askForChangingInstanceName(m_parent, inst->name(), instance->name()) == InstanceNameChange::ShouldChange)
inst->setName(instance->name()); inst->setName(instance->name());
}
} }
inst->copyManagedPack(*instance); inst->copyManagedPack(*instance);
} }
if (endedWell) { if (ended_well) {
return instance; return instance;
} }
return nullptr; return nullptr;
} }
bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<File>& files, bool setInternalData, bool showOptionalDialog) bool ModrinthCreationTask::parseManifest(const QString& index_path,
std::vector<File>& files,
bool set_internal_data,
bool show_optional_dialog)
{ {
try { try {
auto doc = Json::requireDocument(indexPath); auto doc = Json::requireDocument(index_path);
auto obj = Json::requireObject(doc, "modrinth.index.json"); auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
if (formatVersion == 1) { if (formatVersion == 1) {
@ -384,10 +357,9 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
throw JSONValidationError("Unknown game: " + game); throw JSONValidationError("Unknown game: " + game);
} }
if (setInternalData) { if (set_internal_data) {
if (m_managed_version_id.isEmpty()) { if (m_managed_version_id.isEmpty())
m_managed_version_id = obj["versionId"].toString(); m_managed_version_id = obj["versionId"].toString();
}
m_managed_name = obj["name"].toString(); m_managed_name = obj["name"].toString();
} }
@ -403,8 +375,7 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
QString support = env["client"].toString("unsupported"); QString support = env["client"].toString("unsupported");
if (support == "unsupported") { if (support == "unsupported") {
continue; continue;
} } else if (support == "optional") {
if (support == "optional") {
file.required = false; file.required = false;
} }
} }
@ -416,21 +387,20 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces) // (as Modrinth seems to incorrectly handle spaces)
auto downloadArr = modInfo["downloads"].toArray(); auto download_arr = modInfo["downloads"].toArray();
for (auto download : downloadArr) { for (auto download : download_arr) {
qWarning() << download.toString(); qWarning() << download.toString();
bool isLast = download.toString() == downloadArr.last().toString(); bool is_last = download.toString() == download_arr.last().toString();
auto downloadUrl = QUrl(download.toString()); auto download_url = QUrl(download.toString());
if (!downloadUrl.isValid()) { if (!download_url.isValid()) {
qDebug() qDebug()
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path); << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path);
if (isLast && file.downloads.isEmpty()) { if (is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
} else { } else {
file.downloads.push_back(downloadUrl); file.downloads.push_back(download_url);
} }
} }
@ -438,11 +408,10 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
} }
if (!optionalFiles.empty()) { if (!optionalFiles.empty()) {
if (showOptionalDialog) { if (show_optional_dialog) {
QStringList oFiles; QStringList oFiles;
for (const auto& file : optionalFiles) { for (auto file : optionalFiles)
oFiles.push_back(file.path); oFiles.push_back(file.path);
}
OptionalModDialog optionalModDialog(m_parent, oFiles); OptionalModDialog optionalModDialog(m_parent, oFiles);
if (optionalModDialog.exec() == QDialog::Rejected) { if (optionalModDialog.exec() == QDialog::Rejected) {
emitAborted(); emitAborted();
@ -465,7 +434,7 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
} }
} }
} }
if (setInternalData) { if (set_internal_data) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key(); QString name = it.key();

View file

@ -24,18 +24,18 @@ class ModrinthCreationTask final : public InstanceCreationTask {
}; };
public: public:
ModrinthCreationTask(const QString& stagingPath, ModrinthCreationTask(QString staging_path,
SettingsObject* globalSettings, SettingsObject* global_settings,
QWidget* parent, QWidget* parent,
QString id, QString id,
QString versionId = {}, QString version_id = {},
QString originalInstanceId = {}) QString original_instance_id = {})
: m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId)) : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
{ {
setStagingPath(stagingPath); setStagingPath(staging_path);
setParentSettings(globalSettings); setParentSettings(global_settings);
m_original_instance_id = std::move(originalInstanceId); m_original_instance_id = std::move(original_instance_id);
} }
bool abort() override; bool abort() override;
@ -44,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
std::unique_ptr<MinecraftInstance> createInstance() override; std::unique_ptr<MinecraftInstance> createInstance() override;
private: private:
bool parseManifest(const QString&, std::vector<File>&, bool setInternalData = true, bool showOptionalDialog = true); bool parseManifest(const QString&, std::vector<File>&, bool set_internal_data = true, bool show_optional_dialog = true);
private: private:
QWidget* m_parent = nullptr; QWidget* m_parent = nullptr;

View file

@ -18,30 +18,28 @@
*/ */
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include <utility>
#include "net/ApiHeaderProxy.h" #include "net/ApiHeaderProxy.h"
namespace Net { namespace Net {
Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options) Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options)
{ {
auto dl = Download::makeCached(std::move(url), std::move(entry), options); auto dl = Download::makeCached(url, entry, options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>()); dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
return dl; return dl;
} }
std::pair<Download::Ptr, QByteArray*> ApiDownload::makeByteArray(QUrl url, Download::Options options) std::pair<Download::Ptr, QByteArray*> ApiDownload::makeByteArray(QUrl url, Download::Options options)
{ {
auto [dl, response] = Download::makeByteArray(std::move(url), options); auto [dl, response] = Download::makeByteArray(url, options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>()); dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
return { dl, response }; return { dl, response };
} }
Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options)
{ {
auto dl = Download::makeFile(std::move(url), std::move(path), options); auto dl = Download::makeFile(url, path, options);
dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>(std::move(meta))); dl->addHeaderProxy(std::make_unique<ApiHeaderProxy>());
return dl; return dl;
} }

View file

@ -20,17 +20,13 @@
#pragma once #pragma once
#include "Download.h" #include "Download.h"
#include "net/ApiHeaderProxy.h"
namespace Net { namespace Net {
namespace ApiDownload { namespace ApiDownload {
Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions);
std::pair<Download::Ptr, QByteArray*> makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); std::pair<Download::Ptr, QByteArray*> makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions);
Download::Ptr makeFile(QUrl url, Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions);
QString path,
Download::Options options = Download::Option::NoOptions,
ModrinthDownloadMeta meta = ModrinthDownloadMeta());
}; // namespace ApiDownload }; // namespace ApiDownload
} // namespace Net } // namespace Net

View file

@ -23,64 +23,27 @@
#include "BuildConfig.h" #include "BuildConfig.h"
#include "net/HeaderProxy.h" #include "net/HeaderProxy.h"
#include <QJsonDocument>
#include <QJsonObject>
namespace Net { namespace Net {
struct ModrinthDownloadMeta {
QString reason;
QString gameVersion;
QString loader;
bool isEmpty() const { return reason.isEmpty(); }
QByteArray toJson() const
{
QJsonObject obj;
if (!reason.isEmpty()) {
obj["reason"] = reason;
}
if (!gameVersion.isEmpty()) {
obj["game_version"] = gameVersion;
}
if (!loader.isEmpty()) {
obj["loader"] = loader;
}
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}
};
class ApiHeaderProxy : public HeaderProxy { class ApiHeaderProxy : public HeaderProxy {
public: public:
ApiHeaderProxy() = default; ApiHeaderProxy() : HeaderProxy() {}
explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} virtual ~ApiHeaderProxy() = default;
~ApiHeaderProxy() override = default;
public: public:
QList<HeaderPair> headers(const QNetworkRequest& request) const override virtual QList<HeaderPair> headers(const QNetworkRequest& request) const override
{ {
QList<HeaderPair> hdrs; QList<HeaderPair> hdrs;
const auto host = request.url().host(); if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) {
hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() });
if (APPLICATION->capabilities() & Application::SupportsFlame && } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() ||
(host == QUrl(BuildConfig.FLAME_BASE_URL).host() || host == BuildConfig.FLAME_DOWNLOAD_HOST)) { request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
hdrs.append({ .headerName = "x-api-key", .headerValue = APPLICATION->getFlameAPIKey().toUtf8() });
} else if (host == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || host == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) {
QString token = APPLICATION->getModrinthAPIToken(); QString token = APPLICATION->getModrinthAPIToken();
if (!token.isNull()) { if (!token.isNull())
hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); hdrs.append({ "Authorization", token.toUtf8() });
}
}
if (host == BuildConfig.MODRINTH_DOWNLOAD_HOST && !m_meta.isEmpty()) {
hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() });
} }
return hdrs; return hdrs;
}; };
private:
ModrinthDownloadMeta m_meta;
}; };
} // namespace Net } // namespace Net

View file

@ -38,6 +38,7 @@
#include "Validator.h" #include "Validator.h"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QFile>
namespace Net { namespace Net {
class ChecksumValidator : public Validator { class ChecksumValidator : public Validator {
@ -68,10 +69,10 @@ class ChecksumValidator : public Validator {
return true; return true;
} }
auto validate(QNetworkReply& reply) -> bool override auto validate(QNetworkReply&) -> bool override
{ {
if (!m_expected.isEmpty() && m_expected != hash()) { if (m_expected.size() && m_expected != hash()) {
qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash(); qWarning() << "Checksum mismatch, download is bad.";
return false; return false;
} }
return true; return true;

View file

@ -182,7 +182,7 @@ auto HttpMetaCache::evictAll() -> bool
} }
map.entry_list.clear(); map.entry_list.clear();
// AND all return codes together so the result is true iff all runs of deletePath() are true // AND all return codes together so the result is true iff all runs of deletePath() are true
ret &= FS::deleteContents(map.base_path); ret &= FS::deletePath(map.base_path);
} }
return ret; return ret;
} }

View file

@ -69,15 +69,11 @@ void NetJob::executeNextSubTask()
// We're finished, check for failures and retry if we can (up to 3 times) // We're finished, check for failures and retry if we can (up to 3 times)
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) { if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1; m_try += 1;
m_failed.removeIf([this](QHash<Task*, Task::Ptr>::iterator task) { while (!m_failed.isEmpty()) {
// there is no point in retying on 404 Not Found auto task = m_failed.take(*m_failed.keyBegin());
if (static_cast<Net::NetRequest*>(task->get())->replyStatusCode() == 404) { m_done.remove(task.get());
return false; m_queue.enqueue(task);
} }
m_done.remove(task->get());
m_queue.enqueue(*task);
return true;
});
} }
ConcurrentTask::executeNextSubTask(); ConcurrentTask::executeNextSubTask();
} }
@ -104,18 +100,13 @@ auto NetJob::canAbort() const -> bool
auto NetJob::abort() -> bool auto NetJob::abort() -> bool
{ {
bool fullyAborted = true;
// fail all downloads on the queue // fail all downloads on the queue
for (auto task : m_queue) for (auto task : m_queue)
m_failed.insert(task.get(), task); m_failed.insert(task.get(), task);
m_queue.clear(); m_queue.clear();
if (m_doing.isEmpty()) {
// no downloads to abort, NetJob is not running
return true;
}
bool fullyAborted = true;
// abort active downloads // abort active downloads
auto toKill = m_doing.values(); auto toKill = m_doing.values();
for (auto part : toKill) { for (auto part : toKill) {

View file

@ -40,18 +40,4 @@ inline bool isApplicationError(QNetworkReply::NetworkError x)
QNetworkReply::UnknownContentError }; QNetworkReply::UnknownContentError };
return errors.contains(x); return errors.contains(x);
} }
// 500 class errors, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/500
// microsoft may send these error codes when services (auth) are down.
// We treat this as a reason to launch in offline mode.
inline bool isServerError(QNetworkReply::NetworkError x)
{
static QSet<QNetworkReply::NetworkError> errors = { QNetworkReply::InternalServerError,
QNetworkReply::OperationNotImplementedError,
QNetworkReply::ServiceUnavailableError, // 503 | seen in logs in 2026
//QNetworkReply::GatewayTimeoutError, // 504 | seen in logs in 2024
// Qt doesn't have it mapped. Unknown covers it
QNetworkReply::UnknownServerError };
return errors.contains(x);
}
} // namespace Net } // namespace Net

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

View file

@ -118,7 +118,7 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State
Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot) Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot)
{ {
auto up = makeShared<ImgurUpload>(m_shot->m_file); auto up = makeShared<ImgurUpload>(m_shot->m_file);
up->m_url = BuildConfig.IMGUR_BASE_URL + "image"; up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image");
up->m_sink.reset(new Sink(m_shot)); up->m_sink.reset(new Sink(m_shot));
up->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(QList<Net::HeaderPair>{ up->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(QList<Net::HeaderPair>{
{ "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } }));

View file

@ -35,8 +35,6 @@
*/ */
#include "settings/INIFile.h" #include "settings/INIFile.h"
#include <AssertHelpers.h>
#include <FileSystem.h> #include <FileSystem.h>
#include <QDebug> #include <QDebug>
@ -64,10 +62,11 @@ bool INIFile::saveFile(QString fileName)
_settings_obj.sync(); _settings_obj.sync();
if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) {
// Shouldn't be possible!
Q_ASSERT(status != QSettings::Status::FormatError);
if (status == QSettings::Status::AccessError) if (status == QSettings::Status::AccessError)
qCritical() << "An access error occurred while saving INI file" << fileName << "(is the file read-only?)"; qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
if (ASSERT_NEVER(status == QSettings::Status::FormatError))
qCritical() << "A format error occurred while saving INI file" << fileName << "(this shouldn't be possible!)";
return false; return false;
} }
@ -179,9 +178,9 @@ bool INIFile::loadFile(QString fileName)
if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) {
if (status == QSettings::Status::AccessError) if (status == QSettings::Status::AccessError)
qCritical() << "An access error occurred while loading INI file" << fileName; qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
if (status == QSettings::Status::FormatError) if (status == QSettings::Status::FormatError)
qCritical() << "A format error occurred while loading INI file" << fileName << "(is the file malformed or corrupted?)"; qCritical() << "A format error occurred (e.g. loading a malformed INI file).";
return false; return false;
} }
if (!_settings_obj.value("ConfigVersion").isValid()) { if (!_settings_obj.value("ConfigVersion").isValid()) {

View file

@ -48,13 +48,6 @@ Task::Task(bool show_debug) : m_show_debug(show_debug)
setAutoDelete(false); setAutoDelete(false);
} }
Task::~Task()
{
if (isRunning()) {
qCWarning(taskLogC) << "Task" << describe() << "disposed while running!";
}
}
void Task::setStatus(const QString& new_status) void Task::setStatus(const QString& new_status)
{ {
if (m_status != new_status) { if (m_status != new_status) {

View file

@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable {
public: public:
explicit Task(bool show_debug_log = true); explicit Task(bool show_debug_log = true);
~Task() override; virtual ~Task() = default;
bool isRunning() const; bool isRunning() const;
bool isFinished() const; bool isFinished() const;
@ -165,7 +165,7 @@ class Task : public QObject, public QRunnable {
//! used by external code to ask the task to abort //! used by external code to ask the task to abort
virtual bool abort() virtual bool abort()
{ {
if (canAbort() && isRunning()) if (canAbort())
emitAborted(); emitAborted();
return canAbort(); return canAbort();
} }

View file

@ -183,7 +183,8 @@ void POTranslatorPrivate::reload()
nextFuzzy = true; nextFuzzy = true;
} }
} else if (line.startsWith('"')) { } else if (line.startsWith('"')) {
QByteArray* out = nullptr; QByteArray temp;
QByteArray* out = &temp;
switch (mode) { switch (mode) {
case Mode::First: case Mode::First:

View file

@ -36,9 +36,13 @@
#include "TranslationsModel.h" #include "TranslationsModel.h"
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <memory> #include <QDir>
#include <utility> #include <QLibraryInfo>
#include <QLocale>
#include <QTranslator>
#include <locale>
#include "BuildConfig.h" #include "BuildConfig.h"
#include "FileSystem.h" #include "FileSystem.h"
@ -51,26 +55,18 @@
#include "Application.h" #include "Application.h"
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
static constexpr QLatin1String g_defaultLangCode("en_US"); const static QLatin1String defaultLangCode("en_US");
namespace { enum class FileType { NONE, QM, PO };
enum class FileType : std::uint8_t { None, Qm, Po };
QString getSystemLocaleName()
{
return QLocale::system().name();
}
QString getSystemLanguage()
{
return getSystemLocaleName().split('_').front();
}
} // namespace
struct Language { struct Language {
Language() : updated(true) {} Language() { updated = true; }
Language(const QString& _key)
explicit Language(QString _key) : key(std::move(_key)), updated(key == g_defaultLangCode) { locale = QLocale(key); } {
key = _key;
locale = QLocale(key);
updated = (key == defaultLangCode);
}
QString languageName() const QString languageName() const
{ {
@ -102,12 +98,12 @@ struct Language {
float percentTranslated() const float percentTranslated() const
{ {
if (total == 0) { if (total == 0) {
return 100.0F; return 100.0f;
} }
return 100.0F * static_cast<float>(translated) / static_cast<float>(total); return 100.0f * float(translated) / float(total);
} }
void setTranslationStats(const unsigned _translated, const unsigned _untranslated, const unsigned _fuzzy) void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy)
{ {
translated = _translated; translated = _translated;
untranslated = _untranslated; untranslated = _untranslated;
@ -119,18 +115,18 @@ struct Language {
bool isIdenticalTo(const Language& other) const bool isIdenticalTo(const Language& other) const
{ {
return (key == other.key && fileName == other.fileName && fileSize == other.fileSize && fileSha1 == other.fileSha1 && return (key == other.key && file_name == other.file_name && file_size == other.file_size && file_sha1 == other.file_sha1 &&
translated == other.translated && fuzzy == other.fuzzy && total == other.fuzzy && localFileType == other.localFileType); translated == other.translated && fuzzy == other.fuzzy && total == other.fuzzy && localFileType == other.localFileType);
} }
Language& apply(const Language& other) Language& apply(Language& other)
{ {
if (!isOfSameNameAs(other)) { if (!isOfSameNameAs(other)) {
return *this; return *this;
} }
fileName = other.fileName; file_name = other.file_name;
fileSize = other.fileSize; file_size = other.file_size;
fileSha1 = other.fileSha1; file_sha1 = other.file_sha1;
translated = other.translated; translated = other.translated;
fuzzy = other.fuzzy; fuzzy = other.fuzzy;
total = other.total; total = other.total;
@ -142,44 +138,47 @@ struct Language {
QLocale locale; QLocale locale;
bool updated; bool updated;
QString fileName = QString(); QString file_name = QString();
std::size_t fileSize = 0; std::size_t file_size = 0;
QString fileSha1 = QString(); QString file_sha1 = QString();
unsigned translated = 0; unsigned translated = 0;
unsigned untranslated = 0; unsigned untranslated = 0;
unsigned fuzzy = 0; unsigned fuzzy = 0;
unsigned total = 0; unsigned total = 0;
FileType localFileType = FileType::None; FileType localFileType = FileType::NONE;
}; };
struct TranslationsModel::Private { struct TranslationsModel::Private {
QDir m_dir; QDir m_dir;
// initial state is just english // initial state is just english
QList<Language> m_languages = { Language(g_defaultLangCode) }; QList<Language> m_languages = { Language(defaultLangCode) };
QString m_selectedLanguage = g_defaultLangCode; QString m_selectedLanguage = defaultLangCode;
std::unique_ptr<QTranslator> m_qtTranslator; std::unique_ptr<QTranslator> m_qt_translator;
std::unique_ptr<QTranslator> m_appTranslator; std::unique_ptr<QTranslator> m_app_translator;
Net::Download* m_indexTask = nullptr; Net::Download* m_index_task;
QString m_downloadingTranslation; QString m_downloadingTranslation;
NetJob::Ptr m_downloadJob; NetJob::Ptr m_dl_job;
NetJob::Ptr m_indexJob; NetJob::Ptr m_index_job;
QString m_nextDownload; QString m_nextDownload;
QFileSystemWatcher* watcher = nullptr; std::unique_ptr<POTranslator> m_po_translator;
QFileSystemWatcher* watcher;
bool m_noLanguageSet = false; const QString m_system_locale = QLocale::system().name();
const QString m_system_language = m_system_locale.split('_').front();
bool no_language_set = false;
}; };
TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAbstractListModel(parent) TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractListModel(parent)
{ {
d = std::make_unique<Private>(); d.reset(new Private);
d->m_dir.setPath(path); d->m_dir.setPath(path);
d->m_selectedLanguage = APPLICATION->settings()->get("Language").toString();
FS::ensureFolderPathExists(path); FS::ensureFolderPathExists(path);
reloadLocalFiles(); reloadLocalFiles();
@ -188,12 +187,12 @@ TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAb
d->watcher->addPath(d->m_dir.canonicalPath()); d->watcher->addPath(d->m_dir.canonicalPath());
} }
TranslationsModel::~TranslationsModel() = default; TranslationsModel::~TranslationsModel() {}
void TranslationsModel::translationDirChanged(const QString& path) void TranslationsModel::translationDirChanged(const QString& path)
{ {
qDebug() << "Dir changed:" << path; qDebug() << "Dir changed:" << path;
if (!d->m_noLanguageSet) { if (!d->no_language_set) {
reloadLocalFiles(); reloadLocalFiles();
} }
selectLanguage(selectedLanguage()); selectLanguage(selectedLanguage());
@ -202,21 +201,25 @@ void TranslationsModel::translationDirChanged(const QString& path)
void TranslationsModel::indexReceived() void TranslationsModel::indexReceived()
{ {
qDebug() << "Got translations index!"; qDebug() << "Got translations index!";
d->m_indexJob.reset(); d->m_index_job.reset();
reloadLocalFiles();
if (d->m_noLanguageSet) { if (d->no_language_set) {
auto language = getSystemLocaleName(); reloadLocalFiles();
auto language = d->m_system_locale;
if (!findLanguageAsOptional(language).has_value()) { if (!findLanguageAsOptional(language).has_value()) {
language = getSystemLanguage(); language = d->m_system_language;
} }
selectLanguage(language); selectLanguage(language);
if (selectedLanguage() != defaultLangCode) {
updateLanguage(selectedLanguage());
}
APPLICATION->settings()->set("Language", selectedLanguage()); APPLICATION->settings()->set("Language", selectedLanguage());
d->m_noLanguageSet = false; d->no_language_set = false;
} }
if (selectedLanguage() != g_defaultLangCode) { else if (d->m_selectedLanguage != defaultLangCode) {
updateLanguage(selectedLanguage()); downloadTranslation(d->m_selectedLanguage);
} }
} }
@ -232,27 +235,27 @@ void readIndex(const QString& path, QMap<QString, Language>& languages)
} }
try { try {
auto toplevelDoc = Json::requireDocument(data); auto toplevel_doc = Json::requireDocument(data);
auto doc = Json::requireObject(toplevelDoc); auto doc = Json::requireObject(toplevel_doc);
auto fileType = Json::requireString(doc, "file_type"); auto file_type = Json::requireString(doc, "file_type");
if (fileType != "MMC-TRANSLATION-INDEX") { if (file_type != "MMC-TRANSLATION-INDEX") {
qCritical() << "Translations Download Failed: index file is of unknown file type" << fileType; qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type;
return; return;
} }
auto version = Json::requireInteger(doc, "version"); auto version = Json::requireInteger(doc, "version");
if (version > 2) { if (version > 2) {
qCritical() << "Translations Download Failed: index file is of unknown format version" << fileType; qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type;
return; return;
} }
auto langObjs = Json::requireObject(doc, "languages"); auto langObjs = Json::requireObject(doc, "languages");
for (auto iter = langObjs.begin(); iter != langObjs.end(); ++iter) { for (auto iter = langObjs.begin(); iter != langObjs.end(); iter++) {
Language lang(iter.key()); Language lang(iter.key());
auto langObj = Json::requireObject(iter.value()); auto langObj = Json::requireObject(iter.value());
lang.setTranslationStats(langObj["translated"].toInt(), langObj["untranslated"].toInt(), langObj["fuzzy"].toInt()); lang.setTranslationStats(langObj["translated"].toInt(), langObj["untranslated"].toInt(), langObj["fuzzy"].toInt());
lang.fileName = Json::requireString(langObj, "file"); lang.file_name = Json::requireString(langObj, "file");
lang.fileSha1 = Json::requireString(langObj, "sha1"); lang.file_sha1 = Json::requireString(langObj, "sha1");
lang.fileSize = Json::requireInteger(langObj, "size"); lang.file_size = Json::requireInteger(langObj, "size");
languages.insert(lang.key, lang); languages.insert(lang.key, lang);
} }
@ -264,25 +267,20 @@ void readIndex(const QString& path, QMap<QString, Language>& languages)
void TranslationsModel::reloadLocalFiles() void TranslationsModel::reloadLocalFiles()
{ {
QMap<QString, Language> languages = { { g_defaultLangCode, Language(g_defaultLangCode) } }; QMap<QString, Language> languages = { { defaultLangCode, Language(defaultLangCode) } };
const auto indexPath = d->m_dir.absoluteFilePath("index_v2.json"); readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages);
if (!QFileInfo::exists(indexPath)) {
downloadIndex();
return;
}
readIndex(indexPath, languages);
auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot); auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot);
for (auto& entry : entries) { for (auto& entry : entries) {
auto completeSuffix = entry.completeSuffix(); auto completeSuffix = entry.completeSuffix();
QString langCode; QString langCode;
FileType fileType = FileType::None; FileType fileType = FileType::NONE;
if (completeSuffix == "qm") { if (completeSuffix == "qm") {
langCode = entry.baseName().remove(0, 4); langCode = entry.baseName().remove(0, 4);
fileType = FileType::Qm; fileType = FileType::QM;
} else if (completeSuffix == "po") { } else if (completeSuffix == "po") {
langCode = entry.baseName(); langCode = entry.baseName();
fileType = FileType::Po; fileType = FileType::PO;
} else { } else {
continue; continue;
} }
@ -290,14 +288,13 @@ void TranslationsModel::reloadLocalFiles()
auto langIter = languages.find(langCode); auto langIter = languages.find(langCode);
if (langIter != languages.end()) { if (langIter != languages.end()) {
auto& language = *langIter; auto& language = *langIter;
// TODO: use std::to_underlying in C++23 if (int(fileType) > int(language.localFileType)) {
if (static_cast<int>(fileType) > static_cast<int>(language.localFileType)) {
language.localFileType = fileType; language.localFileType = fileType;
} }
} else { } else {
if (fileType == FileType::Po) { if (fileType == FileType::PO) {
Language localFound(langCode); Language localFound(langCode);
localFound.localFileType = FileType::Po; localFound.localFileType = FileType::PO;
languages.insert(langCode, localFound); languages.insert(langCode, localFound);
} }
} }
@ -317,7 +314,7 @@ void TranslationsModel::reloadLocalFiles()
emit dataChanged(index(row), index(row)); emit dataChanged(index(row), index(row));
languages.remove(language.key); languages.remove(language.key);
} }
++iter; iter++;
} else { } else {
beginRemoveRows(QModelIndex(), row, row); beginRemoveRows(QModelIndex(), row, row);
iter = d->m_languages.erase(iter); iter = d->m_languages.erase(iter);
@ -332,38 +329,34 @@ void TranslationsModel::reloadLocalFiles()
for (auto& language : languages) { for (auto& language : languages) {
d->m_languages.append(language); d->m_languages.append(language);
} }
std::sort(d->m_languages.begin(), d->m_languages.end(), [this](const Language& a, const Language& b) {
const auto comp = [systemLocale = getSystemLocaleName(), systemLanguage = getSystemLanguage()](const Language& a, const Language& b) {
if (a.key != b.key) { if (a.key != b.key) {
if (a.key == systemLocale || a.key == systemLanguage) { if (a.key == d->m_system_locale || a.key == d->m_system_language) {
return true; return true;
} }
if (b.key == systemLocale || b.key == systemLanguage) { if (b.key == d->m_system_locale || b.key == d->m_system_language) {
return false; return false;
} }
} }
return a.languageName().toLower() < b.languageName().toLower(); return a.languageName().toLower() < b.languageName().toLower();
}; });
std::ranges::sort(d->m_languages, comp);
endInsertRows(); endInsertRows();
} }
namespace { namespace {
enum class Column : std::uint8_t { Language, Completeness }; enum class Column { Language, Completeness };
} }
QVariant TranslationsModel::data(const QModelIndex& index, const int role) const QVariant TranslationsModel::data(const QModelIndex& index, int role) const
{ {
if (!index.isValid()) { if (!index.isValid())
return {}; return QVariant();
}
const int row = index.row(); int row = index.row();
const auto column = static_cast<Column>(index.column()); auto column = static_cast<Column>(index.column());
if (row < 0 || row >= d->m_languages.size()) { if (row < 0 || row >= d->m_languages.size())
return {}; return QVariant();
}
auto& lang = d->m_languages[row]; auto& lang = d->m_languages[row];
switch (role) { switch (role) {
@ -385,11 +378,11 @@ QVariant TranslationsModel::data(const QModelIndex& index, const int role) const
case Qt::UserRole: case Qt::UserRole:
return lang.key; return lang.key;
default: default:
return {}; return QVariant();
} }
} }
QVariant TranslationsModel::headerData(int section, const Qt::Orientation orientation, const int role) const QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
auto column = static_cast<Column>(section); auto column = static_cast<Column>(section);
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
@ -424,50 +417,49 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c
return 2; return 2;
} }
QList<Language>::Iterator TranslationsModel::findLanguage(const QString& key) const QList<Language>::Iterator TranslationsModel::findLanguage(const QString& key)
{ {
return std::ranges::find_if(d->m_languages, [key](const Language& lang) { return lang.key == key; }); return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; });
} }
std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key) const std::optional<Language> TranslationsModel::findLanguageAsOptional(const QString& key)
{ {
auto found = findLanguage(key); auto found = findLanguage(key);
if (found != d->m_languages.end()) { if (found != d->m_languages.end())
return *found; return *found;
}
return {}; return {};
} }
void TranslationsModel::setUseSystemLocale(const bool useSystemLocale) const void TranslationsModel::setUseSystemLocale(bool useSystemLocale)
{ {
APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); APPLICATION->settings()->set("UseSystemLocale", useSystemLocale);
QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(selectedLanguage())); QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode));
} }
bool TranslationsModel::selectLanguage(QString key) const bool TranslationsModel::selectLanguage(QString key)
{ {
QString& langCode = key; QString& langCode = key;
auto langPtr = findLanguageAsOptional(key); auto langPtr = findLanguageAsOptional(key);
if (langCode.isEmpty()) { if (langCode.isEmpty()) {
d->m_noLanguageSet = true; d->no_language_set = true;
} }
if (!langPtr.has_value()) { if (!langPtr.has_value()) {
qWarning() << "Selected invalid language" << key << ", defaulting to" << g_defaultLangCode; qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode;
langCode = g_defaultLangCode; langCode = defaultLangCode;
} else { } else {
langCode = langPtr->key; langCode = langPtr->key;
} }
// uninstall existing translators if there are any // uninstall existing translators if there are any
if (d->m_appTranslator) { if (d->m_app_translator) {
QCoreApplication::removeTranslator(d->m_appTranslator.get()); QCoreApplication::removeTranslator(d->m_app_translator.get());
d->m_appTranslator.reset(); d->m_app_translator.reset();
} }
if (d->m_qtTranslator) { if (d->m_qt_translator) {
QCoreApplication::removeTranslator(d->m_qtTranslator.get()); QCoreApplication::removeTranslator(d->m_qt_translator.get());
d->m_qtTranslator.reset(); d->m_qt_translator.reset();
} }
/* /*
@ -475,11 +467,11 @@ bool TranslationsModel::selectLanguage(QString key) const
* In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created. * In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created.
* This function is not reentrant. * This function is not reentrant.
*/ */
const bool useSystemLocale = APPLICATION->settings()->get("UseSystemLocale").toBool(); QLocale::setDefault(
QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(langCode)); QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode));
// if it's the default UI language, finish // if it's the default UI language, finish
if (langCode == g_defaultLangCode) { if (langCode == defaultLangCode) {
d->m_selectedLanguage = langCode; d->m_selectedLanguage = langCode;
return true; return true;
} }
@ -487,88 +479,89 @@ bool TranslationsModel::selectLanguage(QString key) const
// otherwise install new translations // otherwise install new translations
bool successful = false; bool successful = false;
// FIXME: this is likely never present. FIX IT. // FIXME: this is likely never present. FIX IT.
d->m_qtTranslator = std::make_unique<QTranslator>(); d->m_qt_translator.reset(new QTranslator());
if (d->m_qtTranslator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "...";
if (!QCoreApplication::installTranslator(d->m_qtTranslator.get())) { if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) {
qCritical() << "Loading Qt Language File failed."; qCritical() << "Loading Qt Language File failed.";
d->m_qtTranslator.reset(); d->m_qt_translator.reset();
} else { } else {
successful = true; successful = true;
} }
} else { } else {
d->m_qtTranslator.reset(); d->m_qt_translator.reset();
} }
if (langPtr->localFileType == FileType::Po) { if (langPtr->localFileType == FileType::PO) {
qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
d->m_appTranslator = std::make_unique<POTranslator>(FS::PathCombine(d->m_dir.path(), langCode + ".po")); auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po"));
if (!d->m_appTranslator->isEmpty()) { if (!poTranslator->isEmpty()) {
if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { if (!QCoreApplication::installTranslator(poTranslator)) {
delete poTranslator;
qCritical() << "Installing Application Language File failed."; qCritical() << "Installing Application Language File failed.";
d->m_appTranslator.reset();
} else { } else {
d->m_app_translator.reset(poTranslator);
successful = true; successful = true;
} }
} else { } else {
qCritical() << "Loading Application Language File failed."; qCritical() << "Loading Application Language File failed.";
d->m_appTranslator.reset(); d->m_app_translator.reset();
} }
} else if (langPtr->localFileType == FileType::Qm) { } else if (langPtr->localFileType == FileType::QM) {
d->m_appTranslator = std::make_unique<QTranslator>(); d->m_app_translator.reset(new QTranslator());
if (d->m_appTranslator->load("mmc_" + langCode, d->m_dir.path())) { if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) {
qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { if (!QCoreApplication::installTranslator(d->m_app_translator.get())) {
qCritical() << "Installing Application Language File failed."; qCritical() << "Installing Application Language File failed.";
d->m_appTranslator.reset(); d->m_app_translator.reset();
} else { } else {
successful = true; successful = true;
} }
} else { } else {
d->m_appTranslator.reset(); d->m_app_translator.reset();
} }
} else { } else {
d->m_appTranslator.reset(); d->m_app_translator.reset();
} }
d->m_selectedLanguage = langCode; d->m_selectedLanguage = langCode;
return successful; return successful;
} }
QModelIndex TranslationsModel::selectedIndex() const QModelIndex TranslationsModel::selectedIndex()
{ {
auto found = findLanguage(d->m_selectedLanguage); auto found = findLanguage(d->m_selectedLanguage);
if (found != d->m_languages.end()) { if (found != d->m_languages.end()) {
return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex()); return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex());
} }
return {}; return QModelIndex();
} }
QString TranslationsModel::selectedLanguage() const QString TranslationsModel::selectedLanguage()
{ {
return d->m_selectedLanguage; return d->m_selectedLanguage;
} }
void TranslationsModel::downloadIndex() void TranslationsModel::downloadIndex()
{ {
if (d->m_indexJob || d->m_downloadJob) { if (d->m_index_job || d->m_dl_job) {
return; return;
} }
qDebug() << "Downloading Translations Index..."; qDebug() << "Downloading Translations Index...";
d->m_indexJob.reset(new NetJob("Translations Index", APPLICATION->network())); d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network()));
const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json");
entry->setStale(true); entry->setStale(true);
auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + "index_v2.json"), entry); auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + "index_v2.json"), entry);
d->m_indexTask = task.get(); d->m_index_task = task.get();
d->m_indexJob->addNetAction(task); d->m_index_job->addNetAction(task);
d->m_indexJob->setAskRetry(false); d->m_index_job->setAskRetry(false);
connect(d->m_indexJob.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed);
connect(d->m_indexJob.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived);
d->m_indexJob->start(); d->m_index_job->start();
} }
void TranslationsModel::updateLanguage(const QString& key) void TranslationsModel::updateLanguage(QString key)
{ {
if (key == g_defaultLangCode) { if (key == defaultLangCode) {
qWarning() << "Cannot update builtin language" << key; qWarning() << "Cannot update builtin language" << key;
return; return;
} }
@ -582,9 +575,9 @@ void TranslationsModel::updateLanguage(const QString& key)
} }
} }
void TranslationsModel::downloadTranslation(const QString& key) void TranslationsModel::downloadTranslation(QString key)
{ {
if (d->m_downloadJob) { if (d->m_dl_job) {
d->m_nextDownload = key; d->m_nextDownload = key;
return; return;
} }
@ -595,21 +588,21 @@ void TranslationsModel::downloadTranslation(const QString& key)
} }
d->m_downloadingTranslation = key; d->m_downloadingTranslation = key;
const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm");
entry->setStale(true); entry->setStale(true);
auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->fileName), entry); auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->file_name), entry);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->fileSha1)); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1));
dl->setProgress(dl->getProgress(), lang->fileSize); dl->setProgress(dl->getProgress(), lang->file_size);
d->m_downloadJob.reset(new NetJob("Translation for " + key, APPLICATION->network())); d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network()));
d->m_downloadJob->addNetAction(dl); d->m_dl_job->addNetAction(dl);
d->m_downloadJob->setAskRetry(false); d->m_dl_job->setAskRetry(false);
connect(d->m_downloadJob.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood);
connect(d->m_downloadJob.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed);
d->m_downloadJob->start(); d->m_dl_job->start();
} }
void TranslationsModel::downloadNext() void TranslationsModel::downloadNext()
@ -620,10 +613,10 @@ void TranslationsModel::downloadNext()
} }
} }
void TranslationsModel::dlFailed(const QString& reason) void TranslationsModel::dlFailed(QString reason)
{ {
qCritical() << "Translations Download Failed:" << reason; qCritical() << "Translations Download Failed:" << reason;
d->m_downloadJob.reset(); d->m_dl_job.reset();
downloadNext(); downloadNext();
} }
@ -634,12 +627,12 @@ void TranslationsModel::dlGood()
if (d->m_downloadingTranslation == d->m_selectedLanguage) { if (d->m_downloadingTranslation == d->m_selectedLanguage) {
selectLanguage(d->m_selectedLanguage); selectLanguage(d->m_selectedLanguage);
} }
d->m_downloadJob.reset(); d->m_dl_job.reset();
downloadNext(); downloadNext();
} }
void TranslationsModel::indexFailed(const QString& reason) const void TranslationsModel::indexFailed(QString reason)
{ {
qCritical() << "Translations Index Download Failed:" << reason; qCritical() << "Translations Index Download Failed:" << reason;
d->m_indexJob.reset(); d->m_index_job.reset();
} }

Some files were not shown because too many files have changed in this diff Show more