diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4ea328301..3574bf20d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,14 +23,14 @@ body: - macOS - Linux - Other -- type: textarea +- type: input attributes: label: Version of Prism Launcher description: The version of Prism Launcher used in the bug report. placeholder: Prism Launcher 5.0 validations: required: true -- type: textarea +- type: input attributes: 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. diff --git a/.github/actions/package/windows/action.yml b/.github/actions/package/windows/action.yml index cd0eb7d91..532f3db44 100644 --- a/.github/actions/package/windows/action.yml +++ b/.github/actions/package/windows/action.yml @@ -69,7 +69,7 @@ runs: - name: Sign executables if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@v2 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher @@ -142,7 +142,7 @@ runs: - name: Sign installer if: ${{ env.CI_HAS_ACCESS_TO_AZURE != '' && inputs.azure-client-id != '' }} - uses: azure/artifact-signing-action@v1 + uses: azure/artifact-signing-action@v2 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: PrismLauncher diff --git a/.github/actions/setup-dependencies/action.yml b/.github/actions/setup-dependencies/action.yml index 7d403ed0a..b73c7509a 100644 --- a/.github/actions/setup-dependencies/action.yml +++ b/.github/actions/setup-dependencies/action.yml @@ -55,7 +55,7 @@ runs: # TODO(@getchoo): Get this working on MSYS2! - name: Setup ccache if: ${{ (runner.os != 'Windows' || inputs.msystem == '') && inputs.build-type == 'Debug' }} - uses: hendrikmuhs/ccache-action@v1.2.22 + uses: hendrikmuhs/ccache-action@v1.2.23 with: variant: sccache create-symlink: ${{ runner.os != 'Windows' }} diff --git a/.github/actions/setup-dependencies/linux/action.yml b/.github/actions/setup-dependencies/linux/action.yml index fe7ee2142..fa5af702b 100644 --- a/.github/actions/setup-dependencies/linux/action.yml +++ b/.github/actions/setup-dependencies/linux/action.yml @@ -13,7 +13,7 @@ runs: dpkg-dev \ ninja-build extra-cmake-modules pkg-config scdoc \ cmark gamemode-dev libarchive-dev libcmark-dev libqrencode-dev zlib1g-dev \ - libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev + libxcb-cursor-dev libtomlplusplus-dev - name: Setup AppImage tooling shell: bash diff --git a/.github/actions/setup-dependencies/windows/action.yml b/.github/actions/setup-dependencies/windows/action.yml index d2d0820d8..24ad51d8f 100644 --- a/.github/actions/setup-dependencies/windows/action.yml +++ b/.github/actions/setup-dependencies/windows/action.yml @@ -91,7 +91,7 @@ runs: - name: Retrieve ccache cache (MinGW) if: ${{ inputs.msystem != '' && inputs.build-type == 'Debug' }} - uses: actions/cache@v5.0.4 + uses: actions/cache@v5.0.5 with: path: '${{ github.workspace }}\.ccache' key: ${{ runner.os }}-mingw-w64-ccache-${{ github.run_id }} diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 02fba2f68..9cde3307d 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -24,7 +24,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - name: Create backport PRs - uses: korthout/backport-action@v4.3.0 + uses: korthout/backport-action@v4.5 with: # Config README: https://github.com/korthout/backport-action#backport-action pull_description: |- diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 5251c149a..d72994c50 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -28,19 +28,14 @@ jobs: fetch-depth: 0 # Required for diffing later on submodules: "true" - - name: Setup sccache - uses: hendrikmuhs/ccache-action@v1.2.22 - with: - variant: sccache - - name: Install Nix uses: cachix/install-nix-action@v31 - - name: Run build + - name: Run source generators # TODO(@getchoo): Figure out how to make this work with PCH run: | nix develop --command bash -c ' - cmake -B build -D Launcher_USE_PCH=OFF -D CMAKE_CXX_COMPILER_LAUNCHER=sccache && cmake --build build + cmake -B build -D Launcher_USE_PCH=OFF && cmake --build build --target autogen autorcc ' # TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index a5dcdc48a..7af2c1ccb 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -33,7 +33,7 @@ jobs: - arch: arm64 os: ubuntu-24.04-arm - arch: amd64 - os: ubuntu-24.04-arm + os: ubuntu-24.04 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 0fea44f08..58f4d263a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -103,7 +103,7 @@ jobs: # For PRs - name: Setup Nix Magic Cache if: ${{ github.event_name == 'pull_request' }} - uses: DeterminateSystems/magic-nix-cache-action@v13 + uses: DeterminateSystems/magic-nix-cache-action@v14 with: diagnostic-endpoint: "" use-flakehub: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ecc23effa..e332488c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,7 @@ jobs: - name: Create release id: create_release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ github.ref }} diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 1353166f1..fa3e3b4d3 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: cachix/install-nix-action@96951a368ba55167b55f1c916f7d416bac6505fe # v31 + - uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31 - uses: DeterminateSystems/update-flake-lock@v28 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b0d41741..12afdefcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,10 @@ endif() ##################################### Set CMake options ##################################### set(CMAKE_AUTOMOC 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_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") @@ -179,9 +183,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 version numbers ######## -set(Launcher_VERSION_MAJOR 11) +set(Launcher_VERSION_MAJOR 12) set(Launcher_VERSION_MINOR 0) -set(Launcher_VERSION_PATCH 2) +set(Launcher_VERSION_PATCH 0) 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") diff --git a/Containerfile b/Containerfile index 8ee4330ed..59595fe55 100644 --- a/Containerfile +++ b/Containerfile @@ -28,7 +28,7 @@ RUN apt-get --assume-yes --no-install-recommends install \ # Build system cmake ninja-build extra-cmake-modules pkg-config \ # Dependencies - cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev libvulkan-dev scdoc zlib1g-dev \ + cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev scdoc zlib1g-dev \ # Tooling clang-format clang-tidy git diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index d430622bc..a5851bfba 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -194,8 +194,10 @@ class Config { QString MODRINTH_STAGING_URL = "https://staging-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" }; + QString MODRINTH_DOWNLOAD_HOST = "cdn.modrinth.com"; QString FLAME_BASE_URL = "https://api.curseforge.com/v1"; + QString FLAME_DOWNLOAD_HOST = "edge.forgecdn.net"; QString versionString() const; /** diff --git a/flake.lock b/flake.lock index eb53cb844..640d2bcf1 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1774709303, - "narHash": "sha256-D4ely1FsBcvtj/qSrNhSWpq+CUZKNiKwJIxpxnfy9o4=", - "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685", + "lastModified": 1778443072, + "narHash": "sha256-rNDJzV2JTV5SUTwv1cgKZYMdyoUYU9/YfegSaUf3QfY=", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre971119.8110df5ad7ab/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre995699.da5ad661ba4e/nixexprs.tar.xz" }, "original": { "type": "tarball", diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 115b6489c..ddeb30588 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -578,16 +578,14 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) } { - bool migrated = false; - - if (!migrated) - migrated = handleDataMigration( - dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", - "polymc.cfg"); - if (!migrated) - migrated = handleDataMigration( - dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", - "multimc.cfg"); + auto migrated = handleDataMigration( + dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", + "polymc.cfg"); + if (!migrated) { + handleDataMigration(dataPath, + FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), + "MultiMC", "multimc.cfg"); + } } { @@ -779,6 +777,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) m_settings->registerSetting("ModDependenciesDisabled", false); m_settings->registerSetting("SkipModpackUpdatePrompt", false); m_settings->registerSetting("ShowModIncompat", false); + m_settings->registerSetting("DownloadGameFilesDuringInstanceCreation", true); // Minecraft offline player name m_settings->registerSetting("LastOfflinePlayerName", ""); @@ -871,6 +870,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) resetIfInvalid(m_settings->registerSetting("LegacyFMLLibsURLOverride", "").get()); } + m_settings->registerSetting("MetaRefreshOnLaunch", true); m_settings->registerSetting("CloseAfterLaunch", false); m_settings->registerSetting("QuitAfterGameStop", false); @@ -935,15 +935,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) 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 { auto setting = APPLICATION->settings()->getSetting("IconsDir"); @@ -1022,8 +1013,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) qInfo() << "<> Cache initialized."; } - // now we have network, download translation updates - m_translations->downloadIndex(); + // load translations + { + 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? m_profilers.insert("jprofiler", std::shared_ptr(new JProfilerFactory())); diff --git a/launcher/BaseVersionList.h b/launcher/BaseVersionList.h index 673d13562..ba546e955 100644 --- a/launcher/BaseVersionList.h +++ b/launcher/BaseVersionList.h @@ -63,7 +63,7 @@ class BaseVersionList : public QAbstractListModel { * 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. */ - virtual Task::Ptr getLoadTask() = 0; + virtual Task::Ptr getLoadTask(bool forceReload = false) = 0; //! Checks whether or not the list is loaded. If this returns false, the list should be // loaded. diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c2f5e70cf..7d4430fd2 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -1206,78 +1206,6 @@ if(WIN32) ) 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 resources/backgrounds/backgrounds.qrc resources/multimc/multimc.qrc @@ -1296,12 +1224,6 @@ qt_add_resources(LAUNCHER_RESOURCES "${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 ######## if(WIN32) set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC}) @@ -1321,7 +1243,7 @@ endif() ####### Targets ######## # Add executable -add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) +add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_RESOURCES}) target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION) @@ -1441,7 +1363,7 @@ endif() if(Launcher_BUILD_UPDATER) # Updater - add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI}) + add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES}) target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if(${Launcher_USE_PCH}) @@ -1569,8 +1491,10 @@ if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter else() - target_compile_options(Launcher_logic PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers) - target_compile_options(${Launcher_Name} PRIVATE -Wno-unused-parameter -Wno-missing-field-initializers) + # sfinae-incomplete is a new GCC warning and triggers in Qt headers + # no-unknown-warning-option so that compilers that don't have sfinae-incomplete don't error + 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() #### The bundle mess! #### diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index f53c9343e..ef56e3e65 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -36,6 +36,7 @@ */ #include "FileSystem.h" +#include #include #include "BuildConfig.h" @@ -683,6 +684,32 @@ bool deletePath(QString path) 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) { // FIXME: Figure out trash in Flatpak. Qt seemingly doesn't use the Trash portal @@ -796,68 +823,33 @@ QString NormalizePath(QString path) } } -static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n"; -static const QString BAD_NTFS_CHARS = "<>:\"|?*"; -static const QString BAD_HFS_CHARS = ":"; - -static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/"; - -QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +namespace { +const QString g_badChars = "<>:\"|?*\r\n!"; +QString removeChars(QString source, QChar replace, const QString& extraChars = "") { - 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 path, QChar 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; + auto badChars = g_badChars; + if (!extraChars.isEmpty()) { + badChars += extraChars; } - if (invalidChars.size() != 0) { - for (int i = 0; i < path.length(); i++) { - if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) { - path[i] = replaceWith; - } + for (auto& c : source) { + if (c.unicode() < 0x20 || !c.isPrint() || badChars.contains(c)) { + c = replace; } } - return path; + return source; +} +} // namespace + +QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) +{ + return removeChars(std::move(string), replaceWith, "\\/"); +} + +QString RemoveInvalidPathChars(QString string, QChar replaceWith) +{ + return removeChars(std::move(string), replaceWith); } QString DirNameFromString(QString string, QString inDir) diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index f2676b147..6d9b01178 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -291,6 +291,13 @@ bool move(const QString& source, const QString& dest); */ 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); /** diff --git a/launcher/HardwareInfo.cpp b/launcher/HardwareInfo.cpp index 5937c33bc..36b6f7783 100644 --- a/launcher/HardwareInfo.cpp +++ b/launcher/HardwareInfo.cpp @@ -18,84 +18,45 @@ #include "HardwareInfo.h" -#include -#include -#include -#include -#include "BuildConfig.h" - -#ifndef Q_OS_MACOS -#include -#include -#endif +#include +#include +#if defined(Q_OS_MACOS) || defined(Q_OS_LINUX) namespace { -bool vulkanInfo(QStringList& out) +QString afterColon(QString str) { - 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; + return str.remove(0, str.indexOf(':') + 2).trimmed(); } -bool openGlInfo(QStringList& out) +template +bool readFromOutput(const char* command, F function) { - if (!QProcessEnvironment::systemEnvironment() - .value(QStringLiteral("%1_DISABLE_GLVULKAN").arg(BuildConfig.LAUNCHER_ENVNAME)) - .isEmpty()) { - return false; - } - QOpenGLContext ctx; - if (!ctx.create()) { - qWarning() << "OpenGL context creation failed"; - out << "Couldn't get OpenGL device information"; + FILE* file = popen(command, "r"); // NOLINT(*-command-processor) + if (!file) { + qWarning().nospace() << "Could not execute command '" << command << "': " << strerror(errno); return false; } - QOffscreenSurface surface; - surface.create(); - ctx.makeCurrent(&surface); + constexpr size_t bufferSize = 512; + std::array buffer{}; + while (fgets(buffer.data(), bufferSize, file) != nullptr) { + function(buffer.data()); + } - auto* f = ctx.functions(); - f->initializeOpenGLFunctions(); + const int exitCode = pclose(file); + if (exitCode != 0) { + if (exitCode == -1) { + qWarning().nospace() << "Could not close stream for command '" << command << "': " << strerror(errno); + } else { + qWarning().nospace() << "Command '" << command << "' exited with code " << exitCode; + } - auto toQString = [](const GLubyte* str) { return QString(reinterpret_cast(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 false; + } return true; } } // namespace - -#ifndef Q_OS_LINUX -QStringList HardwareInfo::gpuInfo() -{ - QStringList info; - vulkanInfo(info); - openGlInfo(info); - return info; -} #endif #ifdef Q_OS_WINDOWS @@ -104,7 +65,11 @@ QStringList HardwareInfo::gpuInfo() #endif #include -#include "windows.h" +#include +#include + +#include +using Microsoft::WRL::ComPtr; QString HardwareInfo::cpuInfo() { @@ -140,16 +105,42 @@ uint64_t HardwareInfo::availableRamMiB() return 0; } +QStringList HardwareInfo::gpuInfo() +{ + ComPtr 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 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) -#include "mach/mach.h" #include "sys/sysctl.h" QString HardwareInfo::cpuInfo() { - std::array buffer; + std::array buffer{}; size_t bufferSize = buffer.size(); if (sysctlbyname("machdep.cpu.brand_string", &buffer, &bufferSize, nullptr, 0) == 0) { - return QString(buffer.data()); + return { buffer.data() }; } qWarning() << "Could not get CPU model: sysctlbyname"; @@ -158,7 +149,7 @@ QString HardwareInfo::cpuInfo() uint64_t HardwareInfo::totalRamMiB() { - uint64_t memsize; + uint64_t memsize = 0; size_t memsizeSize = sizeof memsize; if (sysctlbyname("hw.memsize", &memsize, &memsizeSize, nullptr, 0) == 0) { // transforming bytes -> mib @@ -171,36 +162,62 @@ uint64_t HardwareInfo::totalRamMiB() uint64_t HardwareInfo::availableRamMiB() { - mach_port_t host_port = mach_host_self(); - mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + return 0; +} - vm_statistics64_data_t vm_stats; - - if (host_statistics64(host_port, HOST_VM_INFO64, reinterpret_cast(&vm_stats), &count) == KERN_SUCCESS) { - // transforming bytes -> mib - return (vm_stats.free_count + vm_stats.inactive_count) * vm_page_size / 1024 / 1024; +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(level); } - qWarning() << "Could not get available RAM: host_statistics64"; - return 0; + 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) #include -namespace { -QString afterColon(QString& str) -{ - return str.remove(0, str.indexOf(':') + 2).trimmed(); -} -} // namespace - QString HardwareInfo::cpuInfo() { std::ifstream cpuin("/proc/cpuinfo"); for (std::string line; std::getline(cpuin, line);) { // model name : AMD Ryzen 7 5800X 8-Core Processor - if (QString str = QString::fromStdString(line); str.startsWith("model name")) { + if (const QString str = QString::fromStdString(line); str.startsWith("model name")) { return afterColon(str); } } @@ -209,12 +226,13 @@ QString HardwareInfo::cpuInfo() return "unknown"; } -uint64_t readMemInfo(QString searchTarget) +namespace { +uint64_t readMemInfo(const QString& searchTarget) { std::ifstream memin("/proc/meminfo"); for (std::string line; std::getline(memin, line);) { // MemTotal: 16287480 kB - if (QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { + if (const QString str = QString::fromStdString(line); str.startsWith(searchTarget)) { bool ok = false; const uint total = str.simplified().section(' ', 1, 1).toUInt(&ok); if (!ok) { @@ -230,6 +248,7 @@ uint64_t readMemInfo(QString searchTarget) qWarning() << "Could not read /proc/meminfo: search target not found:" << searchTarget; return 0; } +} // namespace uint64_t HardwareInfo::totalRamMiB() { @@ -243,52 +262,50 @@ uint64_t HardwareInfo::availableRamMiB() QStringList HardwareInfo::gpuInfo() { - QStringList list; - const bool vulkanSuccess = vulkanInfo(list); - const bool openGlSuccess = openGlInfo(list); - if (vulkanSuccess || openGlSuccess) { - return list; - } - - std::array 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()); + QString gpu; + QString driverInUse = "NONE"; + QString driversAvailable = "NONE"; + QStringList out; + + const bool success = readFromOutput("lspci -k", [&](const QString& str) { // 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) // Subsystem: Sapphire Technology Limited Radeon RX 580 Pulse 4GB // Kernel driver in use: amdgpu // Kernel modules: amdgpu // clang-format on - if (str.contains("VGA compatible controller")) { + if (str.contains("VGA compatible controller") || str.contains("3D controller")) { readingGpuInfo = true; } 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; } + if (!readingGpuInfo) { - continue; + return; } + const QString value = afterColon(str); if (str.contains("Subsystem")) { - currentModel = "Found GPU: " + afterColon(str); + gpu = value; } if (str.contains("Kernel driver in use")) { - currentModel += " (using driver " + afterColon(str); + driverInUse = value; } if (str.contains("Kernel modules")) { - currentModel += ", available drivers: " + afterColon(str) + ")"; - list.append(currentModel); + driversAvailable = value; } + }); + if (!success) { + return { "GPU discovery failed: could not read from lspci" }; } - pclose(lspci); - return list; + + return out; } #else @@ -303,19 +320,20 @@ QString HardwareInfo::cpuInfo() uint64_t HardwareInfo::totalRamMiB() { - 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)); + uint64_t out = 0; - // transforming kib -> mib - return mem / 1024; - } + const bool success = readFromOutput("sysctl hw.physmem", [&](const QString& str) { + const uint64_t mem = str.mid(12).toULong(); + + // transforming kib -> mib + out = mem / 1024; + }); + if (!success) { + qWarning() << "Could not get total RAM: could not read from sysctl"; + return 0; } - return 0; + return out; } #else @@ -330,4 +348,8 @@ uint64_t HardwareInfo::availableRamMiB() return 0; } +QStringList HardwareInfo::gpuInfo() +{ + return { "GPU discovery failed: not implemented for this OS" }; +} #endif diff --git a/launcher/HardwareInfo.h b/launcher/HardwareInfo.h index 00e19f214..4efd339b6 100644 --- a/launcher/HardwareInfo.h +++ b/launcher/HardwareInfo.h @@ -27,3 +27,16 @@ uint64_t totalRamMiB(); uint64_t availableRamMiB(); QStringList gpuInfo(); } // 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 \ No newline at end of file diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index ecc9fe591..e58926660 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -3,6 +3,7 @@ #include #include +#include "Application.h" #include "InstanceTask.h" #include "minecraft/MinecraftLoadAndCheck.h" #include "tasks/SequentialTask.h" @@ -18,7 +19,7 @@ bool InstanceCreationTask::abort() return m_gameFilesTask->abort(); } - return true; + return InstanceTask::abort(); } void InstanceCreationTask::executeTask() @@ -38,8 +39,9 @@ void InstanceCreationTask::executeTask() m_instance = createInstance(); if (!m_instance) { - if (m_abort) + if (m_abort) { return; + } qWarning() << "Instance creation failed!"; if (!m_error_message.isEmpty()) { @@ -63,8 +65,9 @@ void InstanceCreationTask::executeTask() qDebug() << "Removing old files"; for (const QString& path : m_filesToRemove) { - if (!QFile::exists(path)) + if (!QFile::exists(path)) { continue; + } qDebug() << "Removing" << path; @@ -81,6 +84,10 @@ void InstanceCreationTask::executeTask() } if (!m_abort) { + if (!APPLICATION->settings()->get("DownloadGameFilesDuringInstanceCreation").toBool()) { + emitSucceeded(); + return; + } setAbortable(true); setAbortButtonText(tr("Skip")); qDebug() << "Downloading game files"; @@ -110,7 +117,7 @@ void InstanceCreationTask::executeTask() } } -void InstanceCreationTask::scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled) +void InstanceCreationTask::scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled) { if (path.isEmpty()) { return; diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 416cf81db..39acaf8b2 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -38,7 +38,7 @@ class InstanceCreationTask : public InstanceTask { protected: void setError(const QString& message) { m_error_message = message; }; - void scheduleToDelete(QWidget* parent, QDir dir, QString path, bool checkDisabled = false); + void scheduleToDelete(QWidget* parent, const QDir& dir, const QString& path, bool checkDisabled = false); protected: bool m_abort = false; diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index d263cc50a..2b882338c 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -40,6 +40,7 @@ #include "minecraft/auth/AccountData.h" #include "minecraft/auth/AccountList.h" +#include "net/NetUtils.h" #include "ui/InstanceWindow.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/MSALoginDialog.h" @@ -109,7 +110,7 @@ void LaunchController::decideAccount() } } - if (!m_accountToUse) { + if (!m_accountToUse && accounts->anyAccountIsValid()) { // 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, m_parentWidget); @@ -133,14 +134,6 @@ LaunchDecision LaunchController::decideLaunchMode() 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(); MinecraftAccountPtr accountToCheck = nullptr; @@ -163,7 +156,9 @@ LaunchDecision LaunchController::decideLaunchMode() } auto state = accountToCheck->accountState(); - if (state == AccountState::Unchecked || state == AccountState::Errored) { + const bool needsRefresh = + m_wantedLaunchMode == LaunchMode::Normal && (state == AccountState::Offline || accountToCheck->shouldRefresh()); + if (state == AccountState::Unchecked || state == AccountState::Errored || needsRefresh) { accountToCheck->refresh(); state = AccountState::Working; } @@ -231,13 +226,14 @@ bool LaunchController::askPlayDemo() const return box.clickedButton() == demoButton; } -QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const +QString LaunchController::askOfflineName(const QString& playerName, bool* ok) { if (ok != nullptr) { *ok = false; } - QString message; + QString title, message; + title = tr("Player name"); switch (m_actualLaunchMode) { case LaunchMode::Normal: Q_ASSERT(false); @@ -247,7 +243,14 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co break; case LaunchMode::Offline: if (m_wantedLaunchMode == LaunchMode::Normal) { - message = tr("You are not connected to the Internet, launching in offline mode\n\n"); + auto netErr = m_accountToUse->accountData()->networkError; + 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"); break; @@ -257,7 +260,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok) co QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName; ChooseOfflineNameDialog dialog(message, m_parentWidget); - dialog.setWindowTitle(tr("Player name")); + dialog.setWindowTitle(title); dialog.setUsername(usedname); if (dialog.exec() != QDialog::Accepted) { return {}; @@ -339,11 +342,11 @@ bool LaunchController::reauthenticateAccount(const MinecraftAccountPtr& account, if (button == QMessageBox::StandardButton::Yes) { auto* accounts = APPLICATION->accounts(); const bool isDefault = accounts->defaultAccount() == account; - accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); if (account->accountType() == AccountType::MSA) { auto newAccount = MSALoginDialog::newAccount(m_parentWidget); if (newAccount != nullptr) { + accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(account->profileId()))); accounts->addAccount(newAccount); if (isDefault) { diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 742f20586..bc0d14e0f 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -78,7 +78,7 @@ class LaunchController : public Task { void decideAccount(); LaunchDecision decideLaunchMode(); bool askPlayDemo() const; - QString askOfflineName(const QString& playerName, bool* ok = nullptr) const; + QString askOfflineName(const QString& playerName, bool* ok = nullptr); bool reauthenticateAccount(const MinecraftAccountPtr& account, const QString& reason); private slots: diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index bae45ad88..c5ef47d7b 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -114,7 +114,7 @@ void LoggedProcess::on_error(QProcess::ProcessError error) { switch (error) { case QProcess::FailedToStart: { - emit log({ tr("The process failed to start.") }, MessageLevel::Fatal); + emit log({ tr("The process failed to start: %1").arg(errorString()) }, MessageLevel::Fatal); changeState(LoggedProcess::FailedToStart); break; } diff --git a/launcher/MTPixmapCache.h b/launcher/MTPixmapCache.h index 0ba9c5ac8..97db598de 100644 --- a/launcher/MTPixmapCache.h +++ b/launcher/MTPixmapCache.h @@ -14,26 +14,26 @@ else \ type = Qt::DirectConnection; -#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \ +#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE, RET_DEF) \ static RET_TYPE NAME() \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \ return ret; \ } -#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \ +#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1) \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1)); \ return ret; \ } -#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \ +#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE, PARAM_2_TYPE) \ static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \ { \ - RET_TYPE ret; \ + RET_TYPE ret = RET_DEF; \ GET_TYPE() \ QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret), Q_ARG(PARAM_1_TYPE, p1), \ Q_ARG(PARAM_2_TYPE, p2)); \ @@ -53,18 +53,18 @@ class PixmapCache final : public QObject { static void setInstance(PixmapCache* i) { s_instance = i; } public: - DEFINE_FUNC_NO_PARAM(cacheLimit, int) - DEFINE_FUNC_NO_PARAM(clear, bool) - DEFINE_FUNC_TWO_PARAM(find, bool, const QString&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(find, bool, const QPixmapCache::Key&, QPixmap*) - DEFINE_FUNC_TWO_PARAM(insert, bool, const QString&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(insert, QPixmapCache::Key, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(remove, bool, const QString&) - DEFINE_FUNC_ONE_PARAM(remove, bool, const QPixmapCache::Key&) - DEFINE_FUNC_TWO_PARAM(replace, bool, const QPixmapCache::Key&, const QPixmap&) - DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, int) - DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool) - DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, int) + DEFINE_FUNC_NO_PARAM(cacheLimit, int, -1) + DEFINE_FUNC_NO_PARAM(clear, bool, false) + DEFINE_FUNC_TWO_PARAM(find, bool, false, const QString&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(find, bool, false, const QPixmapCache::Key&, QPixmap*) + DEFINE_FUNC_TWO_PARAM(insert, bool, false, const QString&, 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, false, const QPixmapCache::Key&) + DEFINE_FUNC_TWO_PARAM(replace, bool, false, const QPixmapCache::Key&, const QPixmap&) + DEFINE_FUNC_ONE_PARAM(setCacheLimit, bool, false, int) + DEFINE_FUNC_NO_PARAM(markCacheMissByEviciton, bool, false) + DEFINE_FUNC_ONE_PARAM(setFastEvictionThreshold, bool, false, int) // NOTE: Every function returns something non-void to simplify the macros. private slots: diff --git a/launcher/ResourceDownloadTask.cpp b/launcher/ResourceDownloadTask.cpp index d50b3d5cf..bb44d2495 100644 --- a/launcher/ResourceDownloadTask.cpp +++ b/launcher/ResourceDownloadTask.cpp @@ -19,23 +19,52 @@ #include "ResourceDownloadTask.h" +#include + #include "Application.h" #include "FileSystem.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h" +#include "modplatform/ModIndex.h" #include "modplatform/helpers/HashUtils.h" #include "net/ApiDownload.h" #include "net/ChecksumValidator.h" +namespace { +Net::ModrinthDownloadMeta createModrinthMeta(BaseInstance* instance, QString reason) +{ + auto* mcInstance = dynamic_cast(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, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed) + bool isIndexed, + QString downloadReason) : m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs) { - if (is_indexed) { + if (isIndexed) { 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); @@ -45,7 +74,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network())); 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()) { switch (Hashing::algorithmFromString(m_pack_version.hash_type)) { case Hashing::Algorithm::Md4: @@ -82,8 +113,9 @@ void ResourceDownloadTask::downloadSucceeded() auto oldName = std::get<0>(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; + } m_pack_model->uninstallResource(oldFilename, true); @@ -95,8 +127,9 @@ void ResourceDownloadTask::downloadSucceeded() if (oldConfig.exists() && !newConfig.exists()) { 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())); + } } } } @@ -104,7 +137,7 @@ void ResourceDownloadTask::downloadSucceeded() void ResourceDownloadTask::downloadFailed(QString reason) { m_filesNetJob.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); } void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total) @@ -114,7 +147,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 // downloaded successfully! -void ResourceDownloadTask::hasOldResource(QString name, QString filename) +void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename) { to_delete = { name, filename }; } diff --git a/launcher/ResourceDownloadTask.h b/launcher/ResourceDownloadTask.h index 7a04c6f1c..84324e99a 100644 --- a/launcher/ResourceDownloadTask.h +++ b/launcher/ResourceDownloadTask.h @@ -33,7 +33,8 @@ class ResourceDownloadTask : public SequentialTask { explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion version, ResourceFolderModel* packs, - bool is_indexed = true); + bool isIndexed = true, + QString downloadReason = "standalone"); const QString& getFilename() const { return m_pack_version.fileName; } const QVariant& getVersionID() const { return m_pack_version.fileId; } const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; } @@ -56,5 +57,5 @@ class ResourceDownloadTask : public SequentialTask { std::tuple to_delete{ "", "" }; private slots: - void hasOldResource(QString name, QString filename); + void hasOldResource(const QString& name, const QString& filename); }; diff --git a/launcher/java/JavaChecker.cpp b/launcher/java/JavaChecker.cpp index 5c52c653d..3a04756fa 100644 --- a/launcher/java/JavaChecker.cpp +++ b/launcher/java/JavaChecker.cpp @@ -179,13 +179,20 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) void JavaChecker::error(QProcess::ProcessError err) { if (err == QProcess::FailedToStart) { - qDebug() << "Java checker has failed to start."; + qDebug() << "Java checker has failed to start:" << process->errorString(); qDebug() << "Process environment:"; qDebug() << process->environment(); qDebug() << "Native environment:"; qDebug() << QProcessEnvironment::systemEnvironment().toStringList(); 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(); } diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp index 254d5f468..1d7b9cdff 100644 --- a/launcher/java/JavaInstallList.cpp +++ b/launcher/java/JavaInstallList.cpp @@ -51,8 +51,9 @@ JavaInstallList::JavaInstallList(QObject* parent, bool onlyManagedVersions) : BaseVersionList(parent), m_only_managed_versions(onlyManagedVersions) {} -Task::Ptr JavaInstallList::getLoadTask() +Task::Ptr JavaInstallList::getLoadTask(bool forceReload) { + Q_UNUSED(forceReload) load(); return getCurrentTask(); } diff --git a/launcher/java/JavaInstallList.h b/launcher/java/JavaInstallList.h index c68c2a3be..58d1ad8ca 100644 --- a/launcher/java/JavaInstallList.h +++ b/launcher/java/JavaInstallList.h @@ -35,7 +35,7 @@ class JavaInstallList : public BaseVersionList { public: explicit JavaInstallList(QObject* parent = 0, bool onlyManagedVersions = false); - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/meta/BaseEntity.cpp b/launcher/meta/BaseEntity.cpp index c809f851a..1869e14d3 100644 --- a/launcher/meta/BaseEntity.cpp +++ b/launcher/meta/BaseEntity.cpp @@ -82,12 +82,12 @@ QUrl BaseEntity::url() const return QUrl(metaOverride).resolved(localFilename()); } -Task::Ptr BaseEntity::loadTask(Net::Mode mode) +Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload) { if (m_task && m_task->isRunning()) { return m_task; } - m_task.reset(new BaseEntityLoadTask(this, mode)); + m_task.reset(new BaseEntityLoadTask(this, mode, forceReload)); return m_task; } @@ -107,7 +107,9 @@ BaseEntity::LoadStatus BaseEntity::status() const return m_load_status; } -BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {} +BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload) + : m_entity(parent), m_mode(mode), m_force_reload(forceReload) +{} void BaseEntityLoadTask::executeTask() { @@ -125,9 +127,11 @@ void BaseEntityLoadTask::executeTask() } // on online the hash needs to match - hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256; + const auto& expected = m_entity->m_sha256; + const auto& actual = m_entity->m_file_sha256; + hashMatches = expected == actual; if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) { - throw Exception("mismatched checksum"); + throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual)); } // load local file @@ -149,13 +153,18 @@ void BaseEntityLoadTask::executeTask() 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 auto wasLoadedRemote = m_entity->m_sha256.isEmpty() ? m_entity->m_load_status == BaseEntity::LoadStatus::Remote : hashMatches; - if (wasLoadedOffline || wasLoadedRemote) { + if (wasLoadedOffline || (wasLoadedRemote && !m_force_reload)) { emitSucceeded(); return; } m_task.reset(new NetJob(QObject::tr("Download of meta file %1").arg(m_entity->localFilename()), APPLICATION->network())); auto url = m_entity->url(); 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); auto dl = Net::ApiDownload::makeCached(url, entry); /* diff --git a/launcher/meta/BaseEntity.h b/launcher/meta/BaseEntity.h index 17aa0cb87..32d8bdbb8 100644 --- a/launcher/meta/BaseEntity.h +++ b/launcher/meta/BaseEntity.h @@ -43,7 +43,7 @@ class BaseEntity { void setSha256(QString sha256); virtual void parse(const QJsonObject& obj) = 0; - [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online); + [[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online, bool forceReload = false); protected: QString m_sha256; // the expected sha256 @@ -58,7 +58,7 @@ class BaseEntityLoadTask : public Task { Q_OBJECT public: - explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode); + explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload); ~BaseEntityLoadTask() override = default; virtual void executeTask() override; @@ -68,6 +68,7 @@ class BaseEntityLoadTask : public Task { private: BaseEntity* m_entity; Net::Mode m_mode; + bool m_force_reload = false; NetJob::Ptr m_task; }; } // namespace Meta diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp index d0c7075cd..bd58215c4 100644 --- a/launcher/meta/Index.cpp +++ b/launcher/meta/Index.cpp @@ -15,6 +15,7 @@ #include "Index.h" +#include "Application.h" #include "JsonFormat.h" #include "QObjectPtr.h" #include "VersionList.h" @@ -135,7 +136,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) { - if (mode == Net::Mode::Offline) { + if (mode == Net::Mode::Offline || !APPLICATION->settings()->get("MetaRefreshOnLaunch").toBool()) { return get(uid, version)->loadTask(mode); } diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index dfca52d87..fa3a271a6 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -32,11 +32,11 @@ VersionList::VersionList(const QString& uid, QObject* parent) : BaseVersionList( setObjectName("Version list: " + uid); } -Task::Ptr VersionList::getLoadTask() +Task::Ptr VersionList::getLoadTask(bool forceReload) { auto loadTask = makeShared(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)); - loadTask->addTask(this->loadTask(Net::Mode::Online)); + loadTask->addTask(APPLICATION->metadataIndex()->loadTask(Net::Mode::Online, forceReload)); + loadTask->addTask(this->loadTask(Net::Mode::Online, forceReload)); return loadTask; } diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 18681b8ed..709c361de 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -37,7 +37,7 @@ class VersionList : public BaseVersionList, public BaseEntity { enum Roles { UidRole = Qt::UserRole + 100, TimeRole, RequiresRole, VersionPtrRole }; bool isLoaded() override; - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; const BaseVersion::Ptr at(int i) const override; int count() const override; void sortVersions() override; diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 026f9c281..2b43d4389 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -149,7 +149,7 @@ QList Library::getDownloads(const RuntimeContext& runtimeC if (sha1.size()) { auto dl = Net::ApiDownload::makeCached(url, entry, options); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, sha1)); - qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url; + qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url << "expected sha1:" << sha1; out.append(dl); } else { out.append(Net::ApiDownload::makeCached(url, entry, options)); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index e8fc642fa..8e98a2efe 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -131,7 +131,8 @@ for (const auto& gpu : gpus) { QString name = qvariant_cast(gpu[QStringLiteral("Name")]); bool defaultGpu = qvariant_cast(gpu[QStringLiteral("Default")]); - if (!defaultGpu) { + bool discrete = qvariant_cast(gpu.value(QStringLiteral("Discrete"), !defaultGpu)); + if (discrete) { QStringList envList = qvariant_cast(gpu[QStringLiteral("Environment")]); for (int i = 0; i + 1 < envList.size(); i += 2) { env.insert(envList[i], envList[i + 1]); @@ -892,6 +893,14 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr 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 << "Main class: " + getMainClass() << emptyLine; diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index 017f85027..e646e2c52 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -7,30 +7,27 @@ #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" -VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version) - : InstanceCreationTask() - , m_version(std::move(version)) - , m_using_loader(true) - , m_loader(std::move(loader)) - , m_loader_version(std::move(loader_version)) +VanillaCreationTask::VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion) + : m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loaderVersion)) {} std::unique_ptr VanillaCreationTask::createInstance() { setStatus(tr("Creating instance from version %1").arg(m_version->name())); - auto inst = std::make_unique(m_globalSettings, std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")), - m_stagingPath); + auto inst = std::make_unique( + m_globalSettings, std::make_unique(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath); SettingsObject::Lock lock(inst->settings()); - auto components = inst->getPackProfile(); + auto* components = inst->getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - if (m_using_loader) + if (m_using_loader) { components->setComponentVersion(m_loader, m_loader_version->descriptor()); + } inst->setName(name()); inst->setIconKey(m_instIcon); - + components->saveNow(); return inst; } diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index 7015a4fe5..c1a69ab62 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -7,8 +7,8 @@ class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - VanillaCreationTask(BaseVersion::Ptr version) : InstanceCreationTask(), m_version(std::move(version)) {} - VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loader_version); + explicit VanillaCreationTask(BaseVersion::Ptr version) : m_version(std::move(version)) {} + VanillaCreationTask(BaseVersion::Ptr version, QString loader, BaseVersion::Ptr loaderVersion); std::unique_ptr createInstance() override; diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 4aa0f7532..ca8ac1aa8 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -157,7 +157,8 @@ bool WorldList::resetIcon(int row) return false; World& m = m_worlds[row]; if (m.resetIcon()) { - emit dataChanged(index(row), index(row), { WorldList::IconFileRole }); + QModelIndex modelIndex = index(row, NameColumn); + emit dataChanged(modelIndex, modelIndex, { WorldList::IconFileRole }); return true; } return false; @@ -426,7 +427,7 @@ void WorldList::loadWorldsAsync() m_worlds[row].setSize(size); // Notify views - QModelIndex modelIndex = index(row); + QModelIndex modelIndex = index(row, SizeColumn); emit dataChanged(modelIndex, modelIndex, { SizeRole }); } }, diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h index 5fbe3213b..96ef94f30 100644 --- a/launcher/minecraft/auth/AccountData.h +++ b/launcher/minecraft/auth/AccountData.h @@ -41,6 +41,7 @@ #include #include +#include #include enum class Validity { None, Assumed, Certain }; @@ -118,5 +119,6 @@ struct AccountData { // runtime only information (not saved with the account) QString internalId; QString errorString; + QNetworkReply::NetworkError networkError = QNetworkReply::NoError; AccountState accountState = AccountState::Unchecked; }; diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index ac27b6bbc..418ba98d2 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -648,9 +648,17 @@ void AccountList::tryNext() while (m_refreshQueue.length()) { auto accountId = m_refreshQueue.front(); m_refreshQueue.pop_front(); + bool found = false; for (int i = 0; i < count(); i++) { auto account = at(i); 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(); if (m_currentTask) { connect(m_currentTask.get(), &Task::succeeded, this, &AccountList::authSucceeded); @@ -660,9 +668,12 @@ void AccountList::tryNext() << accountId; return; } + break; } } - qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; + if (!found) { + qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found."; + } } // if we get here, no account needed refreshing. Schedule refresh in an hour. m_refreshTimer->start(1000 * 3600); diff --git a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp index 89293c22e..4ceed8586 100644 --- a/launcher/minecraft/auth/steps/LauncherLoginStep.cpp +++ b/launcher/minecraft/auth/steps/LauncherLoginStep.cpp @@ -56,10 +56,11 @@ void LauncherLoginStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get Minecraft access token: %1").arg(m_request->errorString())); } return; diff --git a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp index 3feb6852c..78762a32a 100644 --- a/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp +++ b/launcher/minecraft/auth/steps/MSADeviceCodeStep.cpp @@ -113,6 +113,12 @@ DeviceAuthorizationResponse parseDeviceAuthorizationResponse(const QByteArray& d 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); if (!rsp.error.isEmpty() || !rsp.error_description.isEmpty()) { qWarning() << "Device authorization failed:" << rsp.error; @@ -120,12 +126,6 @@ void MSADeviceCodeStep::deviceAuthorizationFinished(QByteArray* response) tr("Device authorization failed: %1").arg(rsp.error_description.isEmpty() ? rsp.error : rsp.error_description)); 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) { qWarning() << "Device authorization failed: required fields missing"; emit finished(AccountTaskState::STATE_FAILED_HARD, tr("Device authorization failed: required fields missing")); diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp index 418c46a0e..b95682b2b 100644 --- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp +++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp @@ -52,10 +52,11 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response) qWarning() << " Response:"; qWarning() << QString::fromUtf8(*response); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Minecraft Java profile acquisition failed: %1").arg(m_request->errorString())); } diff --git a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp index 9e101d42a..422aa1a54 100644 --- a/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp +++ b/launcher/minecraft/auth/steps/XboxAuthorizationStep.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include "Application.h" #include "Logging.h" @@ -12,7 +12,7 @@ #include "net/Upload.h" XboxAuthorizationStep::XboxAuthorizationStep(AccountData* data, Token* token, QString relyingParty, QString authorizationKind) - : AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind) + : AuthStep(data), m_token(token), m_relyingParty(std::move(relyingParty)), m_authorizationKind(std::move(authorizationKind)) {} QString XboxAuthorizationStep::describe() @@ -22,7 +22,7 @@ QString XboxAuthorizationStep::describe() void XboxAuthorizationStep::perform() { - QString xbox_auth_template = R"XXX( + const QString xboxAuthTemplate = R"XXX( { "Properties": { "SandboxId": "RETAIL", @@ -34,15 +34,13 @@ void XboxAuthorizationStep::perform() "TokenType": "JWT" } )XXX"; - auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty); + const auto xboxAuthData = xboxAuthTemplate.arg(m_data->userToken.token, m_relyingParty); // http://xboxlive.com - QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); - auto headers = QList{ - { "Content-Type", "application/json" }, - { "Accept", "application/json" }, - { "x-xbl-contract-version", "1" } - }; - auto [request, response] = Net::Upload::makeByteArray(url, xbox_auth_data.toUtf8()); + const QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize"); + auto headers = QList{ { .headerName = "Content-Type", .headerValue = "application/json" }, + { .headerName = "Accept", .headerValue = "application/json" }, + { .headerName = "x-xbl-contract-version", .headerValue = "1" } }; + auto [request, response] = Net::Upload::makeByteArray(url, xboxAuthData.toUtf8()); m_request = request; m_request->addHeaderProxy(std::make_unique(headers)); m_request->enableAutoRetry(true); @@ -62,15 +60,14 @@ void XboxAuthorizationStep::onRequestDone(QByteArray* response) qCDebug(authCredentials()) << *response; if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { - if (!processSTSError(*response)) { - 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())); + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { + if (processSTSError(*response)) { + return; } + emit finished(AccountTaskState::STATE_FAILED_SOFT, + tr("Unknown STS error for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Failed to get authorization for %1 services: %2").arg(m_authorizationKind, m_request->errorString())); } @@ -99,8 +96,8 @@ bool XboxAuthorizationStep::processSTSError(const QByteArray& response) { if (m_request->error() == QNetworkReply::AuthenticationRequiredError) { QJsonParseError jsonError; - QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); - if (jsonError.error) { + const QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError); + if (jsonError.error != QJsonParseError::NoError) { qWarning() << "Cannot parse error XSTS response as JSON:" << jsonError.errorString(); emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Cannot parse %1 authorization error response as JSON: %2").arg(m_authorizationKind, jsonError.errorString())); diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp index 97544d09b..382750218 100644 --- a/launcher/minecraft/auth/steps/XboxUserStep.cpp +++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp @@ -56,9 +56,10 @@ void XboxUserStep::onRequestDone(QByteArray* response) { if (m_request->error() != QNetworkReply::NoError) { qWarning() << "Reply error:" << m_request->error(); - if (Net::isApplicationError(m_request->error())) { + if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) { emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } else { + m_data->networkError = m_request->error(); emit finished(AccountTaskState::STATE_OFFLINE, tr("Xbox user authentication failed: %1").arg(m_request->errorString())); } return; diff --git a/launcher/minecraft/launch/EnsureAvailableMemory.cpp b/launcher/minecraft/launch/EnsureAvailableMemory.cpp index a0e156770..0f349674a 100644 --- a/launcher/minecraft/launch/EnsureAvailableMemory.cpp +++ b/launcher/minecraft/launch/EnsureAvailableMemory.cpp @@ -25,22 +25,72 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan void EnsureAvailableMemory::executeTask() { - const uint64_t available = HardwareInfo::availableRamMiB(); - const uint64_t min = m_instance->settings()->get("MinMemAlloc").toUInt(); - const uint64_t max = m_instance->settings()->get("MaxMemAlloc").toUInt(); - const uint64_t required = std::max(min, max); +#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; + } - if (static_cast(required) * 0.9 > static_cast(available)) { + 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(); + if (available == 0) { + // could not read + emitSucceeded(); + return; + } + + const uint64_t settingMin = m_instance->settings()->get("MinMemAlloc").toUInt(); + const uint64_t settingMax = m_instance->settings()->get("MaxMemAlloc").toUInt(); + const uint64_t max = std::max(settingMin, settingMax); + + if (static_cast(max) * 0.9 > static_cast(available)) { bool shouldAbort = false; if (m_instance->settings()->get("LowMemWarning").toBool()) { auto* dialog = CustomMessageBox::selectable( - nullptr, tr("Not enough RAM"), - tr("There is not enough RAM available to launch this instance with the current memory settings.\n\n" - "Required: %1 MiB\nAvailable: %2 MiB\n\n" - "Continue anyway? This may cause slowdowns in the game and your system.") - .arg(required) - .arg(available), + nullptr, tr("Low free memory"), + tr("There might not be enough free RAM 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" + "Launch anyway? This may cause slowdowns in the game and your system.") + .arg(max) + .arg(available) + .arg(HardwareInfo::totalRamMiB()), QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::No); @@ -59,4 +109,5 @@ void EnsureAvailableMemory::executeTask() } emitSucceeded(); +#endif } diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index a2c400e75..e4d3ec1ef 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -164,9 +164,9 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) switch (state) { case LoggedProcess::FailedToStart: { //: Error message displayed if instace can't start - const char* reason = QT_TR_NOOP("Could not launch Minecraft!"); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(tr(reason)); + const char* reason = QT_TR_NOOP("Could not launch Minecraft: %1"); + emit logLine(QString(reason).arg(m_process.errorString()), MessageLevel::Fatal); + emitFailed(tr(reason).arg(m_process.errorString())); return; } case LoggedProcess::Aborted: diff --git a/launcher/minecraft/launch/PrintInstanceInfo.cpp b/launcher/minecraft/launch/PrintInstanceInfo.cpp index 7bfe73746..df8c28c7b 100644 --- a/launcher/minecraft/launch/PrintInstanceInfo.cpp +++ b/launcher/minecraft/launch/PrintInstanceInfo.cpp @@ -68,7 +68,12 @@ void PrintInstanceInfo::executeTask() ::runPciconf(log); #else 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()); +#endif + #endif log.append(HardwareInfo::gpuInfo()); log << ""; diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 140596a29..c2b477d45 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -216,7 +216,7 @@ static std::pair map(int format, const QMap(other); - if (type == SortType::PACK_FORMAT) { + if (type == SortType::PackFormat) { auto this_ver = packFormat(); auto other_ver = cast_other.packFormat(); diff --git a/launcher/minecraft/mod/DataPackFolderModel.cpp b/launcher/minecraft/mod/DataPackFolderModel.cpp index f1497b809..350ae60e1 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.cpp +++ b/launcher/minecraft/mod/DataPackFolderModel.cpp @@ -40,25 +40,26 @@ #include #include -#include "Version.h" - #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true }; + m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size", "File Name" }); + m_columnNamesTranslated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, + SortType::Date, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true }; } QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -67,11 +68,13 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case Qt::BackgroundRole: return rowBackground(row); case Qt::DisplayRole: - switch (column) { - case PackFormatColumn: { - const auto& resource = at(row); - return resource.packFormatStr(); - } + if (column == PackFormatColumn) { + const auto& resource = at(row); + return resource.packFormatStr(); + } + if (column == SizeColumn) { + const auto& resource = at(row); + return resource.sizeStr(); } break; case Qt::DecorationRole: { @@ -92,6 +95,8 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -109,7 +114,14 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const case ProviderColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn); break; - // FIXME: there is no size column due to an oversight + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; + case SizeColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); + break; + default: + break; } if (mappedIndex.isValid()) { @@ -129,6 +141,8 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case PackFormatColumn: case DateColumn: case ImageColumn: + case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -145,6 +159,10 @@ 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."); case DateColumn: 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: return {}; } @@ -160,7 +178,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien int DataPackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Resource* DataPackFolderModel::createResource(const QFileInfo& file) @@ -170,5 +188,5 @@ Resource* DataPackFolderModel::createResource(const QFileInfo& file) Task* DataPackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast(&resource)); + return new LocalDataPackParseTask(m_nextResolutionTicket, static_cast(&resource)); } diff --git a/launcher/minecraft/mod/DataPackFolderModel.h b/launcher/minecraft/mod/DataPackFolderModel.h index 2b90e1a2a..24133354a 100644 --- a/launcher/minecraft/mod/DataPackFolderModel.h +++ b/launcher/minecraft/mod/DataPackFolderModel.h @@ -39,16 +39,24 @@ #include "ResourceFolderModel.h" #include "DataPack.h" -#include "ResourcePack.h" class DataPackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + PackFormatColumn, + DateColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; - explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "datapacks"; } + QString id() const override { return "datapacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -56,7 +64,7 @@ class DataPackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override; - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(DataPack) }; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 661192d67..f0cdfaff6 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include "MTPixmapCache.h" #include "MetadataHandler.h" @@ -49,6 +50,34 @@ #include "minecraft/mod/tasks/LocalModParseTask.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() { m_enabled = (file.suffix() != "disabled"); @@ -61,18 +90,18 @@ void Mod::setDetails(const ModDetails& details) int Mod::compare(const Resource& other, SortType type) const { - auto cast_other = dynamic_cast(&other); + auto cast_other = dynamic_cast(&other); if (!cast_other) return Resource::compare(other, type); switch (type) { default: - case SortType::ENABLED: - case SortType::NAME: - case SortType::DATE: - case SortType::SIZE: + case SortType::Enabled: + case SortType::Name: + case SortType::Date: + case SortType::Size: return Resource::compare(other, type); - case SortType::VERSION: { + case SortType::Version: { auto this_ver = Version(version()); auto other_ver = Version(cast_other->version()); if (this_ver > other_ver) @@ -81,38 +110,38 @@ int Mod::compare(const Resource& other, SortType type) const return -1; break; } - case SortType::SIDE: { + case SortType::Side: { auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::MC_VERSIONS: { - auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive); + case SortType::McVersions: { + auto compare_result = compareVersionLists(mcVersions(), cast_other->mcVersions()); if (compare_result != 0) return compare_result; break; } - case SortType::LOADERS: { + case SortType::Loaders: { auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::RELEASE_TYPE: { + case SortType::ReleaseType: { auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive); if (compare_result != 0) return compare_result; break; } - case SortType::REQUIRED_BY: { + case SortType::RequiredBy: { if (requiredByCount() > cast_other->requiredByCount()) return 1; if (requiredByCount() < cast_other->requiredByCount()) return -1; break; } - case SortType::REQUIRES: { + case SortType::Requires: { if (requiresCount() > cast_other->requiresCount()) return 1; if (requiresCount() < cast_other->requiresCount()) @@ -197,14 +226,19 @@ auto Mod::side() const -> QString return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide); } -auto Mod::mcVersions() const -> QString +auto Mod::mcVersions() const -> QStringList { if (metadata()) - return metadata()->mcVersions.join(", "); + return metadata()->mcVersions; return {}; } +auto Mod::mcVersionsString() const -> QString +{ + return mcVersions().join(", "); +} + auto Mod::releaseType() const -> QString { if (metadata()) diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 0d24409bf..27768ae2c 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -68,7 +68,8 @@ class Mod : public Resource { auto issueTracker() const -> QString; auto side() const -> QString; auto loaders() const -> QString; - auto mcVersions() const -> QString; + auto mcVersions() const -> QStringList; + auto mcVersionsString() const -> QString; auto releaseType() const -> QString; QStringList dependencies() const; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 4d54be921..a03410fd3 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,38 +51,39 @@ #include #include #include +#include -#include "minecraft/Component.h" #include "minecraft/mod/Resource.h" #include "minecraft/mod/ResourceFolderModel.h" #include "minecraft/mod/tasks/LocalModParseTask.h" #include "modplatform/ModIndex.h" #include "ui/dialogs/CustomMessageBox.h" -ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", - "Minecraft Versions", "Release Type", "Requires", "Required By" }); - m_column_names_translated = + m_columnNames = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders", + "Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" }); + m_columnNamesTranslated = 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") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, - SortType::PROVIDER, SortType::SIZE, SortType::SIDE, SortType::LOADERS, SortType::MC_VERSIONS, - SortType::RELEASE_TYPE, SortType::REQUIRES, SortType::REQUIRED_BY }; - 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 }; - m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true }; + tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Version, SortType::Date, + SortType::Provider, SortType::Size, SortType::Side, SortType::Loaders, SortType::McVersions, + SortType::ReleaseType, SortType::Requires, SortType::RequiredBy, SortType::Filename }; + m_columnResizeModes = { 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 }; + m_columnsHideable = { false, true, false, true, true, true, true, true, true, true, true, true, true, true }; connect(this, &ModFolderModel::parseFinished, this, &ModFolderModel::onParseFinished); } QVariant ModFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -109,7 +110,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const return at(row).loaders(); } case McVersionsColumn: { - return at(row).mcVersions(); + return at(row).mcVersionsString(); } case ReleaseTypeColumn: { return at(row).releaseType(); @@ -120,6 +121,8 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case RequiresColumn: { return at(row).requiresCount(); } + default: + break; } break; case Qt::DecorationRole: { @@ -155,6 +158,11 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; + default: + break; } if (mappedIndex.isValid()) { @@ -182,6 +190,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio case SizeColumn: case RequiredByColumn: case RequiresColumn: + case FileNameColumn: return columnNames().at(section); default: return QVariant(); @@ -213,6 +222,8 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio return tr("For each mod, the number of other mods which depend on it."); case RequiresColumn: return tr("For each mod, the number of other mods it depends on."); + case FileNameColumn: + return tr("The file name of the mod."); default: return QVariant(); } @@ -223,12 +234,12 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio int ModFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Task* ModFolderModel::createParseTask(Resource& resource) { - return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); + return new LocalModParseTask(m_nextResolutionTicket, resource.type(), resource.fileinfo()); } bool ModFolderModel::isValid() @@ -236,35 +247,37 @@ bool ModFolderModel::isValid() return m_dir.exists() && m_dir.isReadable(); } -void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) +void ModFolderModel::onParseSucceeded(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd()) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd()) { return; + } - int row = m_resources_index[mod_id]; + int row = m_resourcesIndex[resourceId]; - auto parse_task = *iter; - auto cast_task = static_cast(parse_task.get()); + const auto& parseTask = *iter; + auto* castTask = static_cast(parseTask.get()); - Q_ASSERT(cast_task->token() == ticket); + Q_ASSERT(castTask->token() == ticket); - auto resource = find(mod_id); + auto resource = find(resourceId); - auto result = cast_task->result(); + auto result = castTask->result(); if (result && resource) { auto* mod = static_cast(resource.get()); mod->finishResolvingWithDetails(std::move(result->details)); - } emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); } -Mod* findById(QSet mods, QString modId) +namespace { +Mod* findById(QSet mods, const QString& resourceId) { - auto found = std::find_if(mods.begin(), mods.end(), [modId](Mod* m) { return m->mod_id() == modId; }); + auto found = std::ranges::find_if(mods, [resourceId](Mod* m) { return m->mod_id() == resourceId; }); return found != mods.end() ? *found : nullptr; } +} // namespace void ModFolderModel::onParseFinished() { @@ -277,25 +290,25 @@ void ModFolderModel::onParseFinished() m_requires.clear(); m_requiredBy.clear(); - auto findByProjectID = [mods](QVariant modId, ModPlatform::ResourceProvider provider) -> Mod* { - auto found = std::find_if(mods.begin(), mods.end(), [modId, provider](Mod* m) { + auto findByProjectID = [mods](const QVariant& modId, ModPlatform::ResourceProvider provider) -> Mod* { + auto found = std::ranges::find_if(mods, [modId, provider](Mod* m) { return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId; }); return found != mods.end() ? *found : nullptr; }; - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); - for (auto dep : mod->dependencies()) { - auto d = findById(mods, dep); + for (const auto& dep : mod->dependencies()) { + auto* d = findById(mods, dep); if (d) { m_requires[id] << d; m_requiredBy[d->mod_id()] << mod; } } if (mod->metadata()) { - for (auto dep : mod->metadata()->dependencies) { + for (const auto& dep : mod->metadata()->dependencies) { if (dep.type == ModPlatform::DependencyType::REQUIRED) { - auto d = findByProjectID(dep.addonId, mod->metadata()->provider); + auto* d = findByProjectID(dep.addonId, mod->metadata()->provider); if (d) { m_requires[id] << d; m_requiredBy[d->mod_id()] << mod; @@ -304,30 +317,31 @@ void ModFolderModel::onParseFinished() } } } - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiresCount(m_requires[id].count()); - int row = m_resources_index[mod->internal_id()]; + int row = m_resourcesIndex[mod->internalId()]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } } } -QSet collectMods(QSet mods, QHash> relation, std::set& seen, bool shouldBeEnabled) +namespace { + +QSet collectMods(const QSet& mods, QHash> relation, std::set& seen, bool shouldBeEnabled) { QSet affectedList = {}; QSet needToCheck = {}; - for (auto mod : mods) { + for (auto* mod : mods) { auto id = mod->mod_id(); - if (seen.count(id) == 0) { + if (!seen.contains(id)) { seen.insert(id); - for (auto affected : relation[id]) { + for (auto* affected : relation[id]) { auto affectedId = affected->mod_id(); - if (findById(mods, affectedId) == nullptr && seen.count(affectedId) == 0) { - seen.insert(affectedId); + if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) { if (shouldBeEnabled != affected->enabled()) { affectedList << affected; } @@ -342,11 +356,13 @@ QSet collectMods(QSet mods, QHash> relation, std } return affectedList; } +} // namespace QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return {}; + } QModelIndexList affectedList = {}; auto affectedModsList = selectedMods(indexes); @@ -366,9 +382,9 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, return {}; // this function should not be called with TOGGLE } } - for (auto affected : affectedMods) { + for (auto* affected : affectedMods) { auto affectedId = affected->mod_id(); - auto row = m_resources_index[affected->internal_id()]; + auto row = m_resourcesIndex[affected->internalId()]; affectedList << index(row, 0); } return affectedList; @@ -376,8 +392,9 @@ QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return {}; + } auto indexedModsList = selectedMods(indexes); auto indexedMods = QSet(indexedModsList.begin(), indexedModsList.end()); @@ -396,7 +413,7 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc break; } case EnableAction::TOGGLE: { - for (auto mod : indexedMods) { + for (auto* mod : indexedMods) { if (mod->enabled()) { toDisable << mod; } else { @@ -411,10 +428,10 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc auto requiredToDisable = collectMods(toDisable, m_requiredBy, seen, false); toDisable.removeIf([toEnable](Mod* m) { return toEnable.contains(m); }); - auto toList = [this](QSet mods) { + auto toList = [this](const QSet& mods) { QModelIndexList list; - for (auto mod : mods) { - auto row = m_resources_index[mod->internal_id()]; + for (auto* mod : mods) { + auto row = m_resourcesIndex[mod->internalId()]; list << index(row, 0); } return list; @@ -447,8 +464,8 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc yesButton = tr("Disable Required"); } - auto box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); + auto* box = CustomMessageBox::selectable(nullptr, title, message, QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::No); box->button(QMessageBox::No)->setText(noButton); box->button(QMessageBox::Yes)->setText(yesButton); auto response = box->exec(); @@ -466,21 +483,23 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc return disableStatus && enableStatus; } -QStringList reqToList(QSet l) +namespace { +QStringList reqToList(const QSet& l) { QStringList req; - for (auto m : l) { + for (auto* m : l) { req << m->name(); } return req; } +} // namespace -QStringList ModFolderModel::requiresList(QString id) +QStringList ModFolderModel::requiresList(const QString& id) { return reqToList(m_requires[id]); } -QStringList ModFolderModel::requiredByList(QString id) +QStringList ModFolderModel::requiredByList(const QString& id) { return reqToList(m_requiredBy[id]); } @@ -489,7 +508,7 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes) { auto deleteInvalid = [](QSet& mods) { for (auto it = mods.begin(); it != mods.end();) { - auto mod = *it; + auto* mod = *it; // the QFileInfo::exists is used instead of mod->fileinfo().exists // because the later somehow caches that the file exists if (!mod || !QFileInfo::exists(mod->fileinfo().absoluteFilePath())) { @@ -500,14 +519,14 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes) } }; auto rsp = ResourceFolderModel::deleteResources(indexes); - for (auto mod : allMods()) { + for (auto* mod : allMods()) { auto id = mod->mod_id(); deleteInvalid(m_requiredBy[id]); deleteInvalid(m_requires[id]); if (mod->requiredByCount() != m_requiredBy[id].count() || mod->requiresCount() != m_requires[id].count()) { mod->setRequiredByCount(m_requiredBy[id].count()); mod->setRequiresCount(m_requires[id].count()); - int row = m_resources_index[mod->internal_id()]; + int row = m_resourcesIndex[mod->internalId()]; emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn)); } } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 4de875abc..a5c6ba483 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -46,7 +46,6 @@ #include "Mod.h" #include "ResourceFolderModel.h" -#include "minecraft/Component.h" #include "minecraft/mod/Resource.h" class BaseInstance; @@ -59,7 +58,7 @@ class QFileSystemWatcher; class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { + enum Columns : std::uint8_t { ActiveColumn = 0, ImageColumn, NameColumn, @@ -73,11 +72,12 @@ class ModFolderModel : public ResourceFolderModel { ReleaseTypeColumn, RequiresColumn, RequiredByColumn, - NUM_COLUMNS + FileNameColumn, + NumColumns }; - ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + ModFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "mods"; } + QString id() const override { return "mods"; } 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; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; bool isValid(); @@ -97,11 +97,11 @@ class ModFolderModel : public ResourceFolderModel { RESOURCE_HELPERS(Mod) public: - QStringList requiresList(QString id); - QStringList requiredByList(QString id); + QStringList requiresList(const QString& id); + QStringList requiredByList(const QString& id); private slots: - void onParseSucceeded(int ticket, QString resource_id) override; + void onParseSucceeded(int ticket, const QString& resourceId) override; void onParseFinished(); private: diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 692622521..06d9a0af7 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -4,71 +4,75 @@ #include #include #include +#include #include "FileSystem.h" #include "StringUtils.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -Resource::Resource(QObject* parent) : QObject(parent) {} +Resource::Resource(QObject* parent) : QObject(parent), m_size_info(0) {} -Resource::Resource(QFileInfo file_info) : QObject() +Resource::Resource(QFileInfo fileInfo) : m_size_info(0) { - setFile(file_info); + setFile(fileInfo); } -void Resource::setFile(QFileInfo file_info) +void Resource::setFile(QFileInfo fileInfo) { - m_file_info = file_info; + m_file_info = std::move(fileInfo); parseFile(); } -static std::tuple calculateFileSize(const QFileInfo& file) +namespace { +std::tuple calculateFileSize(const QFileInfo& file) { if (file.isDir()) { auto dir = QDir(file.absoluteFilePath()); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); auto count = dir.count(); auto str = QObject::tr("item"); - if (count != 1) + if (count != 1) { str = QObject::tr("items"); + } return { QString("%1 %2").arg(QString::number(count), str), count }; } return { StringUtils::humanReadableFileSize(file.size(), true), file.size() }; } +} // namespace void Resource::parseFile() { - QString file_name{ m_file_info.fileName() }; + QString fileName{ m_file_info.fileName() }; m_type = ResourceType::UNKNOWN; - m_internal_id = file_name; + m_internal_id = fileName; std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info); if (m_file_info.isDir()) { m_type = ResourceType::FOLDER; - m_name = file_name; + m_name = fileName; } else if (m_file_info.isFile()) { - if (file_name.endsWith(".disabled")) { - file_name.chop(9); + if (fileName.endsWith(".disabled")) { + fileName.chop(9); m_enabled = false; } - if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { + if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) { m_type = ResourceType::ZIPFILE; - file_name.chop(4); - } else if (file_name.endsWith(".nilmod")) { + fileName.chop(4); + } else if (fileName.endsWith(".nilmod")) { m_type = ResourceType::ZIPFILE; - file_name.chop(7); - } else if (file_name.endsWith(".litemod")) { + fileName.chop(7); + } else if (fileName.endsWith(".litemod")) { m_type = ResourceType::LITEMOD; - file_name.chop(8); + fileName.chop(8); } else { m_type = ResourceType::SINGLEFILE; } - m_name = file_name; + m_name = fileName; } m_changed_date_time = m_file_info.lastModified(); @@ -76,39 +80,45 @@ void Resource::parseFile() auto Resource::name() const -> QString { - if (metadata()) + if (metadata()) { return metadata()->name; + } return m_name; } -static void removeThePrefix(QString& string) +namespace { +void removeThePrefix(QString& string) { static const QRegularExpression s_regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); string.remove(s_regex); string = string.trimmed(); } +} // namespace auto Resource::provider() const -> QString { - if (metadata()) + if (metadata()) { return ModPlatform::ProviderCapabilities::readableName(metadata()->provider); + } return tr("Unknown"); } auto Resource::homepage() const -> QString { - if (metadata()) + if (metadata()) { return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id); + } return {}; } void Resource::setMetadata(std::shared_ptr&& metadata) { - if (status() == ResourceStatus::NO_METADATA) - setStatus(ResourceStatus::INSTALLED); + if (status() == ResourceStatus::NoMetadata) { + setStatus(ResourceStatus::Installed); + } m_metadata = metadata; } @@ -133,12 +143,12 @@ void Resource::updateIssues(const BaseInstance* inst) return; } - auto mcInst = dynamic_cast(inst); + const auto* mcInst = dynamic_cast(inst); if (mcInst == nullptr) { return; } - auto profile = mcInst->getPackProfile(); + auto* profile = mcInst->getPackProfile(); QString mcVersion = profile->getComponentVersion("net.minecraft"); if (!m_metadata->mcVersions.empty() && !m_metadata->mcVersions.contains(mcVersion)) { @@ -151,46 +161,59 @@ int Resource::compare(const Resource& other, SortType type) const { switch (type) { default: - case SortType::ENABLED: - if (enabled() && !other.enabled()) + case SortType::Enabled: + if (enabled() && !other.enabled()) { return 1; - if (!enabled() && other.enabled()) + } + if (!enabled() && other.enabled()) { return -1; + } break; - case SortType::NAME: { - QString this_name{ name() }; - QString other_name{ other.name() }; + case SortType::Name: { + QString thisName{ name() }; + QString otherName{ other.name() }; // TODO do we need this? it could result in 0 being returned - removeThePrefix(this_name); - removeThePrefix(other_name); + removeThePrefix(thisName); + removeThePrefix(otherName); - return QString::compare(this_name, other_name, Qt::CaseInsensitive); + return QString::compare(thisName, otherName, Qt::CaseInsensitive); } - case SortType::DATE: - if (dateTimeChanged() > other.dateTimeChanged()) + case SortType::Date: + if (dateTimeChanged() > other.dateTimeChanged()) { return 1; - if (dateTimeChanged() < other.dateTimeChanged()) + } + if (dateTimeChanged() < other.dateTimeChanged()) { return -1; + } break; - case SortType::SIZE: { + case SortType::Filename: + return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName()); + + case SortType::Size: { if (this->type() != other.type()) { - if (this->type() == ResourceType::FOLDER) + if (this->type() == ResourceType::FOLDER) { return -1; - if (other.type() == ResourceType::FOLDER) + } + if (other.type() == ResourceType::FOLDER) { return 1; + } } - if (sizeInfo() > other.sizeInfo()) + if (sizeInfo() > other.sizeInfo()) { return 1; - if (sizeInfo() < other.sizeInfo()) + } + if (sizeInfo() < other.sizeInfo()) { return -1; + } break; } - case SortType::PROVIDER: { - auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); - if (compare_result != 0) - return compare_result; + + case SortType::Provider: { + auto compareResult = QString::compare(provider(), other.provider(), Qt::CaseInsensitive); + if (compareResult != 0) { + return compareResult; + } break; } } @@ -200,13 +223,20 @@ int Resource::compare(const Resource& other, SortType type) const bool Resource::applyFilter(QRegularExpression filter) const { - return filter.match(name()).hasMatch(); + if (filter.match(name()).hasMatch()) { + return true; + } + if (filter.match(fileinfo().fileName()).hasMatch()) { + return true; + } + return false; } 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; + } QString path = m_file_info.absoluteFilePath(); QFile file(path); @@ -225,14 +255,16 @@ bool Resource::enable(EnableAction action) break; } - if (m_enabled == enable) + if (m_enabled == enable) { return false; + } if (enable) { // m_enabled is false, but there's no '.disabled' suffix. // TODO: Report error? - if (!path.endsWith(".disabled")) + if (!path.endsWith(".disabled")) { return false; + } path.chop(9); } else { path += ".disabled"; @@ -240,8 +272,9 @@ bool Resource::enable(EnableAction action) path = FS::getUniqueResourceName(path); } } - if (!file.rename(path)) + if (!file.rename(path)) { return false; + } setFile(QFileInfo(path)); @@ -249,33 +282,34 @@ bool Resource::enable(EnableAction action) return true; } -auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool +auto Resource::destroy(const QDir& indexDir, bool preserveMetadata, bool attemptTrash) -> bool { m_type = ResourceType::UNKNOWN; - if (!preserve_metadata) { + if (!preserveMetadata) { qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name()); - destroyMetadata(index_dir); + destroyMetadata(indexDir); } - return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); + return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath()); } -auto Resource::destroyMetadata(const QDir& index_dir) -> void +auto Resource::destroyMetadata(const QDir& indexDir) -> void { if (metadata()) { - Metadata::remove(index_dir, metadata()->slug); + Metadata::remove(indexDir, metadata()->slug); } else { auto n = name(); - Metadata::remove(index_dir, n); + Metadata::remove(indexDir, n); } m_metadata = nullptr; } bool Resource::isSymLinkUnder(const QString& instPath) const { - if (isSymLink()) + if (isSymLink()) { return true; + } auto instDir = QDir(instPath); @@ -293,8 +327,9 @@ bool Resource::isMoreThanOneHardLink() const auto Resource::getOriginalFileName() const -> QString { auto fileName = m_file_info.fileName(); - if (!m_enabled) + if (!m_enabled) { fileName.chop(9); + } return fileName; } @@ -324,16 +359,16 @@ QDebug operator<<(QDebug debug, ResourceType type) QDebug operator<<(QDebug debug, ResourceStatus status) { switch (status) { - case ResourceStatus::INSTALLED: + case ResourceStatus::Installed: debug << "INSTALLED"; break; - case ResourceStatus::NOT_INSTALLED: + case ResourceStatus::NotInstalled: debug << "NOT_INSTALLED"; break; - case ResourceStatus::NO_METADATA: + case ResourceStatus::NoMetadata: debug << "NO_METADATA"; break; - case ResourceStatus::UNKNOWN: + case ResourceStatus::Unknown: default: debug << "UNKNOWN"; break; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 485405b24..694d74ec0 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -45,7 +45,7 @@ class BaseInstance; -enum class ResourceType { +enum class ResourceType : std::uint8_t { UNKNOWN, //!< Indicates an unspecified resource type. ZIPFILE, //!< The resource is a zip file containing the resource's class files. SINGLEFILE, //!< The resource is a single file (not a zip file). @@ -55,32 +55,33 @@ enum class ResourceType { QDebug operator<<(QDebug debug, ResourceType type); -enum class ResourceStatus { - INSTALLED, // Both JAR and Metadata are present - NOT_INSTALLED, // Only the Metadata is present - NO_METADATA, // Only the JAR is present - UNKNOWN, // Default status +enum class ResourceStatus : std::uint8_t { + Installed, // Both JAR and Metadata are present + NotInstalled, // Only the Metadata is present + NoMetadata, // Only the JAR is present + Unknown, // Default status }; QDebug operator<<(QDebug debug, ResourceStatus status); -enum class SortType { - NAME, - DATE, - VERSION, - ENABLED, - PACK_FORMAT, - PROVIDER, - SIZE, - SIDE, - MC_VERSIONS, - LOADERS, - RELEASE_TYPE, - REQUIRES, - REQUIRED_BY, +enum class SortType : std::uint8_t { + Name, + Date, + Version, + Enabled, + PackFormat, + Provider, + Size, + Side, + McVersions, + Loaders, + ReleaseType, + Requires, + RequiredBy, + Filename, }; -enum class EnableAction { ENABLE, DISABLE, TOGGLE }; +enum class EnableAction : std::uint8_t { ENABLE, DISABLE, TOGGLE }; /** General class for managed resources. It mirrors a file in disk, with some more info * for display and house-keeping purposes. @@ -92,20 +93,19 @@ class Resource : public QObject { Q_DISABLE_COPY(Resource) public: using Ptr = shared_qobject_ptr; - using WeakPtr = QPointer; Resource(QObject* parent = nullptr); - Resource(QFileInfo file_info); - Resource(QString file_path) : Resource(QFileInfo(file_path)) {} + Resource(QFileInfo fileInfo); + Resource(const QString& filePath) : Resource(QFileInfo(filePath)) {} ~Resource() override = default; - void setFile(QFileInfo file_info); + void setFile(QFileInfo fileInfo); void parseFile(); auto fileinfo() const -> QFileInfo { return m_file_info; } auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } - auto internal_id() const -> QString { return m_internal_id; } + auto internalId() const -> QString { return m_internal_id; } auto type() const -> ResourceType { return m_type; } bool enabled() const { return m_enabled; } auto getOriginalFileName() const -> QString; @@ -138,7 +138,7 @@ class Resource : public QObject { * = 0: 'this' is equal to '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), * or if such filter includes the Resource (true). @@ -163,9 +163,9 @@ class Resource : public QObject { } // Delete all files of this resource. - auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool; + auto destroy(const QDir& indexDir, bool preserveMetadata = false, bool attemptTrash = true) -> bool; // Delete the metadata only. - auto destroyMetadata(const QDir& index_dir) -> void; + auto destroyMetadata(const QDir& indexDir) -> void; auto isSymLink() const -> bool { return m_file_info.isSymLink(); } @@ -195,7 +195,7 @@ class Resource : public QObject { ResourceType m_type = ResourceType::UNKNOWN; /* Installation status of the resource. */ - ResourceStatus m_status = ResourceStatus::UNKNOWN; + ResourceStatus m_status = ResourceStatus::Unknown; std::shared_ptr m_metadata = nullptr; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 9d3f3e749..ddc132089 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "Application.h" @@ -27,10 +28,10 @@ #include "tasks/Task.h" #include "ui/dialogs/CustomMessageBox.h" -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_is_indexed(is_indexed) +ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_isIndexed(isIndexed) { - if (create_dir) { + if (createDir) { FS::ensureFolderPathExists(m_dir.absolutePath()); } @@ -49,70 +50,75 @@ ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance ResourceFolderModel::~ResourceFolderModel() { - while (!QThreadPool::globalInstance()->waitForDone(100)) + while (!QThreadPool::globalInstance()->waitForDone(100)) { QCoreApplication::processEvents(); + } } bool ResourceFolderModel::startWatching(const QStringList& paths) { // Remove orphaned metadata next time - m_first_folder_load = true; + m_firstFolderLoad = true; - if (m_is_watching) + if (m_isWatching) { return false; + } - auto couldnt_be_watched = m_watcher.addPaths(paths); - for (auto path : paths) { - if (couldnt_be_watched.contains(path)) + auto couldntBeWatched = m_watcher.addPaths(paths); + for (const auto& path : paths) { + if (couldntBeWatched.contains(path)) { qDebug() << "Failed to start watching" << path; - else + } else { qDebug() << "Started watching" << path; + } } update(); - m_is_watching = !m_is_watching; - return m_is_watching; + m_isWatching = !m_isWatching; + return m_isWatching; } bool ResourceFolderModel::stopWatching(const QStringList& paths) { - if (!m_is_watching) + if (!m_isWatching) { return false; - - auto couldnt_be_stopped = m_watcher.removePaths(paths); - for (auto path : paths) { - if (couldnt_be_stopped.contains(path)) - qDebug() << "Failed to stop watching" << path; - else - qDebug() << "Stopped watching" << path; } - m_is_watching = !m_is_watching; - return !m_is_watching; + auto couldntBeStopped = m_watcher.removePaths(paths); + for (const auto& path : paths) { + if (couldntBeStopped.contains(path)) { + qDebug() << "Failed to stop watching" << path; + } else { + qDebug() << "Stopped watching" << path; + } + } + + m_isWatching = !m_isWatching; + return !m_isWatching; } -bool ResourceFolderModel::installResource(QString original_path) +bool ResourceFolderModel::installResource(QString originalPath) { // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - original_path = FS::NormalizePath(original_path); - QFileInfo file_info(original_path); + originalPath = FS::NormalizePath(originalPath); + QFileInfo fileInfo(originalPath); - if (!file_info.exists() || !file_info.isReadable()) { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path; + if (!fileInfo.exists() || !fileInfo.isReadable()) { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; return false; } - qDebug() << "Installing:" << file_info.absoluteFilePath(); + qDebug() << "Installing:" << fileInfo.absoluteFilePath(); - Resource resource(file_info); + Resource resource(fileInfo); if (!resource.valid()) { - qWarning() << original_path << "is not a valid resource. Ignoring it."; + qWarning() << originalPath << "is not a valid resource. Ignoring it."; return false; } - auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName())); - if (original_path == new_path) { - qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense..."; + auto newPath = FS::NormalizePath(m_dir.filePath(fileInfo.fileName())); + if (originalPath == newPath) { + qWarning() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; return false; } @@ -120,45 +126,47 @@ bool ResourceFolderModel::installResource(QString original_path) case ResourceType::SINGLEFILE: case ResourceType::ZIPFILE: case ResourceType::LITEMOD: { - if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { - if (!FS::deletePath(new_path)) { - qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; + if (QFile::exists(newPath) || QFile::exists(newPath + QString(".disabled"))) { + if (!FS::deletePath(newPath)) { + qCritical() << "Cleaning up new location (" << newPath << ") was unsuccessful!"; return false; } - qDebug() << new_path << "has been deleted."; + qDebug() << newPath << "has been deleted."; } - if (!QFile::copy(original_path, new_path)) { - qCritical() << "Copy from" << original_path << "to" << new_path << "has failed."; + if (!QFile::copy(originalPath, newPath)) { + qCritical() << "Copy from" << originalPath << "to" << newPath << "has failed."; return false; } - FS::updateTimestamp(new_path); + FS::updateTimestamp(newPath); - QFileInfo new_path_file_info(new_path); - resource.setFile(new_path_file_info); + QFileInfo newPathFileInfo(newPath); + resource.setFile(newPathFileInfo); - if (!m_is_watching) + if (!m_isWatching) { return update(); + } return true; } case ResourceType::FOLDER: { - if (QFile::exists(new_path)) { - qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path; + if (QFile::exists(newPath)) { + qDebug() << "Ignoring folder '" << originalPath << "', it would merge with" << newPath; return false; } - if (!FS::copy(original_path, new_path)()) { - qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed."; + if (!FS::copy(originalPath, newPath)()) { + qWarning() << "Copy of folder from" << originalPath << "to" << newPath << "has (potentially partially) failed."; return false; } - QFileInfo newpathInfo(new_path); + QFileInfo newpathInfo(newPath); resource.setFile(newpathInfo); - if (!m_is_watching) + if (!m_isWatching) { return update(); + } return true; } @@ -168,24 +176,24 @@ bool ResourceFolderModel::installResource(QString original_path) return false; } -void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers) +void ResourceFolderModel::installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers) { - auto install = [this, path] { installResource(std::move(path)); }; + auto install = [this, path] { installResource(path); }; if (vers.addonId.isValid()) { ModPlatform::IndexedPack pack{ - vers.addonId, - ModPlatform::ResourceProvider::FLAME, + .addonId = vers.addonId, + .provider = ModPlatform::ResourceProvider::FLAME, }; auto [job, response] = FlameAPI().getProject(vers.addonId.toString()); connect(job.get(), &Task::failed, this, install); connect(job.get(), &Task::aborted, this, install); connect(job.get(), &Task::succeeded, [response, this, &vers, install, &pack] { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response for mod info at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response for mod info at" << parseError.offset + << "reason:" << parseError.errorString(); qDebug() << *response; return; } @@ -196,9 +204,9 @@ void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlat qDebug() << doc; qWarning() << "Error while reading mod info:" << e.cause(); } - LocalResourceUpdateTask update_metadata(indexDir(), pack, vers); - connect(&update_metadata, &Task::finished, this, install); - update_metadata.start(); + LocalResourceUpdateTask updateMetadata(indexDir(), pack, vers); + connect(&updateMetadata, &Task::finished, this, install); + updateMetadata.start(); }); job->start(); @@ -207,7 +215,7 @@ void ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlat } } -bool ResourceFolderModel::uninstallResource(const QString& file_name, bool preserve_metadata) +bool ResourceFolderModel::uninstallResource(const QString& fileName, bool preserveMetadata) { for (auto& resource : m_resources) { auto resourceFileInfo = resource->fileinfo(); @@ -216,8 +224,8 @@ bool ResourceFolderModel::uninstallResource(const QString& file_name, bool prese resourceFileName.chop(9); } - if (resourceFileName == file_name) { - auto res = resource->destroy(indexDir(), preserve_metadata, false); + if (resourceFileName == fileName) { + auto res = resource->destroy(indexDir(), preserveMetadata, false); update(); @@ -229,14 +237,16 @@ bool ResourceFolderModel::uninstallResource(const QString& file_name, bool prese bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return true; + } for (auto i : indexes) { - if (i.column() != 0) + if (i.column() != 0) { continue; + } - auto& resource = m_resources.at(i.row()); + const auto& resource = m_resources.at(i.row()); resource->destroy(indexDir()); } @@ -247,14 +257,16 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes) { - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return; + } for (auto i : indexes) { - if (i.column() != 0) + if (i.column() != 0) { continue; + } - auto& resource = m_resources.at(i.row()); + const auto& resource = m_resources.at(i.row()); resource->destroyMetadata(indexDir()); } @@ -271,33 +283,36 @@ bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, Ena QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return false; + } } - if (indexes.isEmpty()) + if (indexes.isEmpty()) { return true; + } bool succeeded = true; - for (auto const& idx : indexes) { - if (!validateIndex(idx) || idx.column() != 0) + for (const auto& idx : indexes) { + if (!validateIndex(idx) || idx.column() != 0) { continue; + } int row = idx.row(); auto& resource = m_resources[row]; // Preserve the row, but change its ID - auto old_id = resource->internal_id(); + auto oldId = resource->internalId(); if (!resource->enable(action)) { succeeded = false; continue; } - auto new_id = resource->internal_id(); + auto newId = resource->internalId(); - m_resources_index.remove(old_id); - m_resources_index[new_id] = row; + m_resourcesIndex.remove(oldId); + m_resourcesIndex[newId] = row; emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } @@ -312,24 +327,25 @@ bool ResourceFolderModel::update() QMutexLocker lock(&s_update_task_mutex); // Already updating, so we schedule a future update and return. - if (m_current_update_task) { - m_scheduled_update = true; + if (m_currentUpdateTask) { + m_scheduledUpdate = true; return false; } - m_current_update_task.reset(createUpdateTask()); - if (!m_current_update_task) + m_currentUpdateTask.reset(createUpdateTask()); + if (!m_currentUpdateTask) { return false; + } - connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, + connect(m_currentUpdateTask.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); - connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); + connect(m_currentUpdateTask.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); connect( - m_current_update_task.get(), &Task::finished, this, + m_currentUpdateTask.get(), &Task::finished, this, [this] { - m_current_update_task.reset(); - if (m_scheduled_update) { - m_scheduled_update = false; + m_currentUpdateTask.reset(); + if (m_scheduledUpdate) { + m_scheduledUpdate = false; update(); } else { emit updateFinished(); @@ -340,16 +356,16 @@ bool ResourceFolderModel::update() Task::Ptr preUpdate{ createPreUpdateTask() }; if (preUpdate != nullptr) { - auto task = new SequentialTask("ResourceFolderModel::update"); + auto* task = new SequentialTask("ResourceFolderModel::update"); task->addTask(preUpdate); - task->addTask(m_current_update_task); + task->addTask(m_currentUpdateTask); connect(task, &Task::finished, [task] { task->deleteLater(); }); QThreadPool::globalInstance()->start(task); } else { - QThreadPool::globalInstance()->start(m_current_update_task.get()); + QThreadPool::globalInstance()->start(m_currentUpdateTask.get()); } return true; @@ -362,24 +378,25 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) } Task::Ptr task{ createParseTask(*res) }; - if (!task) + if (!task) { return; + } - int ticket = m_next_resolution_ticket.fetch_add(1); + int ticket = m_nextResolutionTicket.fetch_add(1); res->setResolving(true, ticket); - m_active_parse_tasks.insert(ticket, task); + m_activeParseTasks.insert(ticket, task); connect( - task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internal_id()); }, + task.get(), &Task::succeeded, this, [this, ticket, res] { onParseSucceeded(ticket, res->internalId()); }, Qt::ConnectionType::QueuedConnection); connect( - task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internal_id()); }, + task.get(), &Task::failed, this, [this, ticket, res] { onParseFailed(ticket, res->internalId()); }, Qt::ConnectionType::QueuedConnection); connect( task.get(), &Task::finished, this, [this, ticket] { - m_active_parse_tasks.remove(ticket); + m_activeParseTasks.remove(ticket); emit parseFinished(); }, Qt::ConnectionType::QueuedConnection); @@ -394,44 +411,45 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) void ResourceFolderModel::onUpdateSucceeded() { - auto update_results = static_cast(m_current_update_task.get())->result(); + auto updateResults = static_cast(m_currentUpdateTask.get())->result(); - auto& new_resources = update_results->resources; + auto& newResources = updateResults->resources; - auto current_list = m_resources_index.keys(); - QSet current_set(current_list.begin(), current_list.end()); + auto currentList = m_resourcesIndex.keys(); + QSet currentSet(currentList.begin(), currentList.end()); - auto new_list = new_resources.keys(); - QSet new_set(new_list.begin(), new_list.end()); + auto newList = newResources.keys(); + QSet newSet(newList.begin(), newList.end()); - applyUpdates(current_set, new_set, new_resources); + applyUpdates(currentSet, newSet, newResources); } -void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) +void ResourceFolderModel::onParseSucceeded(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { return; + } - int row = m_resources_index[resource_id]; + int row = m_resourcesIndex[resourceId]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } Task* ResourceFolderModel::createUpdateTask() { - auto index_dir = indexDir(); - auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, - [this](const QFileInfo& file) { return createResource(file); }); - m_first_folder_load = false; + auto indexDir2 = indexDir(); + auto* task = new ResourceFolderLoadTask(dir(), indexDir2, m_isIndexed, m_firstFolderLoad, + [this](const QFileInfo& file) { return createResource(file); }); + m_firstFolderLoad = false; return task; } bool ResourceFolderModel::hasPendingParseTasks() const { - return !m_active_parse_tasks.isEmpty(); + return !m_activeParseTasks.isEmpty(); } -void ResourceFolderModel::directoryChanged(QString path) +void ResourceFolderModel::directoryChanged(const QString& /*path*/) { update(); } @@ -446,8 +464,9 @@ Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const { Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); auto flags = defaultFlags | Qt::ItemIsDropEnabled; - if (index.isValid()) + if (index.isValid()) { flags |= Qt::ItemIsUserCheckable; + } return flags; } @@ -458,21 +477,25 @@ QStringList ResourceFolderModel::mimeTypes() const return types; } -bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) +bool ResourceFolderModel::dropMimeData(const QMimeData* data, + Qt::DropAction action, + int /*row*/, + int /*column*/, + const QModelIndex& /*parent*/) { if (action == Qt::IgnoreAction) { return true; } // check if the action is supported - if (!data || !(action & supportedDropActions())) { + if ((data == nullptr) || !(action & supportedDropActions())) { return false; } // files dropped from outside? if (data->hasUrls()) { auto urls = data->urls(); - for (auto url : urls) { + for (const auto& url : urls) { // only local files may be dropped... if (!url.isLocalFile()) { continue; @@ -488,14 +511,12 @@ bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction act bool ResourceFolderModel::validateIndex(const QModelIndex& index) const { - if (!index.isValid()) + if (!index.isValid()) { return false; + } int row = index.row(); - if (row < 0 || row >= m_resources.size()) - return false; - - return true; + return row >= 0 && row < m_resources.size(); } // HACK: all subclasses need to call this to have the whole row painted @@ -504,15 +525,15 @@ QBrush ResourceFolderModel::rowBackground(int row) const { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && m_resources[row]->hasIssues()) { return { QColor(255, 0, 0, 40) }; - } else { - return {}; } + return {}; } QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -530,11 +551,13 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return m_resources[row]->provider(); case SizeColumn: return m_resources[row]->sizeStr(); + case FileNameColumn: + return m_resources[row]->fileinfo().fileName(); default: return {}; } case Qt::ToolTipRole: { - QString tooltip = m_resources[row]->internal_id(); + QString tooltip = m_resources[row]->internalId(); if (column == NameColumn) { if (APPLICATION->settings()->get("ShowModIncompat").toBool()) { @@ -545,7 +568,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (at(row).isSymLinkUnder(instDirPath())) { tooltip += - m_resources[row]->internal_id() + + m_resources[row]->internalId() + tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original." "\nCanonical Path: %1") .arg(at(row).fileinfo().canonicalFilePath()); @@ -562,7 +585,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const if (column == NameColumn) { if (APPLICATION->settings()->get("ShowModIncompat").toBool() && at(row).hasIssues()) { 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"); } } @@ -570,8 +594,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const return {}; } case Qt::CheckStateRole: - if (column == ActiveColumn) + if (column == ActiveColumn) { return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + } return {}; default: return {}; @@ -581,8 +606,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role) { int row = index.row(); - if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) + if (row < 0 || row >= rowCount(index.parent()) || !index.isValid()) { return false; + } if (role == Qt::CheckStateRole) { return setResourceEnabled({ index }, EnableAction::TOGGLE); @@ -601,6 +627,7 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien case DateColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -618,6 +645,8 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien return tr("The source provider of the resource."); case SizeColumn: return tr("The size of the resource."); + case FileNameColumn: + return tr("The file name of the resource."); default: return {}; } @@ -638,22 +667,22 @@ void ResourceFolderModel::setupHeaderAction(QAction* act, int column) void ResourceFolderModel::saveColumns(QTreeView* tree) { - auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getSetting(stateSettingName); 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 - auto settings = m_instance->settings(); + auto* settings = m_instance->settings(); if (!settings->get(overrideSettingName).toBool()) { settings = APPLICATION->settings(); } auto visibility = Json::toMap(settings->get(visibilitySettingName).toString()); - for (auto i = 0; i < m_column_names.size(); ++i) { + for (auto i = 0; i < m_columnNames.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_column_names[i]; + auto name = m_columnNames[i]; visibility[name] = !tree->isColumnHidden(i); } } @@ -662,24 +691,24 @@ void ResourceFolderModel::saveColumns(QTreeView* tree) void ResourceFolderModel::loadColumns(QTreeView* tree) { - auto const stateSettingName = QString("UI/%1_Page/Columns").arg(id()); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); - auto const visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); + const auto stateSettingName = QString("UI/%1_Page/Columns").arg(id()); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + const auto visibilitySettingName = QString("UI/%1_Page/ColumnsVisibility").arg(id()); auto stateSetting = m_instance->settings()->getOrRegisterSetting(stateSettingName, ""); tree->header()->restoreState(QByteArray::fromBase64(stateSetting->get().toString().toUtf8())); - auto setVisible = [this, tree](QVariant value) { + auto setVisible = [this, tree](const QVariant& value) { auto visibility = Json::toMap(value.toString()); - for (auto i = 0; i < m_column_names.size(); ++i) { + for (auto i = 0; i < m_columnNames.size(); ++i) { if (m_columnsHideable[i]) { - auto name = m_column_names[i]; + auto name = m_columnNames[i]; tree->setColumnHidden(i, !visibility.value(name, false).toBool()); } } }; - auto const defaultValue = Json::fromMap({ + const auto defaultValue = Json::fromMap({ { "Image", true }, { "Version", true }, { "Last Modified", true }, @@ -687,7 +716,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) { "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 - auto settings = m_instance->settings(); + auto* settings = m_instance->settings(); if (!settings->getOrRegisterSetting(overrideSettingName, false)->get().toBool()) { settings = APPLICATION->settings(); } @@ -696,7 +725,7 @@ void ResourceFolderModel::loadColumns(QTreeView* tree) // allways connect the signal in case the setting is toggled on and off auto gSetting = APPLICATION->settings()->getOrRegisterSetting(visibilitySettingName, defaultValue); - connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, QVariant value) { + connect(gSetting.get(), &Setting::SettingChanged, tree, [this, setVisible, overrideSettingName](const Setting&, const QVariant& value) { if (!m_instance->settings()->get(overrideSettingName).toBool()) { setVisible(value); } @@ -705,11 +734,11 @@ void ResourceFolderModel::loadColumns(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 - auto act = new QAction(tr("Override Columns Visibility"), menu); - auto const overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); + auto* act = new QAction(tr("Override Columns Visibility"), menu); + const auto overrideSettingName = QString("UI/%1_Page/ColumnsOverride").arg(id()); act->setCheckable(true); act->setChecked(m_instance->settings()->getOrRegisterSetting(overrideSettingName, false)->get().toBool()); @@ -725,9 +754,10 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) for (int col = 0; col < columnCount(); ++col) { // Skip creating actions for columns that should not be hidden - if (!m_columnsHideable.at(col)) + if (!m_columnsHideable.at(col)) { continue; - auto act = new QAction(menu); + } + auto* act = new QAction(menu); setupHeaderAction(act, col); act->setCheckable(true); @@ -736,8 +766,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree) connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled) { tree->setColumnHidden(col, !toggled); for (int c = 0; c < columnCount(); ++c) { - if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents) + if (m_columnResizeModes.at(c) == QHeaderView::ResizeToContents) { tree->resizeColumnToContents(c); + } } saveColumns(tree); }); @@ -755,41 +786,43 @@ QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* pare SortType ResourceFolderModel::columnToSortKey(size_t column) const { - Q_ASSERT(m_column_sort_keys.size() == columnCount()); - return m_column_sort_keys.at(column); + Q_ASSERT(m_columnSortKeys.size() == columnCount()); + return m_columnSortKeys.at(column); } /* Standard Proxy Model for createFilterProxyModel */ -bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, [[maybe_unused]] const QModelIndex& source_parent) const +bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const { auto* model = qobject_cast(sourceModel()); - if (!model) + if (!model) { return true; + } - const auto& resource = model->at(source_row); + const auto& resource = model->at(sourceRow); return resource.applyFilter(filterRegularExpression()); } -bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const { auto* model = qobject_cast(sourceModel()); - if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { - return QSortFilterProxyModel::lessThan(source_left, source_right); + if (!model || !sourceLeft.isValid() || !sourceRight.isValid() || sourceLeft.column() != sourceRight.column()) { + return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); } // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and // proceed. - auto column_sort_key = model->columnToSortKey(source_left.column()); - auto const& resource_left = model->at(source_left.row()); - auto const& resource_right = model->at(source_right.row()); + auto columnSortKey = model->columnToSortKey(sourceLeft.column()); + const auto& resourceLeft = model->at(sourceLeft.row()); + const auto& resourceRight = model->at(sourceRight.row()); - auto compare_result = resource_left.compare(resource_right, column_sort_key); - if (compare_result == 0) - return QSortFilterProxyModel::lessThan(source_left, source_right); + auto compareResult = resourceLeft.compare(resourceRight, columnSortKey); + if (compareResult == 0) { + return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); + } - return compare_result < 0; + return compareResult < 0; } QString ResourceFolderModel::instDirPath() const @@ -797,50 +830,51 @@ QString ResourceFolderModel::instDirPath() const return QFileInfo(m_instance->instanceRoot()).absoluteFilePath(); } -void ResourceFolderModel::onParseFailed(int ticket, QString resource_id) +void ResourceFolderModel::onParseFailed(int ticket, const QString& resourceId) { - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd() || !m_resources_index.contains(resource_id)) + auto iter = m_activeParseTasks.constFind(ticket); + if (iter == m_activeParseTasks.constEnd() || !m_resourcesIndex.contains(resourceId)) { return; + } - auto removed_index = m_resources_index[resource_id]; - auto removed_it = m_resources.begin() + removed_index; - Q_ASSERT(removed_it != m_resources.end()); + auto removedIndex = m_resourcesIndex[resourceId]; + auto removedIt = m_resources.begin() + removedIndex; + Q_ASSERT(removedIt != m_resources.end()); - beginRemoveRows(QModelIndex(), removed_index, removed_index); - m_resources.erase(removed_it); + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + m_resources.erase(removedIt); // update index - m_resources_index.clear(); + m_resourcesIndex.clear(); int idx = 0; - for (auto const& mod : qAsConst(m_resources)) { - m_resources_index[mod->internal_id()] = idx; + for (const auto& mod : qAsConst(m_resources)) { + m_resourcesIndex[mod->internalId()] = idx; idx++; } endRemoveRows(); } -void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) +void ResourceFolderModel::applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources) { // see if the kept resources changed in some way { - QSet kept_set = current_set; - kept_set.intersect(new_set); + QSet keptSet = currentSet; + keptSet.intersect(newSet); - for (auto const& kept : kept_set) { - auto row_it = m_resources_index.constFind(kept); - Q_ASSERT(row_it != m_resources_index.constEnd()); - auto row = row_it.value(); + for (const auto& kept : keptSet) { + auto rowIt = m_resourcesIndex.constFind(kept); + Q_ASSERT(rowIt != m_resourcesIndex.constEnd()); + auto row = rowIt.value(); - auto& new_resource = new_resources[kept]; - auto const& current_resource = m_resources.at(row); + auto& newResource = newResources[kept]; + const auto& currentResource = m_resources.at(row); - if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { + if (newResource->dateTimeChanged() == currentResource->dateTimeChanged()) { // no significant change - bool hadIssues = !current_resource->hasIssues(); - current_resource->updateIssues(m_instance); + bool hadIssues = !currentResource->hasIssues(); + currentResource->updateIssues(m_instance); - if (hadIssues != current_resource->hasIssues()) { + if (hadIssues != currentResource->hasIssues()) { emit dataChanged(index(row, 0), index(row, columnCount({}) - 1)); } continue; @@ -848,16 +882,16 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // If the resource is resolving, but something about it changed, we don't want to // continue the resolving. - if (current_resource->isResolving()) { - auto ticket = current_resource->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); + if (currentResource->isResolving()) { + auto ticket = currentResource->resolutionTicket(); + if (m_activeParseTasks.contains(ticket)) { + auto* task = (*m_activeParseTasks.find(ticket)).get(); task->abort(); } } - m_resources[row].reset(new_resource); - new_resource->updateIssues(m_instance); + m_resources[row].reset(newResource); + newResource->updateIssues(m_instance); resolveResource(m_resources.at(row)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); @@ -866,46 +900,47 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // remove resources no longer present { - QSet removed_set = current_set; - removed_set.subtract(new_set); + QSet removedSet = currentSet; + removedSet.subtract(newSet); - QList removed_rows; - for (auto& removed : removed_set) - removed_rows.append(m_resources_index[removed]); + QList removedRows; + for (const auto& removed : removedSet) { + removedRows.append(m_resourcesIndex[removed]); + } - std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); + std::ranges::sort(removedRows, std::greater()); - for (auto& removed_index : removed_rows) { - auto removed_it = m_resources.begin() + removed_index; + for (auto& removedIndex : removedRows) { + auto removedIt = m_resources.begin() + removedIndex; - Q_ASSERT(removed_it != m_resources.end()); + Q_ASSERT(removedIt != m_resources.end()); - if ((*removed_it)->isResolving()) { - auto ticket = (*removed_it)->resolutionTicket(); - if (m_active_parse_tasks.contains(ticket)) { - auto task = (*m_active_parse_tasks.find(ticket)).get(); + if ((*removedIt)->isResolving()) { + auto ticket = (*removedIt)->resolutionTicket(); + if (m_activeParseTasks.contains(ticket)) { + auto* task = (*m_activeParseTasks.find(ticket)).get(); task->abort(); } } - beginRemoveRows(QModelIndex(), removed_index, removed_index); - m_resources.erase(removed_it); + beginRemoveRows(QModelIndex(), removedIndex, removedIndex); + m_resources.erase(removedIt); endRemoveRows(); } } // add new resources to the end { - QSet added_set = new_set; - added_set.subtract(current_set); + QSet addedSet = newSet; + addedSet.subtract(currentSet); // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (added_set.size() > 0) { + if (addedSet.size() > 0) { beginInsertRows(QModelIndex(), static_cast(m_resources.size()), - static_cast(m_resources.size() + added_set.size() - 1)); + static_cast(m_resources.size() + addedSet.size() - 1)); - for (auto& added : added_set) { - auto res = new_resources[added]; + for (const auto& added : addedSet) { + auto res = newResources[added]; res->updateIssues(m_instance); m_resources.append(res); resolveResource(m_resources.last()); @@ -917,10 +952,10 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // update index { - m_resources_index.clear(); + m_resourcesIndex.clear(); int idx = 0; - for (auto const& mod : qAsConst(m_resources)) { - m_resources_index[mod->internal_id()] = idx; + for (const auto& mod : qAsConst(m_resources)) { + m_resourcesIndex[mod->internalId()] = idx; idx++; } } @@ -928,17 +963,19 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet Resource::Ptr ResourceFolderModel::find(QString id) { auto iter = - std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; }); - if (iter == m_resources.constEnd()) + std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](const Resource::Ptr& r) { return r->internalId() == id; }); + if (iter == m_resources.constEnd()) { return nullptr; + } return *iter; } QList ResourceFolderModel::allResources() { QList result; result.reserve(m_resources.size()); - for (const Resource ::Ptr& resource : m_resources) + for (const Resource ::Ptr& resource : m_resources) { result.append((resource.get())); + } return result; } @@ -946,8 +983,9 @@ QList ResourceFolderModel::selectedResources(const QModelIndexList& i { QList result; for (const QModelIndex& index : indexes) { - if (index.column() != 0) + if (index.column() != 0) { continue; + } result.append(&at(index.row())); } return result; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 81bc6f5fc..7699394ab 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -61,7 +61,7 @@ class QSortFilterProxyModel; class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: - ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); ~ResourceFolderModel() override; virtual QString id() const { return "resource"; } @@ -93,13 +93,13 @@ class ResourceFolderModel : public QAbstractListModel { */ virtual bool installResource(QString path); - virtual void installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers); + virtual void installResourceWithFlameMetadata(const QString& path, ModPlatform::IndexedVersion& vers); /** Uninstall (i.e. remove all data about it) a resource, given its file name. * * Returns whether the removal was successful. */ - virtual bool uninstallResource(const QString& file_name, bool preserve_metadata = false); + virtual bool uninstallResource(const QString& fileName, bool preserveMetadata = false); virtual bool deleteResources(const QModelIndexList&); virtual void deleteMetadata(const QModelIndexList&); @@ -125,7 +125,7 @@ class ResourceFolderModel : public QAbstractListModel { Resource::Ptr find(QString id); - QDir const& dir() const { return m_dir; } + const QDir& dir() const { return m_dir; } /** Checks whether there's any parse tasks being done. * @@ -137,12 +137,12 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, FileNameColumn, NumColumns }; - QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; } + QStringList columnNames(bool translated = true) const { return translated ? m_columnNamesTranslated : m_columnNames; } int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast(size()); } - int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; } + int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NumColumns; } Qt::DropActions supportedDropActions() const override; @@ -171,18 +171,19 @@ class ResourceFolderModel : public QAbstractListModel { QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); SortType columnToSortKey(size_t column) const; - QList columnResizeModes() const { return m_column_resize_modes; } + QList columnResizeModes() const { return m_columnResizeModes; } class ProxyModel : public QSortFilterProxyModel { public: explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; + bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight) const override; }; QString instDirPath() const; + BaseInstance* instance() const { return m_instance; } signals: void updateFinished(); @@ -206,7 +207,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 * in the background, so it slowly updates the UI as tasks get done. */ - [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; } + [[nodiscard]] virtual Task* createParseTask(Resource& /*unused*/) { return nullptr; } /** Standard implementation of the model update logic. * @@ -214,10 +215,10 @@ class ResourceFolderModel : public QAbstractListModel { * to act only on those disparities. * */ - void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); + void applyUpdates(QSet& currentSet, QSet& newSet, QMap& newResources); protected slots: - void directoryChanged(QString); + void directoryChanged(const QString&); /** Called when the update task is successful. * @@ -233,39 +234,40 @@ class ResourceFolderModel : public QAbstractListModel { * 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. */ - virtual void onParseSucceeded(int ticket, QString resource_id); - virtual void onParseFailed(int ticket, QString resource_id); + virtual void onParseSucceeded(int ticket, const QString& resourceId); + virtual void onParseFailed(int ticket, const QString& resourceId); protected: // 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! - QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" }; - QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }; - QList m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive }; - QList m_columnsHideable = { false, false, true, true, true }; + QList m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Date, + SortType::Provider, SortType::Size, SortType::Filename }; + QStringList m_columnNames = { "Enable", "Name", "Last Modified", "Provider", "Size", "File Name" }; + QStringList m_columnNamesTranslated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }; + QList m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + QList m_columnsHideable = { false, false, true, true, true, true }; QDir m_dir; BaseInstance* m_instance; QFileSystemWatcher m_watcher; - bool m_is_watching = false; + bool m_isWatching = false; - bool m_is_indexed; - bool m_first_folder_load = true; + bool m_isIndexed; + bool m_firstFolderLoad = true; - Task::Ptr m_current_update_task = nullptr; - bool m_scheduled_update = false; + Task::Ptr m_currentUpdateTask = nullptr; + bool m_scheduledUpdate = false; QList m_resources; // Represents the relationship between a resource's internal ID and it's row position on the model. - QMap m_resources_index; + QMap m_resourcesIndex; // Runs off-thread ConcurrentTask m_resourceResolver; bool m_resourceResolverRunning = false; - QMap m_active_parse_tasks; - std::atomic m_next_resolution_ticket = 0; + QMap m_activeParseTasks; + std::atomic m_nextResolutionTicket = 0; }; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index b2a28290a..4dd8e314c 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -39,27 +39,26 @@ #include #include -#include "Version.h" - #include "minecraft/mod/tasks/LocalDataPackParseTask.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(dir, instance, is_indexed, create_dir, parent) +ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(dir, instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" }); - m_column_names_translated = - QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, - SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true, true }; + m_columnNames = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size", "File Name" }); + m_columnNamesTranslated = QStringList( + { tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::PackFormat, + SortType::Date, SortType::Provider, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true, true }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -92,6 +91,8 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -112,6 +113,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; + default: + break; } if (mappedIndex.isValid()) { @@ -133,6 +139,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O case ImageColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -153,6 +160,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O return tr("The source provider of the resource pack."); case SizeColumn: return tr("The size of the resource pack."); + case FileNameColumn: + return tr("The file name of the resource pack."); default: return {}; } @@ -168,10 +177,10 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } Task* ResourcePackFolderModel::createParseTask(Resource& resource) { - return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource)); + return new LocalDataPackParseTask(m_nextResolutionTicket, dynamic_cast(&resource)); } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index b552c324e..186bbb75d 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -7,9 +7,19 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + PackFormatColumn, + DateColumn, + ProviderColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; - explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); QString id() const override { return "resourcepacks"; } @@ -19,7 +29,7 @@ class ResourcePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(ResourcePack) }; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h index 9b0180180..ded641770 100644 --- a/launcher/minecraft/mod/ShaderPackFolderModel.h +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -18,7 +18,7 @@ class ShaderPackFolderModel : public ResourceFolderModel { [[nodiscard]] Task* createParseTask(Resource& resource) override { - return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast(resource)); + return new LocalShaderPackParseTask(m_nextResolutionTicket, static_cast(resource)); } QDir indexDir() const override { return m_dir; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index d96b768db..976bf3854 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -36,28 +36,30 @@ #include "TexturePackFolderModel.h" #include "minecraft/mod/tasks/LocalTexturePackParseTask.h" -#include "minecraft/mod/tasks/ResourceFolderLoadTask.h" -TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent) - : ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent) +TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent) + : ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent) { - m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" }); - m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") }); - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE }; - m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, - QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; - m_columnsHideable = { false, true, false, true, true, true }; + m_columnNames = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size", "File Name" }); + m_columnNamesTranslated = + QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("File Name") }); + m_columnSortKeys = { SortType::Enabled, SortType::Name, SortType::Name, SortType::Date, + SortType::Provider, SortType::Size, SortType::Filename }; + m_columnResizeModes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, + QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive }; + m_columnsHideable = { false, true, false, true, true, true, true }; } Task* TexturePackFolderModel::createParseTask(Resource& resource) { - return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); + return new LocalTexturePackParseTask(m_nextResolutionTicket, static_cast(resource)); } QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const { - if (!validateIndex(index)) + if (!validateIndex(index)) { return {}; + } int row = index.row(); int column = index.column(); @@ -76,6 +78,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const return QSize(32, 32); } break; + default: + break; } // map the columns to the base equivilents @@ -96,6 +100,11 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const case SizeColumn: mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn); break; + case FileNameColumn: + mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn); + break; + default: + break; } if (mappedIndex.isValid()) { @@ -116,6 +125,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or case ImageColumn: case ProviderColumn: case SizeColumn: + case FileNameColumn: return columnNames().at(section); default: return {}; @@ -132,6 +142,8 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or return tr("The source provider of the texture pack."); case SizeColumn: return tr("The size of the texture pack."); + case FileNameColumn: + return tr("The file name of the texture pack."); default: return {}; } @@ -145,5 +157,5 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or int TexturePackFolderModel::columnCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : NUM_COLUMNS; + return parent.isValid() ? 0 : NumColumns; } diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 37f78d8d7..3e7343092 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -44,11 +44,20 @@ class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: - enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS }; + enum Columns : std::uint8_t { + ActiveColumn = 0, + ImageColumn, + NameColumn, + DateColumn, + ProviderColumn, + SizeColumn, + FileNameColumn, + NumColumns + }; - explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr); + explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent = nullptr); - virtual QString id() const override { return "texturepacks"; } + QString id() const override { return "texturepacks"; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; @@ -56,7 +65,7 @@ class TexturePackFolderModel : public ResourceFolderModel { int columnCount(const QModelIndex& parent) const override; [[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); } - [[nodiscard]] Task* createParseTask(Resource&) override; + [[nodiscard]] Task* createParseTask(Resource& /*unused*/) override; RESOURCE_HELPERS(TexturePack) }; diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp index 3b98e053b..a90e9ca5e 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.cpp @@ -41,26 +41,28 @@ #include "minecraft/mod/MetadataHandler.h" #include +#include -ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir, - const QDir& index_dir, - bool is_indexed, - bool clean_orphan, - std::function create_function) +ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resourceDir, + const QDir& indexDir, + bool isIndexed, + bool cleanOrphan, + std::function createFunction) : Task(false) - , m_resource_dir(resource_dir) - , m_index_dir(index_dir) - , m_is_indexed(is_indexed) - , m_clean_orphan(clean_orphan) - , m_create_func(create_function) + , m_resource_dir(resourceDir) + , m_index_dir(indexDir) + , m_is_indexed(isIndexed) + , m_clean_orphan(cleanOrphan) + , m_create_func(std::move(createFunction)) , m_result(new Result()) , m_thread_to_spawn_into(thread()) {} 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); + } if (m_is_indexed) { // Read metadata first @@ -71,7 +73,7 @@ void ResourceFolderLoadTask::executeTask() m_resource_dir.refresh(); for (auto entry : m_resource_dir.entryInfoList()) { auto filePath = entry.absoluteFilePath(); - if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) { + if (auto* app = APPLICATION_DYN; (app != nullptr) && app->checkQSavePath(filePath)) { continue; } auto newFilePath = FS::getUniqueResourceName(filePath); @@ -83,29 +85,29 @@ void ResourceFolderLoadTask::executeTask() Resource* resource = m_create_func(entry); if (resource->enabled()) { - if (m_result->resources.contains(resource->internal_id())) { - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); + if (m_result->resources.contains(resource->internalId())) { + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); // Delete the object we just created, since a valid one is already in the mods list. delete resource; } else { - m_result->resources[resource->internal_id()].reset(resource); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); + m_result->resources[resource->internalId()].reset(resource); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); } } else { - QString chopped_id = resource->internal_id().chopped(9); - if (m_result->resources.contains(chopped_id)) { - m_result->resources[resource->internal_id()].reset(resource); + QString choppedId = resource->internalId().chopped(9); + if (m_result->resources.contains(choppedId)) { + m_result->resources[resource->internalId()].reset(resource); - auto metadata = m_result->resources[chopped_id]->metadata(); + auto metadata = m_result->resources[choppedId]->metadata(); if (metadata) { resource->setMetadata(*metadata); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED); - m_result->resources.remove(chopped_id); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::Installed); + m_result->resources.remove(choppedId); } } else { - m_result->resources[resource->internal_id()].reset(resource); - m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA); + m_result->resources[resource->internalId()].reset(resource); + m_result->resources[resource->internalId()]->setStatus(ResourceStatus::NoMetadata); } } } @@ -116,38 +118,41 @@ void ResourceFolderLoadTask::executeTask() QMutableMapIterator iter(m_result->resources); while (iter.hasNext()) { auto resource = iter.next().value(); - if (resource->status() == ResourceStatus::NOT_INSTALLED) { + if (resource->status() == ResourceStatus::NotInstalled) { resource->destroy(m_index_dir, false, false); iter.remove(); } } } - for (auto mod : m_result->resources) + for (const auto& mod : m_result->resources) { mod->moveToThread(m_thread_to_spawn_into); + } - if (m_aborted) + if (m_aborted) { emit finished(); - else + } else { emitSucceeded(); + } } void ResourceFolderLoadTask::getFromMetadata() { m_index_dir.refresh(); - for (auto entry : m_index_dir.entryList(QDir::Files)) { + for (const auto& entry : m_index_dir.entryList(QDir::Files)) { if (!entry.endsWith(".pw.toml")) { continue; } auto metadata = Metadata::get(m_index_dir, entry); - if (!metadata.isValid()) + if (!metadata.isValid()) { continue; + } auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename))); resource->setMetadata(metadata); - resource->setStatus(ResourceStatus::NOT_INSTALLED); - m_result->resources[resource->internal_id()].reset(resource); + resource->setStatus(ResourceStatus::NotInstalled); + m_result->resources[resource->internalId()].reset(resource); } } diff --git a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h index 7c872c13d..6489176b7 100644 --- a/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ResourceFolderLoadTask.h @@ -41,7 +41,7 @@ #include #include #include -#include "minecraft/mod/Mod.h" +#include "minecraft/mod/Resource.h" #include "tasks/Task.h" class ResourceFolderLoadTask : public Task { @@ -54,11 +54,11 @@ class ResourceFolderLoadTask : public Task { ResultPtr result() const { return m_result; } public: - ResourceFolderLoadTask(const QDir& resource_dir, - const QDir& index_dir, - bool is_indexed, - bool clean_orphan, - std::function create_function); + ResourceFolderLoadTask(const QDir& resourceDir, + const QDir& indexDir, + bool isIndexed, + bool cleanOrphan, + std::function createFunction); bool canAbort() const override { return true; } bool abort() override @@ -76,7 +76,7 @@ class ResourceFolderLoadTask : public Task { QDir m_resource_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; - std::function m_create_func; + std::function m_create_func; ResultPtr m_result; std::atomic m_aborted = false; diff --git a/launcher/minecraft/skins/SkinList.cpp b/launcher/minecraft/skins/SkinList.cpp index b47f17db5..c960b9493 100644 --- a/launcher/minecraft/skins/SkinList.cpp +++ b/launcher/minecraft/skins/SkinList.cpp @@ -121,7 +121,7 @@ bool SkinList::update() auto folderContents = m_dir.entryInfoList(); // if there are any untracked files... for (QFileInfo entry : folderContents) { - if (!entry.isFile() && entry.suffix() != "png") + if (!entry.isFile() || entry.suffix() != "png") continue; SkinModel w(entry.absoluteFilePath()); diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h index d3f577f44..ad68fd963 100644 --- a/launcher/modplatform/CheckUpdateTask.h +++ b/launcher/modplatform/CheckUpdateTask.h @@ -15,15 +15,15 @@ class CheckUpdateTask : public Task { std::vector& mcVersions, QList loadersList, ResourceFolderModel* resourceModel) - : Task(), m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) + : m_resources(resources), m_gameVersions(mcVersions), m_loadersList(std::move(loadersList)), m_resourceModel(resourceModel) {} struct Update { QString name; - QString old_hash; - QString old_version; - QString new_version; - std::optional new_version_type; + QString oldHash; + QString oldVersion; + QString newVersion; + std::optional newVersionType; QString changelog; ModPlatform::ResourceProvider provider; shared_qobject_ptr download; @@ -31,19 +31,19 @@ class CheckUpdateTask : public Task { public: Update(QString name, - QString old_h, - QString old_v, - QString new_v, - std::optional new_v_type, + QString oldH, + QString oldV, + QString newV, + std::optional newVType, QString changelog, ModPlatform::ResourceProvider p, shared_qobject_ptr t, bool enabled = true) : name(std::move(name)) - , old_hash(std::move(old_h)) - , old_version(std::move(old_v)) - , new_version(std::move(new_v)) - , new_version_type(std::move(new_v_type)) + , oldHash(std::move(oldH)) + , oldVersion(std::move(oldV)) + , newVersion(std::move(newV)) + , newVersionType(newVType) , changelog(std::move(changelog)) , provider(p) , download(std::move(t)) @@ -54,14 +54,11 @@ class CheckUpdateTask : public Task { auto getUpdates() -> std::vector&& { return std::move(m_updates); } auto getDependencies() -> QList>&& { return std::move(m_deps); } - public slots: - bool abort() override = 0; - protected slots: void executeTask() override = 0; signals: - void checkFailed(Resource* failed, QString reason, QUrl recover_url = {}); + void checkFailed(Resource* failed, QString reason, QUrl recoverUrl = {}); protected: QList& m_resources; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 1ef3a56c9..2c7e485e5 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -99,7 +99,7 @@ void EnsureMetadataTask::executeTask() } // They already have the right metadata :o - if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) { + if (resource->status() != ResourceStatus::NoMetadata && resource->metadata() && resource->metadata()->provider == m_provider) { qDebug() << "Resource" << resource->name() << "already has metadata!"; emitReady(resource); continue; @@ -263,7 +263,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask() Task::Ptr EnsureMetadataTask::modrinthProjectsTask() { QHash addonIds; - for (auto const& data : m_tempVersions) + for (const auto& data : m_tempVersions) addonIds.insert(data.addonId.toString(), data.hash); Task::Ptr proj_task; @@ -404,7 +404,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask() Task::Ptr EnsureMetadataTask::flameProjectsTask() { QHash addonIds; - for (auto const& hash : m_resources.keys()) { + for (const auto& hash : m_resources.keys()) { if (m_tempVersions.contains(hash)) { auto data = m_tempVersions.find(hash).value(); diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index e1278ae84..8984e575e 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -32,6 +32,7 @@ class QIODevice; namespace ModPlatform { enum class ModLoaderType : std::uint16_t { + None = 0U, NeoForge = 1U << 0U, Forge = 1U << 1U, Cauldron = 1U << 2U, diff --git a/launcher/modplatform/ResourceAPI.cpp b/launcher/modplatform/ResourceAPI.cpp index cda90b677..bd683ed21 100644 --- a/launcher/modplatform/ResourceAPI.cpp +++ b/launcher/modplatform/ResourceAPI.cpp @@ -148,9 +148,9 @@ Task::Ptr ResourceAPI::getProjectVersions(VersionSearchArgs&& args, Callback&& callbacks) const +Task::Ptr ResourceAPI::getProjectInfo(ProjectInfoArgs&& args, Callback&& callbacks, bool askRetry) const { - auto [job, response] = getProject(args.pack->addonId.toString()); + auto [job, response] = getProject(args.pack->addonId.toString(), askRetry); QObject::connect(job.get(), &NetJob::succeeded, [this, response, callbacks, args] { auto pack = args.pack; @@ -284,7 +284,7 @@ QString ResourceAPI::mapMCVersionToModrinth(Version v) const return verStr; } -std::pair ResourceAPI::getProject(QString addonId) const +std::pair ResourceAPI::getProject(QString addonId, bool askRetry) const { auto project_url_optional = getInfoURL(addonId); if (!project_url_optional.has_value()) @@ -293,6 +293,7 @@ std::pair ResourceAPI::getProject(QString addonId) const auto project_url = project_url_optional.value(); auto netJob = makeShared(QString("%1::GetProject").arg(addonId), APPLICATION->network()); + netJob->setAskRetry(askRetry); auto [action, response] = Net::ApiDownload::makeByteArray(QUrl(project_url)); netJob->addNetAction(action); diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h index 0ad55775c..51b6d4b50 100644 --- a/launcher/modplatform/ResourceAPI.h +++ b/launcher/modplatform/ResourceAPI.h @@ -115,10 +115,10 @@ class ResourceAPI { public slots: virtual Task::Ptr searchProjects(SearchArgs&&, Callback>&&) const; - virtual std::pair getProject(QString addonId) const; + virtual std::pair getProject(QString addonId, bool askRetry = true) const; virtual std::pair getProjects(QStringList addonIds) const = 0; - virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&) const; + virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, Callback&&, bool askRetry = true) const; Task::Ptr getProjectVersions(VersionSearchArgs&& args, Callback>&& callbacks) const; virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, Callback&&) const; diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 7a7365fbc..f3f66997b 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -38,6 +38,7 @@ #include #include +#include #include "FileSystem.h" #include "Json.h" @@ -59,18 +60,28 @@ #include "BuildConfig.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 { -static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version); - 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]"); m_pack_safe_name = packName.replace(s_regex, ""); - m_version_name = version; - m_install_mode = installMode; } bool PackInstallTask::abort() @@ -107,11 +118,10 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) QByteArray response = std::move(*responsePtr); jobPtr.reset(); - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ATLauncher at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ATLauncher at" << parseError.offset << "reason:" << parseError.errorString(); qWarning() << response; return; } @@ -128,7 +138,7 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) // Derived from the installation mode QString message; - bool resetDirectory; + bool resetDirectory = false; switch (m_install_mode) { case InstallMode::Reinstall: @@ -148,8 +158,9 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr) } // Display message if one exists - if (!message.isEmpty()) + if (!message.isEmpty()) { m_support->displayMessage(message); + } auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { @@ -173,7 +184,7 @@ void PackInstallTask::onDownloadFailed(QString reason) { qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId(); jobPtr.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); } void PackInstallTask::onDownloadAborted() @@ -202,26 +213,30 @@ void PackInstallTask::deleteExistingFiles() keeps.files.append(VersionKeep{ "root", "servers.dat" }); // 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); - for (const auto& item : m_version.deletes.folders) + } + for (const auto& item : m_version.deletes.folders) { deletes.folders.append(item); - for (const auto& item : m_version.keeps.files) + } + for (const auto& item : m_version.keeps.files) { keeps.files.append(item); - for (const auto& item : m_version.keeps.folders) + } + for (const auto& item : m_version.keeps.folders) { keeps.folders.append(item); + } auto getPathForBase = [this](const QString& base) { auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft"); if (base == "root") { return minecraftPath; - } else if (base == "config") { - return FS::PathCombine(minecraftPath, "config"); - } else { - qWarning() << "Unrecognised base path" << base; - return minecraftPath; } + if (base == "config") { + return FS::PathCombine(minecraftPath, "config"); + } + qWarning() << "Unrecognised base path" << base; + return minecraftPath; }; auto convertToSystemPath = [](const QString& path) { @@ -231,24 +246,22 @@ void PackInstallTask::deleteExistingFiles() }; auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) { - for (const auto& item : keeps.files) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - - if (fullPath == path) { - return true; - } + if (std::ranges::any_of(keeps.files, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + return fullPath == path; + })) { + return true; } - for (const auto& item : keeps.folders) { - auto basePath = getPathForBase(item.base); - auto targetPath = convertToSystemPath(item.target); - auto path = FS::PathCombine(basePath, targetPath); - - if (fullPath.startsWith(path)) { - return true; - } + if (std::ranges::any_of(keeps.folders, [&fullPath, &getPathForBase, &convertToSystemPath](const auto& item) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + return fullPath.startsWith(path); + })) { + return true; } return false; @@ -262,8 +275,9 @@ void PackInstallTask::deleteExistingFiles() auto targetPath = convertToSystemPath(item.target); auto fullPath = FS::PathCombine(basePath, targetPath); - if (shouldKeep(fullPath)) + if (shouldKeep(fullPath)) { continue; + } filesToDelete.insert(fullPath); } @@ -277,8 +291,9 @@ void PackInstallTask::deleteExistingFiles() while (it.hasNext()) { auto path = it.next(); - if (shouldKeep(path)) + if (shouldKeep(path)) { continue; + } filesToDelete.insert(path); } @@ -290,7 +305,7 @@ void PackInstallTask::deleteExistingFiles() } } -QString PackInstallTask::getDirForModType(ModType type, QString raw) +QString PackInstallTask::getDirForModType(ModType type, const QString& raw) { switch (type) { // Mod types that can either be ignored at this stage, or ignored @@ -338,7 +353,7 @@ QString PackInstallTask::getDirForModType(ModType type, QString raw) return Q_NULLPTR; } -QString PackInstallTask::getVersionForLoader(QString uid) +QString PackInstallTask::getVersionForLoader(const QString& uid) { if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) { auto vlist = APPLICATION->metadataIndex()->get(uid); @@ -359,16 +374,19 @@ QString PackInstallTask::getVersionForLoader(QString uid) // filtering for those loaders. if (m_version.loader.type != "fabric") { 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; - if (iter->equalsVersion != m_version.minecraft) + } + if (iter->equalsVersion != m_version.minecraft) { continue; + } } if (m_version.loader.recommended) { // first recommended build we find, we use. - if (!version->isRecommended()) + if (!version->isRecommended()) { continue; + } } return version->descriptor(); @@ -376,7 +394,8 @@ QString PackInstallTask::getVersionForLoader(QString uid) emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type)); return Q_NULLPTR; - } else if (m_version.loader.choose) { + } + if (m_version.loader.choose) { // Fabric Loader doesn't depend on a given Minecraft version. if (m_version.loader.type == "fabric") { return m_support->chooseVersion(vlist, Q_NULLPTR); @@ -420,7 +439,8 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) if (name == QString("guava")) { return "com.google.guava:guava:" + version; - } else if (name == QString("commons-lang3")) { + } + if (name == QString("commons-lang3")) { return "org.apache.commons:commons-lang3:" + version; } } @@ -428,7 +448,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library) return "org.multimc.atlauncher:" + library.md5 + ":1"; } -bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile* profile) +bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, PackProfile* profile) { if (m_version.libraries.isEmpty()) { return true; @@ -453,18 +473,18 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile } auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto target_id = "org.multimc.atlauncher." + id; + auto targetId = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); auto f = std::make_shared(); f->name = m_pack_name + " " + m_version_name + " (libraries)"; - const static QMap liteLoaderMap = { + const static QMap s_liteLoaderMap = { { "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" }, { "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" }, { "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" }, @@ -484,8 +504,8 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile for (const auto& lib : m_version.libraries) { // If the library is LiteLoader, we need to ignore it and handle it separately. - if (liteLoaderMap.contains(lib.md5)) { - auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5)); + if (s_liteLoaderMap.contains(lib.md5)) { + auto ver = getComponentVersion("com.mumfrey.liteloader", s_liteLoaderMap.value(lib.md5)); if (ver) { componentsToInstall.insert("com.mumfrey.liteloader", ver); continue; @@ -502,8 +522,9 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile libExempt = Version(libSpecifier.version()) >= Version(existingLib.version()); } } - if (libExempt) + if (libExempt) { continue; + } auto library = std::make_shared(); library->setRawName(libName); @@ -536,11 +557,11 @@ bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); return true; } -bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* profile) +bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfile* profile) { if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) { return true; @@ -571,13 +592,13 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro } auto id = QUuid::createUuid().toString(QUuid::WithoutBraces); - auto target_id = "org.multimc.atlauncher." + id; + auto targetId = "org.multimc.atlauncher." + id; auto patchDir = FS::PathCombine(instanceRoot, "patches"); if (!FS::ensureFolderPathExists(patchDir)) { return false; } - auto patchFileName = FS::PathCombine(patchDir, target_id + ".json"); + auto patchFileName = FS::PathCombine(patchDir, targetId + ".json"); QStringList mainClasses; QStringList tweakers; @@ -604,8 +625,9 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro for (auto arg : args) { if (arg.startsWith("--tweakClass=") || previous == "--tweakClass") { auto tweakClass = arg.remove("--tweakClass="); - if (tweakers.contains(tweakClass)) + if (tweakers.contains(tweakClass)) { continue; + } f->addTweakers.append(tweakClass); } @@ -624,7 +646,7 @@ bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* pro file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); file.close(); - profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) }); + profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) }); return true; } @@ -654,7 +676,7 @@ void PackInstallTask::installConfigs() connect(jobPtr.get(), &NetJob::failed, [this](QString reason) { abortable = false; jobPtr.reset(); - emitFailed(reason); + emitFailed(std::move(reason)); }); connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) { abortable = true; @@ -711,15 +733,17 @@ void PackInstallTask::downloadMods() jarmods.clear(); jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network())); - QList blocked_mods; + QList blockedMods; for (const auto& mod : m_version.mods) { // skip non-client mods - if (!mod.client) + if (!mod.client) { continue; + } // skip optional mods that were not selected - if (mod.optional && !selectedMods.contains(mod.name)) + if (mod.optional && !selectedMods.contains(mod.name)) { continue; + } QString url; switch (mod.download) { @@ -727,7 +751,7 @@ void PackInstallTask::downloadMods() url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url; break; case DownloadType::Browser: { - blocked_mods.append(mod); + blockedMods.append(mod); continue; } case DownloadType::Direct: @@ -763,8 +787,9 @@ void PackInstallTask::downloadMods() jobPtr->addNetAction(dl); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) + if (relpath == Q_NULLPTR) { continue; + } auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName); entry->setStale(true); @@ -798,49 +823,51 @@ void PackInstallTask::downloadMods() modsToCopy[entry->getFullPath()] = path; } } - if (!blocked_mods.isEmpty()) { + if (!blockedMods.isEmpty()) { QList mods; - for (auto mod : blocked_mods) { - BlockedMod blocked_mod; - blocked_mod.name = mod.file; - blocked_mod.websiteUrl = mod.url; - blocked_mod.hash = mod.md5; - blocked_mod.matched = false; - blocked_mod.localPath = ""; + for (const auto& mod : blockedMods) { + BlockedMod blockedMod; + blockedMod.name = mod.file; + blockedMod.websiteUrl = mod.url; + blockedMod.hash = mod.md5; + blockedMod.matched = false; + blockedMod.localPath = ""; - mods.append(blocked_mod); + mods.append(blockedMod); } qWarning() << "Blocked mods found, displaying mod list"; - BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - mods, "md5"); + BlockedModsDialog messageDialog(nullptr, tr("Blocked mods found"), + tr("The following files are not available for download in third party launchers.
" + "You will need to manually download them and add them to the instance."), + mods, "md5"); - message_dialog.setModal(true); + messageDialog.setModal(true); - if (message_dialog.exec()) { + if (messageDialog.exec() != 0) { qDebug() << "Post dialog blocked mods list:" << mods; - for (auto blocked : mods) { + for (const auto& blocked : mods) { if (!blocked.matched) { qDebug() << blocked.name << "was not matched to a local file, skipping copy"; continue; } - auto modIter = std::find_if(blocked_mods.begin(), blocked_mods.end(), - [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); - if (modIter == blocked_mods.end()) + auto modIter = + std::ranges::find_if(blockedMods, [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; }); + if (modIter == blockedMods.end()) { continue; - auto mod = *modIter; + } + const auto& mod = *modIter; if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) { modsToExtract.insert(blocked.localPath, mod); } else if (mod.type == ModType::Decomp) { modsToDecomp.insert(blocked.localPath, mod); } else { auto relpath = getDirForModType(mod.type, mod.type_raw); - if (relpath == Q_NULLPTR) + if (relpath == Q_NULLPTR) { continue; + } auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file); @@ -918,8 +945,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, setStatus(tr("Extracting mods...")); for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) { - auto& modPath = iter.key(); - auto& mod = iter.value(); + const auto& modPath = iter.key(); + const auto& mod = iter.value(); QString extractToDir; if (mod.type == ModType::Extract) { @@ -938,6 +965,10 @@ bool PackInstallTask::extractMods(const QMap& toExtract, folderToExtract = mod.extractFolder; static const QRegularExpression 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; @@ -948,13 +979,18 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) { - auto& modPath = iter.key(); - auto& mod = iter.value(); + const auto& modPath = iter.key(); + const auto& mod = iter.value(); auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw); QDir extractDir(m_stagingPath); 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; if (!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) { qWarning() << "Failed to extract" << mod.decompFile; @@ -963,8 +999,8 @@ bool PackInstallTask::extractMods(const QMap& toExtract, } for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) { - auto& from = iter.key(); - auto& to = iter.value(); + const auto& from = iter.key(); + const auto& to = iter.value(); // If the file already exists, assume the mod is the correct copy - and remove // the copy from the Configs.zip @@ -994,7 +1030,7 @@ void PackInstallTask::install() MinecraftInstance instance(m_globalSettings, std::make_unique(instanceConfigPath), m_stagingPath); { SettingsObject::Lock lock(instance.settings()); - auto components = instance.getPackProfile(); + auto* components = instance.getPackProfile(); components->buildingFromScratch(); // Use a component to add libraries BEFORE Minecraft @@ -1009,20 +1045,23 @@ void PackInstallTask::install() // Loader if (m_version.loader.type == QString("forge")) { auto version = getVersionForLoader("net.minecraftforge"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.minecraftforge", version); } else if (m_version.loader.type == QString("neoforge")) { auto version = getVersionForLoader("net.neoforged"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.neoforged", version); } else if (m_version.loader.type == QString("fabric")) { auto version = getVersionForLoader("net.fabricmc.fabric-loader"); - if (version == Q_NULLPTR) + if (version == Q_NULLPTR) { return; + } components->setComponentVersion("net.fabricmc.fabric-loader", version); } else if (m_version.loader.type != QString()) { @@ -1055,9 +1094,4 @@ void PackInstallTask::install() emitSucceeded(); } -static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version) -{ - return APPLICATION->metadataIndex()->getLoadedVersion(uid, version); -} - } // namespace ATLauncher diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index d1ffdfe7d..e8df132be 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -44,14 +44,13 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "net/NetJob.h" -#include "settings/INISettingsObject.h" -#include +#include #include namespace ATLauncher { -enum class InstallMode { +enum class InstallMode : std::uint8_t { Install, Reinstall, Update, @@ -86,13 +85,13 @@ class PackInstallTask : public InstanceTask { QString packName, QString version, InstallMode installMode = InstallMode::Install); - virtual ~PackInstallTask() { delete m_support; } + ~PackInstallTask() override { delete m_support; } bool canAbort() const override { return true; } bool abort() override; protected: - virtual void executeTask() override; + void executeTask() override; private slots: void onDownloadSucceeded(QByteArray* responsePtr); @@ -103,12 +102,12 @@ class PackInstallTask : public InstanceTask { void onModsExtracted(); private: - QString getDirForModType(ModType type, QString raw); - QString getVersionForLoader(QString uid); - QString detectLibrary(const VersionLibrary& library); + QString getDirForModType(ModType type, const QString& raw); + QString getVersionForLoader(const QString& uid); + static QString detectLibrary(const VersionLibrary& library); - bool createLibrariesComponent(QString instanceRoot, PackProfile* profile); - bool createPackComponent(QString instanceRoot, PackProfile* profile); + bool createLibrariesComponent(const QString& instanceRoot, PackProfile* profile); + bool createPackComponent(const QString& instanceRoot, PackProfile* profile); void deleteExistingFiles(); void installConfigs(); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 607b32cab..e77d53a4b 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -79,6 +79,7 @@ class FlameAPI : public ResourceAPI { case ModPlatform::LegacyFabric: case ModPlatform::Ornithe: case ModPlatform::Rift: + case ModPlatform::None: break; // not supported } return 0; diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 37f0bcb32..577f9967a 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -18,8 +18,6 @@ #include "net/NetJob.h" #include "tasks/Task.h" -static FlameAPI api; - bool FlameCheckUpdate::abort() { bool result = false; @@ -39,7 +37,7 @@ void FlameCheckUpdate::executeTask() { 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::progress, this, &FlameCheckUpdate::setProgress); @@ -48,9 +46,10 @@ void FlameCheckUpdate::executeTask() for (auto* resource : m_resources) { auto project = std::make_shared(); project->addonId = resource->metadata()->project_id.toString(); - auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions }); - if (!versionsUrlOptional.has_value()) + auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions }); + if (!versionsUrlOptional.has_value()) { continue; + } auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value()); @@ -63,11 +62,11 @@ void FlameCheckUpdate::executeTask() void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response) { - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from latest mod version at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from latest mod version at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } @@ -88,100 +87,104 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* qCritical() << e.what(); qDebug() << doc; } - auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); + auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty()); setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name())); - if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) { + if (!latestVer.has_value() || !latestVer->addonId.isValid()) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); return; } - if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) { - m_blocked[resource] = latest_ver->fileId.toString(); + if (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) { + m_blocked[resource] = latestVer->fileId.toString(); return; } - if (!latest_ver->hash.isEmpty() && - (resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + if (!latestVer->hash.isEmpty() && + (resource->metadata()->hash != latestVer->hash || resource->status() == ResourceStatus::NotInstalled)) { + auto oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NotInstalled) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - auto download_task = makeShared(pack, latest_ver.value(), m_resourceModel); - m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type, - api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()), - ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled()); + auto downloadTask = makeShared(pack, latestVer.value(), m_resourceModel, true, "update"); + m_updates.emplace_back(pack->name, resource->metadata()->hash, oldVersion, latestVer->version, latestVer->version_type, + FlameAPI().getModFileChangelog(latestVer->addonId.toInt(), latestVer->fileId.toInt()), + ModPlatform::ResourceProvider::FLAME, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, latest_ver.value())); + m_deps.append(std::make_shared(pack, latestVer.value())); } void FlameCheckUpdate::collectBlockedMods() { QStringList addonIds; QHash quickSearch; - for (auto const& resource : m_blocked.keys()) { + for (const auto& resource : m_blocked.keys()) { auto addonId = resource->metadata()->project_id.toString(); addonIds.append(addonId); quickSearch[addonId] = resource; } Task::Ptr projTask; - QByteArray* response; + QByteArray* response = nullptr; if (addonIds.isEmpty()) { emitSucceeded(); return; - } else if (addonIds.size() == 1) { - std::tie(projTask, response) = api.getProject(*addonIds.begin()); + } + if (addonIds.size() == 1) { + std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin()); } else { - std::tie(projTask, response) = api.getProjects(addonIds); + std::tie(projTask, response) = FlameAPI().getProjects(addonIds); } connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] { - QJsonParseError parse_error{}; - auto doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from Flame projects task at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + auto doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame projects task at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; return; } try { QJsonArray entries; - if (addonIds.size() == 1) + if (addonIds.size() == 1) { entries = { Json::requireObject(Json::requireObject(doc), "data") }; - else + } else { entries = Json::requireArray(Json::requireObject(doc), "data"); + } for (auto entry : entries) { - auto entry_obj = Json::requireObject(entry); + auto entryObj = Json::requireObject(entry); - auto id = QString::number(Json::requireInteger(entry_obj, "id")); + auto id = QString::number(Json::requireInteger(entryObj, "id")); - auto resource = quickSearch.find(id).value(); + auto* resource = quickSearch.find(id).value(); ModPlatform::IndexedPack pack; try { setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name())); - FlameMod::loadIndexedPack(pack, entry_obj); - auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]); + FlameMod::loadIndexedPack(pack, entryObj); + auto recoverUrl = 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."), - recover_url); + recoverUrl); } catch (Json::JsonException& e) { qDebug() << e.cause(); qDebug() << entries; diff --git a/launcher/modplatform/helpers/ExportToModList.h b/launcher/modplatform/helpers/ExportToModList.h index ab7797fe6..7cbe730f2 100644 --- a/launcher/modplatform/helpers/ExportToModList.h +++ b/launcher/modplatform/helpers/ExportToModList.h @@ -23,7 +23,9 @@ namespace ExportToModList { enum Formats { HTML, MARKDOWN, PLAINTXT, JSON, CSV, CUSTOM }; -enum OptionalData { Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; +enum OptionalDataValue { None = 0, Authors = 1 << 0, Url = 1 << 1, Version = 1 << 2, FileName = 1 << 3 }; +Q_DECLARE_FLAGS(OptionalData, OptionalDataValue) + QString exportToModList(QList mods, Formats format, OptionalData extraData); QString exportToModList(QList mods, QString lineTemplate); } // namespace ExportToModList diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp index 878ef26fa..659c5d2ed 100644 --- a/launcher/modplatform/import_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp @@ -63,12 +63,12 @@ void PackInstallTask::copySettings() instance.settings()->set("JvmArgs", m_pack.jvmArgs.toString()); } - auto components = instance.getPackProfile(); + auto* components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_pack.mcVersion, true); auto modloader = m_pack.loaderType; - if (modloader.has_value()) + if (modloader.has_value()) { switch (modloader.value()) { case ModPlatform::NeoForge: { components->setComponentVersion("net.neoforged", m_pack.loaderVersion, true); @@ -86,28 +86,16 @@ void PackInstallTask::copySettings() components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true); break; } - 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: + default: break; } + } components->saveNow(); instance.setName(name()); - if (m_instIcon == "default") + if (m_instIcon == "default") { m_instIcon = "ftb_logo"; + } instance.setIconKey(m_instIcon); } emitSucceeded(); diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index bd5a50d1b..0fda37e57 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -53,7 +53,7 @@ void ModrinthCheckUpdate::executeTask() setStatus(tr("Preparing resources for Modrinth...")); setProgress(0, ((m_loadersList.isEmpty() ? 1 : m_loadersList.length()) * 2) + 1); - auto hashing_task = + auto hashingTask = makeShared("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()); bool startHasing = false; for (auto* resource : m_resources) { @@ -63,10 +63,11 @@ void ModrinthCheckUpdate::executeTask() // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) if (resource->metadata()->hash_format != m_hashType) { - auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); - connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); }); - connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); - hashing_task->addTask(hash_task); + auto hashTask = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH); + connect(hashTask.get(), &Hashing::Hasher::resultsReady, + [this, resource](const QString& hash) { m_mappings.insert(hash, resource); }); + connect(hashTask.get(), &Task::failed, [this] { failed("Failed to generate hash"); }); + hashingTask->addTask(hashTask); startHasing = true; } else { m_mappings.insert(hash, resource); @@ -74,9 +75,9 @@ void ModrinthCheckUpdate::executeTask() } if (startHasing) { - connect(hashing_task.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); - m_job = hashing_task; - hashing_task->start(); + connect(hashingTask.get(), &Task::finished, this, &ModrinthCheckUpdate::checkNextLoader); + m_job = hashingTask; + hashingTask->start(); } else { checkNextLoader(); } @@ -120,14 +121,14 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio setStatus(tr("Parsing the API response from Modrinth...")); setProgress(m_progress + 1, m_progressTotal); - QJsonParseError parse_error{}; - QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); - if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parse_error.offset - << "reason:" << parse_error.errorString(); + QJsonParseError parseError{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModrinthCheckUpdate at" << parseError.offset + << "reason:" << parseError.errorString(); qWarning() << *response; - emitFailed(parse_error.errorString()); + emitFailed(parseError.errorString()); return; } @@ -138,11 +139,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio const QString hash = iter.key(); Resource* resource = iter.value(); - auto project_obj = doc[hash].toObject(); + auto projectObj = doc[hash].toObject(); // If the returned project is empty, but we have Modrinth metadata, // it means this specific version is not available - if (project_obj.isEmpty()) { + if (projectObj.isEmpty()) { qDebug() << "Mod" << m_mappings.find(hash).value()->name() << "got an empty response. Hash:" << hash; ++iter; continue; @@ -150,11 +151,11 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio // Sometimes a version may have multiple files, one with "forge" and one with "fabric", // so we may want to filter it - QString loader_filter; + QString loaderFilter; if (loader.has_value() && loader != 0) { auto modLoaders = ModPlatform::modLoaderTypesToList(*loader); if (!modLoaders.isEmpty()) { - loader_filter = ModPlatform::getModLoaderAsString(modLoaders.first()); + loaderFilter = ModPlatform::getModLoaderAsString(modLoaders.first()); } } @@ -164,9 +165,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) // Such is the pain of having arbitrary files for a given version .-. - auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hashType, loader_filter); - if (project_ver.downloadUrl.isEmpty()) { - qCritical() << "Modrinth mod without download url!" << project_ver.fileName; + auto projectVer = Modrinth::loadIndexedPackVersion(projectObj, m_hashType, loaderFilter); + if (projectVer.downloadUrl.isEmpty()) { + qCritical() << "Modrinth mod without download url!" << projectVer.fileName; ++iter; continue; } @@ -177,21 +178,22 @@ void ModrinthCheckUpdate::checkVersionsResponse(QByteArray* response, std::optio pack->slug = resource->metadata()->slug; pack->addonId = resource->metadata()->project_id; pack->provider = ModPlatform::ResourceProvider::MODRINTH; - if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) { - auto download_task = makeShared(pack, project_ver, m_resourceModel); + if ((projectVer.hash != hash && projectVer.is_preferred) || (resource->status() == ResourceStatus::NotInstalled)) { + auto downloadTask = makeShared(pack, projectVer, m_resourceModel, true, "update"); - QString old_version = resource->metadata()->version_number; - if (old_version.isEmpty()) { - if (resource->status() == ResourceStatus::NOT_INSTALLED) - old_version = tr("Not installed"); - else - old_version = tr("Unknown"); + QString oldVersion = resource->metadata()->version_number; + if (oldVersion.isEmpty()) { + if (resource->status() == ResourceStatus::NotInstalled) { + oldVersion = tr("Not installed"); + } else { + oldVersion = tr("Unknown"); + } } - m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type, - project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled()); + m_updates.emplace_back(pack->name, hash, oldVersion, projectVer.version_number, projectVer.version_type, + projectVer.changelog, ModPlatform::ResourceProvider::MODRINTH, downloadTask, resource->enabled()); } - m_deps.append(std::make_shared(pack, project_ver)); + m_deps.append(std::make_shared(pack, projectVer)); iter = m_mappings.erase(iter); } @@ -211,20 +213,22 @@ void ModrinthCheckUpdate::checkNextLoader() if (m_loaderIdx < m_loadersList.size()) { // this are mods so check with loades getUpdateModsForLoader(m_loadersList.at(m_loaderIdx), m_loaderIdx > m_initialSize); 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(); return; } - for (auto resource : m_mappings) { + for (auto* resource : m_mappings) { QString reason; - if (dynamic_cast(resource) != nullptr) + if (dynamic_cast(resource) != nullptr) { reason = tr("No valid version found for this resource. It's probably unavailable for the current game " "version / mod loader."); - else + } else { reason = tr("No valid version found for this resource. It's probably unavailable for the current game version."); + } emit checkFailed(resource, reason); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f308c88bc..0cb2c547d 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -16,7 +16,10 @@ #include "net/ChecksumValidator.h" #include "net/ApiDownload.h" +#include "net/ApiHeaderProxy.h" #include "net/NetJob.h" + +#include "modplatform/ModIndex.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" @@ -29,114 +32,119 @@ bool ModrinthCreationTask::abort() { - if (!canAbort()) + if (!canAbort()) { return false; + } - if (m_task) + if (m_task) { m_task->abort(); + } return InstanceCreationTask::abort(); } bool ModrinthCreationTask::updateInstance() { - auto instance_list = APPLICATION->instances(); + auto* instanceList = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - BaseInstance* inst; - if (auto original_id = originalInstanceID(); !original_id.isEmpty()) { - inst = instance_list->getInstanceById(original_id); + BaseInstance* inst = nullptr; + if (auto originalId = originalInstanceID(); !originalId.isEmpty()) { + inst = instanceList->getInstanceById(originalId); Q_ASSERT(inst); } else { - inst = instance_list->getInstanceByManagedName(originalName()); + inst = instanceList->getInstanceByManagedName(originalName()); if (!inst) { - inst = instance_list->getInstanceById(originalName()); + inst = instanceList->getInstanceById(originalName()); - if (!inst) + if (!inst) { return false; + } } } - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(index_path, m_files, true, false)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(indexPath, m_files, true, false)) { return false; + } - auto version_name = inst->getManagedPackVersionName(); + auto versionName = inst->getManagedPackVersionName(); m_root_path = QFileInfo(inst->gameRoot()).fileName(); - auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; + auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : ""; if (shouldConfirmUpdate()) { - auto should_update = askIfShouldUpdate(m_parent, version_str); - if (should_update == ShouldUpdate::SkipUpdating) + auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr); + if (shouldUpdate == ShouldUpdate::SkipUpdating) { return false; - if (should_update == ShouldUpdate::Cancel) { + } + if (shouldUpdate == ShouldUpdate::Cancel) { m_abort = true; return false; } } // Remove repeated files, we don't need to download them! - QDir old_inst_dir(inst->instanceRoot()); + QDir oldInstDir(inst->instanceRoot()); - QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); + QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack")); - QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); - QFileInfo old_index_file(old_index_path); - if (old_index_file.exists()) { - std::vector old_files; - parseManifest(old_index_path, old_files, false, false); + QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json")); + QFileInfo oldIndexFile(oldIndexPath); + if (oldIndexFile.exists()) { + std::vector oldFiles; + parseManifest(oldIndexPath, oldFiles, false, false); // Let's remove all duplicated, identical resources! - auto files_iterator = m_files.begin(); + auto filesIterator = m_files.begin(); begin: - while (files_iterator != m_files.end()) { - auto const& file = *files_iterator; + while (filesIterator != m_files.end()) { + const auto& file = *filesIterator; - auto old_files_iterator = old_files.begin(); - while (old_files_iterator != old_files.end()) { - auto const& old_file = *old_files_iterator; + auto oldFilesIterator = oldFiles.begin(); + while (oldFilesIterator != oldFiles.end()) { + const auto& oldFile = *oldFilesIterator; - if (old_file.hash == file.hash) { + if (oldFile.hash == file.hash) { qDebug() << "Removed file at" << file.path << "from list of downloads"; - files_iterator = m_files.erase(files_iterator); - old_files_iterator = old_files.erase(old_files_iterator); + filesIterator = m_files.erase(filesIterator); + oldFilesIterator = oldFiles.erase(oldFilesIterator); goto begin; // Sorry :c } - old_files_iterator++; + oldFilesIterator++; } - files_iterator++; + filesIterator++; } - QDir old_minecraft_dir(inst->gameRoot()); + QDir oldMinecraftDir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! - if (!old_files.empty()) { - for (auto const& file : old_files) { - scheduleToDelete(m_parent, old_minecraft_dir, file.path, true); + if (!oldFiles.empty()) { + for (const auto& file : oldFiles) { + scheduleToDelete(m_parent, oldMinecraftDir, file.path, true); } } // 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? // FIXME: We may want to do something about disabled mods. - auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (const auto& entry : old_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldOverrides = Override::readOverrides("overrides", oldIndexFolder); + for (const auto& entry : oldOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } - auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (const auto& entry : old_client_overrides) { - scheduleToDelete(m_parent, old_minecraft_dir, entry); + auto oldClientOverrides = Override::readOverrides("client-overrides", oldIndexFolder); + for (const auto& entry : oldClientOverrides) { + scheduleToDelete(m_parent, oldMinecraftDir, entry); } } else { // We don't have an old index file, so we may duplicate stuff! - 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 " - "of the files to be duplicated. Do you want to continue?"), - QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + 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 " + "of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); if (dialog->exec() == QDialog::DialogCode::Rejected) { m_abort = true; @@ -158,39 +166,40 @@ std::unique_ptr ModrinthCreationTask::createInstance() { QEventLoop loop; - QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); + QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack")); - QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) { return nullptr; + } // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); - FS::ensureFilePathExists(new_index_place); - FS::move(index_path, new_index_place); + QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json")); + FS::ensureFilePathExists(newIndexPlace); + FS::move(indexPath, newIndexPlace); auto mcPath = FS::PathCombine(m_stagingPath, m_root_path); - auto override_path = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(override_path)) { + auto overridePath = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(overridePath)) { // Create a list of overrides in "overrides.txt" inside mrpack/ - Override::createOverrides("overrides", parent_folder, override_path); + Override::createOverrides("overrides", parentFolder, overridePath); // Apply the overrides - if (!FS::move(override_path, mcPath)) { + if (!FS::move(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return nullptr; } } // Do client overrides - auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(client_override_path)) { + auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(clientOverridePath)) { // Create a list of overrides in "client-overrides.txt" inside mrpack/ - Override::createOverrides("client-overrides", parent_folder, client_override_path); + Override::createOverrides("client-overrides", parentFolder, clientOverridePath); // Apply the overrides - if (!FS::overrideFolder(mcPath, client_override_path)) { + if (!FS::overrideFolder(mcPath, clientOverridePath)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); return nullptr; } @@ -200,18 +209,27 @@ std::unique_ptr ModrinthCreationTask::createInstance() auto instanceSettings = std::make_unique(configPath); auto instance = std::make_unique(m_globalSettings, std::move(instanceSettings), m_stagingPath); - auto components = instance->getPackProfile(); + auto* components = instance->getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", m_minecraft_version, true); - if (!m_fabric_version.isEmpty()) + QString loader; + if (!m_fabric_version.isEmpty()) { components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version); - if (!m_quilt_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric); + } + if (!m_quilt_version.isEmpty()) { components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); - if (!m_forge_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt); + } + if (!m_forge_version.isEmpty()) { components->setComponentVersion("net.minecraftforge", m_forge_version); - if (!m_neoForge_version.isEmpty()) + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge); + } + if (!m_neoForge_version.isEmpty()) { components->setComponentVersion("net.neoforged", m_neoForge_version); + loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge); + } if (m_instIcon != "default") { instance->setIconKey(m_instIcon); @@ -220,34 +238,35 @@ std::unique_ptr ModrinthCreationTask::createInstance() } // 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()); - else + } else { instance->setManagedPack("modrinth", "", name(), "", ""); + } instance->setName(name()); instance->saveNow(); auto downloadMods = makeShared(tr("Mod Download Modrinth"), APPLICATION->network()); - auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path); - auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path); + auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path); + auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath); // TODO make this work with other sorts of resource QHash resources; for (auto& file : m_files) { auto fileName = file.path; fileName = FS::RemoveInvalidPathChars(fileName); - auto file_path = FS::PathCombine(root_modpack_path, fileName); - if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) { + auto filePath = FS::PathCombine(rootModpackPath, fileName); + if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) { // 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.") .arg(fileName)); return nullptr; } if (fileName.startsWith("mods/")) { - auto mod = new Mod(file_path); + auto* mod = new Mod(filePath); ModDetails d; - d.mod_id = file_path; + d.mod_id = filePath; mod->setDetails(d); resources[file.hash.toHex()] = mod; } @@ -255,29 +274,39 @@ std::unique_ptr ModrinthCreationTask::createInstance() setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName)); return nullptr; } - qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path; - auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + qDebug() << "Will try to download" << file.downloads.front() << "to" << filePath; + + 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)); downloadMods->addNetAction(dl); if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) auto param = dl.toWeakRef(); - connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] { - auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path); + connect(dl.get(), &Task::failed, [&file, filePath, param, downloadMods, meta] { + QUrl fallbackUrl = file.downloads.dequeue(); + auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta); ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); downloadMods->addNetAction(ndl); - if (auto shared = param.lock()) + if (auto shared = param.lock()) { shared->succeeded(); + } }); } } - bool ended_well = false; + bool endedWell = false; - connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; }); - connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) { - ended_well = false; + connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; }); + connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) { + endedWell = false; setError(reason); }); connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit); @@ -293,8 +322,8 @@ std::unique_ptr ModrinthCreationTask::createInstance() loop.exec(); - if (!ended_well) { - for (auto resource : resources) { + if (!endedWell) { + for (auto* resource : resources) { delete resource; } return nullptr; @@ -303,7 +332,7 @@ std::unique_ptr ModrinthCreationTask::createInstance() QEventLoop ensureMetaLoop; QDir folder = FS::PathCombine(instance->modsRoot(), ".index"); auto ensureMetadataTask = makeShared(resources, folder, ModPlatform::ResourceProvider::MODRINTH); - connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; }); + connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; }); connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit); connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) { setDetails(tr("%1 out of %2 complete").arg(current).arg(total)); @@ -315,40 +344,38 @@ std::unique_ptr ModrinthCreationTask::createInstance() m_task = ensureMetadataTask; ensureMetaLoop.exec(); - for (auto resource : resources) { + for (auto* resource : resources) { delete resource; } resources.clear(); // Update information of the already installed instance, if any. - if (m_instance && ended_well) { + if (m_instance && endedWell) { 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 // is preserved, but if we're using the original one, we update the version string. // NOTE: This needs to come before the copyManagedPack call! 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->copyManagedPack(*instance); } - if (ended_well) { + if (endedWell) { return instance; } return nullptr; } -bool ModrinthCreationTask::parseManifest(const QString& index_path, - std::vector& files, - bool set_internal_data, - bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector& files, bool setInternalData, bool showOptionalDialog) { try { - auto doc = Json::requireDocument(index_path); + auto doc = Json::requireDocument(indexPath); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -357,9 +384,10 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, throw JSONValidationError("Unknown game: " + game); } - if (set_internal_data) { - if (m_managed_version_id.isEmpty()) + if (setInternalData) { + if (m_managed_version_id.isEmpty()) { m_managed_version_id = obj["versionId"].toString(); + } m_managed_name = obj["name"].toString(); } @@ -375,7 +403,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, QString support = env["client"].toString("unsupported"); if (support == "unsupported") { continue; - } else if (support == "optional") { + } + if (support == "optional") { file.required = false; } } @@ -387,20 +416,21 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode // (as Modrinth seems to incorrectly handle spaces) - auto download_arr = modInfo["downloads"].toArray(); - for (auto download : download_arr) { + auto downloadArr = modInfo["downloads"].toArray(); + for (auto download : downloadArr) { qWarning() << download.toString(); - bool is_last = download.toString() == download_arr.last().toString(); + bool isLast = download.toString() == downloadArr.last().toString(); - auto download_url = QUrl(download.toString()); + auto downloadUrl = QUrl(download.toString()); - if (!download_url.isValid()) { + if (!downloadUrl.isValid()) { qDebug() - << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path); - if (is_last && file.downloads.isEmpty()) + << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path); + if (isLast && file.downloads.isEmpty()) { throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } } else { - file.downloads.push_back(download_url); + file.downloads.push_back(downloadUrl); } } @@ -408,10 +438,11 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } if (!optionalFiles.empty()) { - if (show_optional_dialog) { + if (showOptionalDialog) { QStringList oFiles; - for (auto file : optionalFiles) + for (const auto& file : optionalFiles) { oFiles.push_back(file.path); + } OptionalModDialog optionalModDialog(m_parent, oFiles); if (optionalModDialog.exec() == QDialog::Rejected) { emitAborted(); @@ -434,7 +465,7 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path, } } } - if (set_internal_data) { + if (setInternalData) { auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { QString name = it.key(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 01cc8755a..c8835e142 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -24,18 +24,18 @@ class ModrinthCreationTask final : public InstanceCreationTask { }; public: - ModrinthCreationTask(QString staging_path, - SettingsObject* global_settings, + ModrinthCreationTask(const QString& stagingPath, + SettingsObject* globalSettings, QWidget* parent, QString id, - QString version_id = {}, - QString original_instance_id = {}) - : InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id)) + QString versionId = {}, + QString originalInstanceId = {}) + : m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(versionId)) { - setStagingPath(staging_path); - setParentSettings(global_settings); + setStagingPath(stagingPath); + setParentSettings(globalSettings); - m_original_instance_id = std::move(original_instance_id); + m_original_instance_id = std::move(originalInstanceId); } bool abort() override; @@ -44,7 +44,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::unique_ptr createInstance() override; private: - bool parseManifest(const QString&, std::vector&, bool set_internal_data = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool setInternalData = true, bool showOptionalDialog = true); private: QWidget* m_parent = nullptr; diff --git a/launcher/net/ApiDownload.cpp b/launcher/net/ApiDownload.cpp index 9a5a44104..54d3e746e 100644 --- a/launcher/net/ApiDownload.cpp +++ b/launcher/net/ApiDownload.cpp @@ -18,28 +18,30 @@ */ #include "net/ApiDownload.h" + +#include #include "net/ApiHeaderProxy.h" namespace Net { Download::Ptr ApiDownload::makeCached(QUrl url, MetaEntryPtr entry, Download::Options options) { - auto dl = Download::makeCached(url, entry, options); + auto dl = Download::makeCached(std::move(url), std::move(entry), options); dl->addHeaderProxy(std::make_unique()); return dl; } std::pair ApiDownload::makeByteArray(QUrl url, Download::Options options) { - auto [dl, response] = Download::makeByteArray(url, options); + auto [dl, response] = Download::makeByteArray(std::move(url), options); dl->addHeaderProxy(std::make_unique()); return { dl, response }; } -Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options) +Download::Ptr ApiDownload::makeFile(QUrl url, QString path, Download::Options options, ModrinthDownloadMeta meta) { - auto dl = Download::makeFile(url, path, options); - dl->addHeaderProxy(std::make_unique()); + auto dl = Download::makeFile(std::move(url), std::move(path), options); + dl->addHeaderProxy(std::make_unique(std::move(meta))); return dl; } diff --git a/launcher/net/ApiDownload.h b/launcher/net/ApiDownload.h index 01a31eb17..033dd0e12 100644 --- a/launcher/net/ApiDownload.h +++ b/launcher/net/ApiDownload.h @@ -20,13 +20,17 @@ #pragma once #include "Download.h" +#include "net/ApiHeaderProxy.h" namespace Net { namespace ApiDownload { Download::Ptr makeCached(QUrl url, MetaEntryPtr entry, Download::Options options = Download::Option::NoOptions); std::pair makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions); -Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions); +Download::Ptr makeFile(QUrl url, + QString path, + Download::Options options = Download::Option::NoOptions, + ModrinthDownloadMeta meta = ModrinthDownloadMeta()); }; // namespace ApiDownload } // namespace Net diff --git a/launcher/net/ApiHeaderProxy.h b/launcher/net/ApiHeaderProxy.h index 789a6fada..d12287a4e 100644 --- a/launcher/net/ApiHeaderProxy.h +++ b/launcher/net/ApiHeaderProxy.h @@ -23,27 +23,64 @@ #include "BuildConfig.h" #include "net/HeaderProxy.h" +#include +#include + 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 { public: - ApiHeaderProxy() : HeaderProxy() {} - virtual ~ApiHeaderProxy() = default; + ApiHeaderProxy() = default; + explicit ApiHeaderProxy(ModrinthDownloadMeta meta) : m_meta(std::move(meta)) {} + ~ApiHeaderProxy() override = default; public: - virtual QList headers(const QNetworkRequest& request) const override + QList headers(const QNetworkRequest& request) const override { QList hdrs; - if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host() == QUrl(BuildConfig.FLAME_BASE_URL).host()) { - hdrs.append({ "x-api-key", APPLICATION->getFlameAPIKey().toUtf8() }); - } else if (request.url().host() == QUrl(BuildConfig.MODRINTH_PROD_URL).host() || - request.url().host() == QUrl(BuildConfig.MODRINTH_STAGING_URL).host()) { + const auto host = request.url().host(); + + if (APPLICATION->capabilities() & Application::SupportsFlame && + (host == QUrl(BuildConfig.FLAME_BASE_URL).host() || host == BuildConfig.FLAME_DOWNLOAD_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(); - if (!token.isNull()) - hdrs.append({ "Authorization", token.toUtf8() }); + if (!token.isNull()) { + hdrs.append({ .headerName = "Authorization", .headerValue = token.toUtf8() }); + } + } + + if (host == BuildConfig.MODRINTH_DOWNLOAD_HOST && !m_meta.isEmpty()) { + hdrs.append({ .headerName = "modrinth-download-meta", .headerValue = m_meta.toJson() }); } return hdrs; }; + + private: + ModrinthDownloadMeta m_meta; }; } // namespace Net diff --git a/launcher/net/ChecksumValidator.h b/launcher/net/ChecksumValidator.h index 7663d5d12..c7906cc13 100644 --- a/launcher/net/ChecksumValidator.h +++ b/launcher/net/ChecksumValidator.h @@ -38,7 +38,6 @@ #include "Validator.h" #include -#include namespace Net { class ChecksumValidator : public Validator { @@ -69,10 +68,10 @@ class ChecksumValidator : public Validator { return true; } - auto validate(QNetworkReply&) -> bool override + auto validate(QNetworkReply& reply) -> bool override { - if (m_expected.size() && m_expected != hash()) { - qWarning() << "Checksum mismatch, download is bad."; + if (!m_expected.isEmpty() && m_expected != hash()) { + qWarning() << "Checksum mismatch for URL:" << reply.url().toString() << "expected:" << m_expected << "got:" << hash(); return false; } return true; diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 6b81bfe0d..5c1e47dfd 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -182,7 +182,7 @@ auto HttpMetaCache::evictAll() -> bool } map.entry_list.clear(); // AND all return codes together so the result is true iff all runs of deletePath() are true - ret &= FS::deletePath(map.base_path); + ret &= FS::deleteContents(map.base_path); } return ret; } diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index dae149ab3..3dd1c09cf 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -69,11 +69,15 @@ void NetJob::executeNextSubTask() // 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) { m_try += 1; - while (!m_failed.isEmpty()) { - auto task = m_failed.take(*m_failed.keyBegin()); - m_done.remove(task.get()); - m_queue.enqueue(task); - } + m_failed.removeIf([this](QHash::iterator task) { + // there is no point in retying on 404 Not Found + if (static_cast(task->get())->replyStatusCode() == 404) { + return false; + } + m_done.remove(task->get()); + m_queue.enqueue(*task); + return true; + }); } ConcurrentTask::executeNextSubTask(); } @@ -100,13 +104,18 @@ auto NetJob::canAbort() const -> bool auto NetJob::abort() -> bool { - bool fullyAborted = true; - // fail all downloads on the queue for (auto task : m_queue) m_failed.insert(task.get(), task); m_queue.clear(); + if (m_doing.isEmpty()) { + // no downloads to abort, NetJob is not running + return true; + } + + bool fullyAborted = true; + // abort active downloads auto toKill = m_doing.values(); for (auto part : toKill) { diff --git a/launcher/net/NetUtils.h b/launcher/net/NetUtils.h index cd517bcca..67ebc9685 100644 --- a/launcher/net/NetUtils.h +++ b/launcher/net/NetUtils.h @@ -40,4 +40,18 @@ inline bool isApplicationError(QNetworkReply::NetworkError x) QNetworkReply::UnknownContentError }; 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 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 diff --git a/launcher/resources/multimc/16x16/new.png b/launcher/resources/multimc/16x16/new.png deleted file mode 100644 index dfde06f61..000000000 Binary files a/launcher/resources/multimc/16x16/new.png and /dev/null differ diff --git a/launcher/screenshots/ImgurUpload.cpp b/launcher/screenshots/ImgurUpload.cpp index e6ba372ed..dd41fc340 100644 --- a/launcher/screenshots/ImgurUpload.cpp +++ b/launcher/screenshots/ImgurUpload.cpp @@ -118,7 +118,7 @@ auto ImgurUpload::Sink::finalize(QNetworkReply&) -> Task::State Net::NetRequest::Ptr ImgurUpload::make(ScreenShot::Ptr m_shot) { auto up = makeShared(m_shot->m_file); - up->m_url = std::move(BuildConfig.IMGUR_BASE_URL + "image"); + up->m_url = BuildConfig.IMGUR_BASE_URL + "image"; up->m_sink.reset(new Sink(m_shot)); up->addHeaderProxy(std::make_unique(QList{ { "Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toUtf8() }, { "Accept", "application/json" } })); diff --git a/launcher/settings/INIFile.cpp b/launcher/settings/INIFile.cpp index 75e888938..a1d1c01d9 100644 --- a/launcher/settings/INIFile.cpp +++ b/launcher/settings/INIFile.cpp @@ -35,6 +35,8 @@ */ #include "settings/INIFile.h" + +#include #include #include @@ -62,11 +64,10 @@ bool INIFile::saveFile(QString fileName) _settings_obj.sync(); 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) - qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; + qCritical() << "An access error occurred while saving INI file" << fileName << "(is the file read-only?)"; + if (ASSERT_NEVER(status == QSettings::Status::FormatError)) + qCritical() << "A format error occurred while saving INI file" << fileName << "(this shouldn't be possible!)"; return false; } @@ -178,9 +179,9 @@ bool INIFile::loadFile(QString fileName) if (auto status = _settings_obj.status(); status != QSettings::Status::NoError) { if (status == QSettings::Status::AccessError) - qCritical() << "An access error occurred (e.g. trying to write to a read-only file)."; + qCritical() << "An access error occurred while loading INI file" << fileName; if (status == QSettings::Status::FormatError) - qCritical() << "A format error occurred (e.g. loading a malformed INI file)."; + qCritical() << "A format error occurred while loading INI file" << fileName << "(is the file malformed or corrupted?)"; return false; } if (!_settings_obj.value("ConfigVersion").isValid()) { diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index a54b4e7c2..5857a0663 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -48,6 +48,13 @@ Task::Task(bool show_debug) : m_show_debug(show_debug) setAutoDelete(false); } +Task::~Task() +{ + if (isRunning()) { + qCWarning(taskLogC) << "Task" << describe() << "disposed while running!"; + } +} + void Task::setStatus(const QString& new_status) { if (m_status != new_status) { diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 94fb57783..38c09da90 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable { public: explicit Task(bool show_debug_log = true); - virtual ~Task() = default; + ~Task() override; bool isRunning() const; bool isFinished() const; @@ -165,7 +165,7 @@ class Task : public QObject, public QRunnable { //! used by external code to ask the task to abort virtual bool abort() { - if (canAbort()) + if (canAbort() && isRunning()) emitAborted(); return canAbort(); } diff --git a/launcher/translations/POTranslator.cpp b/launcher/translations/POTranslator.cpp index 458ebd230..0bd3638e6 100644 --- a/launcher/translations/POTranslator.cpp +++ b/launcher/translations/POTranslator.cpp @@ -183,8 +183,7 @@ void POTranslatorPrivate::reload() nextFuzzy = true; } } else if (line.startsWith('"')) { - QByteArray temp; - QByteArray* out = &temp; + QByteArray* out = nullptr; switch (mode) { case Mode::First: diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index dea7241b2..7f905608b 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -36,13 +36,9 @@ #include "TranslationsModel.h" -#include #include -#include -#include -#include -#include -#include +#include +#include #include "BuildConfig.h" #include "FileSystem.h" @@ -55,18 +51,26 @@ #include "Application.h" #include "settings/SettingsObject.h" -const static QLatin1String defaultLangCode("en_US"); +static constexpr QLatin1String g_defaultLangCode("en_US"); -enum class FileType { NONE, QM, PO }; +namespace { +enum class FileType : std::uint8_t { None, Qm, Po }; + +QString getSystemLocaleName() +{ + return QLocale::system().name(); +} + +QString getSystemLanguage() +{ + return getSystemLocaleName().split('_').front(); +} +} // namespace struct Language { - Language() { updated = true; } - Language(const QString& _key) - { - key = _key; - locale = QLocale(key); - updated = (key == defaultLangCode); - } + Language() : updated(true) {} + + explicit Language(QString _key) : key(std::move(_key)), updated(key == g_defaultLangCode) { locale = QLocale(key); } QString languageName() const { @@ -98,12 +102,12 @@ struct Language { float percentTranslated() const { if (total == 0) { - return 100.0f; + return 100.0F; } - return 100.0f * float(translated) / float(total); + return 100.0F * static_cast(translated) / static_cast(total); } - void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy) + void setTranslationStats(const unsigned _translated, const unsigned _untranslated, const unsigned _fuzzy) { translated = _translated; untranslated = _untranslated; @@ -115,18 +119,18 @@ struct Language { bool isIdenticalTo(const Language& other) const { - return (key == other.key && file_name == other.file_name && file_size == other.file_size && file_sha1 == other.file_sha1 && + return (key == other.key && fileName == other.fileName && fileSize == other.fileSize && fileSha1 == other.fileSha1 && translated == other.translated && fuzzy == other.fuzzy && total == other.fuzzy && localFileType == other.localFileType); } - Language& apply(Language& other) + Language& apply(const Language& other) { if (!isOfSameNameAs(other)) { return *this; } - file_name = other.file_name; - file_size = other.file_size; - file_sha1 = other.file_sha1; + fileName = other.fileName; + fileSize = other.fileSize; + fileSha1 = other.fileSha1; translated = other.translated; fuzzy = other.fuzzy; total = other.total; @@ -138,47 +142,44 @@ struct Language { QLocale locale; bool updated; - QString file_name = QString(); - std::size_t file_size = 0; - QString file_sha1 = QString(); + QString fileName = QString(); + std::size_t fileSize = 0; + QString fileSha1 = QString(); unsigned translated = 0; unsigned untranslated = 0; unsigned fuzzy = 0; unsigned total = 0; - FileType localFileType = FileType::NONE; + FileType localFileType = FileType::None; }; struct TranslationsModel::Private { QDir m_dir; // initial state is just english - QList m_languages = { Language(defaultLangCode) }; + QList m_languages = { Language(g_defaultLangCode) }; - QString m_selectedLanguage = defaultLangCode; - std::unique_ptr m_qt_translator; - std::unique_ptr m_app_translator; + QString m_selectedLanguage = g_defaultLangCode; + std::unique_ptr m_qtTranslator; + std::unique_ptr m_appTranslator; - Net::Download* m_index_task; + Net::Download* m_indexTask = nullptr; QString m_downloadingTranslation; - NetJob::Ptr m_dl_job; - NetJob::Ptr m_index_job; + NetJob::Ptr m_downloadJob; + NetJob::Ptr m_indexJob; QString m_nextDownload; - std::unique_ptr m_po_translator; - QFileSystemWatcher* watcher; + QFileSystemWatcher* watcher = nullptr; - const QString m_system_locale = QLocale::system().name(); - const QString m_system_language = m_system_locale.split('_').front(); - - bool no_language_set = false; + bool m_noLanguageSet = false; }; -TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractListModel(parent) +TranslationsModel::TranslationsModel(const QString& path, QObject* parent) : QAbstractListModel(parent) { - d.reset(new Private); + d = std::make_unique(); d->m_dir.setPath(path); + d->m_selectedLanguage = APPLICATION->settings()->get("Language").toString(); FS::ensureFolderPathExists(path); reloadLocalFiles(); @@ -187,12 +188,12 @@ TranslationsModel::TranslationsModel(QString path, QObject* parent) : QAbstractL d->watcher->addPath(d->m_dir.canonicalPath()); } -TranslationsModel::~TranslationsModel() {} +TranslationsModel::~TranslationsModel() = default; void TranslationsModel::translationDirChanged(const QString& path) { qDebug() << "Dir changed:" << path; - if (!d->no_language_set) { + if (!d->m_noLanguageSet) { reloadLocalFiles(); } selectLanguage(selectedLanguage()); @@ -201,25 +202,21 @@ void TranslationsModel::translationDirChanged(const QString& path) void TranslationsModel::indexReceived() { qDebug() << "Got translations index!"; - d->m_index_job.reset(); + d->m_indexJob.reset(); + reloadLocalFiles(); - if (d->no_language_set) { - reloadLocalFiles(); - - auto language = d->m_system_locale; + if (d->m_noLanguageSet) { + auto language = getSystemLocaleName(); if (!findLanguageAsOptional(language).has_value()) { - language = d->m_system_language; + language = getSystemLanguage(); } selectLanguage(language); - if (selectedLanguage() != defaultLangCode) { - updateLanguage(selectedLanguage()); - } APPLICATION->settings()->set("Language", selectedLanguage()); - d->no_language_set = false; + d->m_noLanguageSet = false; } - else if (d->m_selectedLanguage != defaultLangCode) { - downloadTranslation(d->m_selectedLanguage); + if (selectedLanguage() != g_defaultLangCode) { + updateLanguage(selectedLanguage()); } } @@ -235,27 +232,27 @@ void readIndex(const QString& path, QMap& languages) } try { - auto toplevel_doc = Json::requireDocument(data); - auto doc = Json::requireObject(toplevel_doc); - auto file_type = Json::requireString(doc, "file_type"); - if (file_type != "MMC-TRANSLATION-INDEX") { - qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type; + auto toplevelDoc = Json::requireDocument(data); + auto doc = Json::requireObject(toplevelDoc); + auto fileType = Json::requireString(doc, "file_type"); + if (fileType != "MMC-TRANSLATION-INDEX") { + qCritical() << "Translations Download Failed: index file is of unknown file type" << fileType; return; } auto version = Json::requireInteger(doc, "version"); if (version > 2) { - qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type; + qCritical() << "Translations Download Failed: index file is of unknown format version" << fileType; return; } 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()); auto langObj = Json::requireObject(iter.value()); lang.setTranslationStats(langObj["translated"].toInt(), langObj["untranslated"].toInt(), langObj["fuzzy"].toInt()); - lang.file_name = Json::requireString(langObj, "file"); - lang.file_sha1 = Json::requireString(langObj, "sha1"); - lang.file_size = Json::requireInteger(langObj, "size"); + lang.fileName = Json::requireString(langObj, "file"); + lang.fileSha1 = Json::requireString(langObj, "sha1"); + lang.fileSize = Json::requireInteger(langObj, "size"); languages.insert(lang.key, lang); } @@ -267,20 +264,25 @@ void readIndex(const QString& path, QMap& languages) void TranslationsModel::reloadLocalFiles() { - QMap languages = { { defaultLangCode, Language(defaultLangCode) } }; + QMap languages = { { g_defaultLangCode, Language(g_defaultLangCode) } }; - readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages); + const auto indexPath = d->m_dir.absoluteFilePath("index_v2.json"); + if (!QFileInfo::exists(indexPath)) { + downloadIndex(); + return; + } + readIndex(indexPath, languages); auto entries = d->m_dir.entryInfoList({ "mmc_*.qm", "*.po" }, QDir::Files | QDir::NoDotAndDotDot); for (auto& entry : entries) { auto completeSuffix = entry.completeSuffix(); QString langCode; - FileType fileType = FileType::NONE; + FileType fileType = FileType::None; if (completeSuffix == "qm") { langCode = entry.baseName().remove(0, 4); - fileType = FileType::QM; + fileType = FileType::Qm; } else if (completeSuffix == "po") { langCode = entry.baseName(); - fileType = FileType::PO; + fileType = FileType::Po; } else { continue; } @@ -288,13 +290,14 @@ void TranslationsModel::reloadLocalFiles() auto langIter = languages.find(langCode); if (langIter != languages.end()) { auto& language = *langIter; - if (int(fileType) > int(language.localFileType)) { + // TODO: use std::to_underlying in C++23 + if (static_cast(fileType) > static_cast(language.localFileType)) { language.localFileType = fileType; } } else { - if (fileType == FileType::PO) { + if (fileType == FileType::Po) { Language localFound(langCode); - localFound.localFileType = FileType::PO; + localFound.localFileType = FileType::Po; languages.insert(langCode, localFound); } } @@ -314,7 +317,7 @@ void TranslationsModel::reloadLocalFiles() emit dataChanged(index(row), index(row)); languages.remove(language.key); } - iter++; + ++iter; } else { beginRemoveRows(QModelIndex(), row, row); iter = d->m_languages.erase(iter); @@ -329,34 +332,38 @@ void TranslationsModel::reloadLocalFiles() for (auto& language : languages) { 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 == d->m_system_locale || a.key == d->m_system_language) { + if (a.key == systemLocale || a.key == systemLanguage) { return true; } - if (b.key == d->m_system_locale || b.key == d->m_system_language) { + if (b.key == systemLocale || b.key == systemLanguage) { return false; } } return a.languageName().toLower() < b.languageName().toLower(); - }); + }; + std::ranges::sort(d->m_languages, comp); endInsertRows(); } namespace { -enum class Column { Language, Completeness }; +enum class Column : std::uint8_t { Language, Completeness }; } -QVariant TranslationsModel::data(const QModelIndex& index, int role) const +QVariant TranslationsModel::data(const QModelIndex& index, const int role) const { - if (!index.isValid()) - return QVariant(); + if (!index.isValid()) { + return {}; + } - int row = index.row(); - auto column = static_cast(index.column()); + const int row = index.row(); + const auto column = static_cast(index.column()); - if (row < 0 || row >= d->m_languages.size()) - return QVariant(); + if (row < 0 || row >= d->m_languages.size()) { + return {}; + } auto& lang = d->m_languages[row]; switch (role) { @@ -378,11 +385,11 @@ QVariant TranslationsModel::data(const QModelIndex& index, int role) const case Qt::UserRole: return lang.key; default: - return QVariant(); + return {}; } } -QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const +QVariant TranslationsModel::headerData(int section, const Qt::Orientation orientation, const int role) const { auto column = static_cast(section); if (role == Qt::DisplayRole) { @@ -417,49 +424,50 @@ int TranslationsModel::columnCount([[maybe_unused]] const QModelIndex& parent) c return 2; } -QList::Iterator TranslationsModel::findLanguage(const QString& key) +QList::Iterator TranslationsModel::findLanguage(const QString& key) const { - return std::find_if(d->m_languages.begin(), d->m_languages.end(), [key](Language& lang) { return lang.key == key; }); + return std::ranges::find_if(d->m_languages, [key](const Language& lang) { return lang.key == key; }); } -std::optional TranslationsModel::findLanguageAsOptional(const QString& key) +std::optional TranslationsModel::findLanguageAsOptional(const QString& key) const { auto found = findLanguage(key); - if (found != d->m_languages.end()) + if (found != d->m_languages.end()) { return *found; + } return {}; } -void TranslationsModel::setUseSystemLocale(bool useSystemLocale) +void TranslationsModel::setUseSystemLocale(const bool useSystemLocale) const { APPLICATION->settings()->set("UseSystemLocale", useSystemLocale); - QLocale::setDefault(QLocale(useSystemLocale ? QString::fromStdString(std::locale().name()) : defaultLangCode)); + QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(selectedLanguage())); } -bool TranslationsModel::selectLanguage(QString key) +bool TranslationsModel::selectLanguage(QString key) const { QString& langCode = key; auto langPtr = findLanguageAsOptional(key); if (langCode.isEmpty()) { - d->no_language_set = true; + d->m_noLanguageSet = true; } if (!langPtr.has_value()) { - qWarning() << "Selected invalid language" << key << ", defaulting to" << defaultLangCode; - langCode = defaultLangCode; + qWarning() << "Selected invalid language" << key << ", defaulting to" << g_defaultLangCode; + langCode = g_defaultLangCode; } else { langCode = langPtr->key; } // uninstall existing translators if there are any - if (d->m_app_translator) { - QCoreApplication::removeTranslator(d->m_app_translator.get()); - d->m_app_translator.reset(); + if (d->m_appTranslator) { + QCoreApplication::removeTranslator(d->m_appTranslator.get()); + d->m_appTranslator.reset(); } - if (d->m_qt_translator) { - QCoreApplication::removeTranslator(d->m_qt_translator.get()); - d->m_qt_translator.reset(); + if (d->m_qtTranslator) { + QCoreApplication::removeTranslator(d->m_qtTranslator.get()); + d->m_qtTranslator.reset(); } /* @@ -467,11 +475,11 @@ bool TranslationsModel::selectLanguage(QString key) * 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. */ - QLocale::setDefault( - QLocale(APPLICATION->settings()->get("UseSystemLocale").toBool() ? QString::fromStdString(std::locale().name()) : langCode)); + const bool useSystemLocale = APPLICATION->settings()->get("UseSystemLocale").toBool(); + QLocale::setDefault(useSystemLocale ? QLocale::system() : QLocale(langCode)); // if it's the default UI language, finish - if (langCode == defaultLangCode) { + if (langCode == g_defaultLangCode) { d->m_selectedLanguage = langCode; return true; } @@ -479,89 +487,88 @@ bool TranslationsModel::selectLanguage(QString key) // otherwise install new translations bool successful = false; // FIXME: this is likely never present. FIX IT. - d->m_qt_translator.reset(new QTranslator()); - if (d->m_qt_translator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { + d->m_qtTranslator = std::make_unique(); + if (d->m_qtTranslator->load("qt_" + langCode, QLibraryInfo::path(QLibraryInfo::TranslationsPath))) { qDebug() << "Loading Qt Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_qt_translator.get())) { + if (!QCoreApplication::installTranslator(d->m_qtTranslator.get())) { qCritical() << "Loading Qt Language File failed."; - d->m_qt_translator.reset(); + d->m_qtTranslator.reset(); } else { successful = true; } } else { - d->m_qt_translator.reset(); + d->m_qtTranslator.reset(); } - if (langPtr->localFileType == FileType::PO) { + if (langPtr->localFileType == FileType::Po) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po")); - if (!poTranslator->isEmpty()) { - if (!QCoreApplication::installTranslator(poTranslator)) { - delete poTranslator; + d->m_appTranslator = std::make_unique(FS::PathCombine(d->m_dir.path(), langCode + ".po")); + if (!d->m_appTranslator->isEmpty()) { + if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { qCritical() << "Installing Application Language File failed."; + d->m_appTranslator.reset(); } else { - d->m_app_translator.reset(poTranslator); successful = true; } } else { qCritical() << "Loading Application Language File failed."; - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } - } else if (langPtr->localFileType == FileType::QM) { - d->m_app_translator.reset(new QTranslator()); - if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path())) { + } else if (langPtr->localFileType == FileType::Qm) { + d->m_appTranslator = std::make_unique(); + if (d->m_appTranslator->load("mmc_" + langCode, d->m_dir.path())) { qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "..."; - if (!QCoreApplication::installTranslator(d->m_app_translator.get())) { + if (!QCoreApplication::installTranslator(d->m_appTranslator.get())) { qCritical() << "Installing Application Language File failed."; - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } else { successful = true; } } else { - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } } else { - d->m_app_translator.reset(); + d->m_appTranslator.reset(); } d->m_selectedLanguage = langCode; return successful; } -QModelIndex TranslationsModel::selectedIndex() +QModelIndex TranslationsModel::selectedIndex() const { auto found = findLanguage(d->m_selectedLanguage); if (found != d->m_languages.end()) { return index(std::distance(d->m_languages.begin(), found), 0, QModelIndex()); } - return QModelIndex(); + return {}; } -QString TranslationsModel::selectedLanguage() +QString TranslationsModel::selectedLanguage() const { return d->m_selectedLanguage; } void TranslationsModel::downloadIndex() { - if (d->m_index_job || d->m_dl_job) { + if (d->m_indexJob || d->m_downloadJob) { return; } qDebug() << "Downloading Translations Index..."; - d->m_index_job.reset(new NetJob("Translations Index", APPLICATION->network())); - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); + d->m_indexJob.reset(new NetJob("Translations Index", APPLICATION->network())); + const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "index_v2.json"); entry->setStale(true); auto task = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + "index_v2.json"), entry); - d->m_index_task = task.get(); - d->m_index_job->addNetAction(task); - d->m_index_job->setAskRetry(false); - connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); - connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); - d->m_index_job->start(); + d->m_indexTask = task.get(); + d->m_indexJob->addNetAction(task); + d->m_indexJob->setAskRetry(false); + connect(d->m_indexJob.get(), &NetJob::failed, this, &TranslationsModel::indexFailed); + connect(d->m_indexJob.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived); + d->m_indexJob->start(); } -void TranslationsModel::updateLanguage(QString key) +void TranslationsModel::updateLanguage(const QString& key) { - if (key == defaultLangCode) { + if (key == g_defaultLangCode) { qWarning() << "Cannot update builtin language" << key; return; } @@ -575,9 +582,9 @@ void TranslationsModel::updateLanguage(QString key) } } -void TranslationsModel::downloadTranslation(QString key) +void TranslationsModel::downloadTranslation(const QString& key) { - if (d->m_dl_job) { + if (d->m_downloadJob) { d->m_nextDownload = key; return; } @@ -588,21 +595,21 @@ void TranslationsModel::downloadTranslation(QString key) } d->m_downloadingTranslation = key; - MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); + const MetaEntryPtr entry = APPLICATION->metacache()->resolveEntry("translations", "mmc_" + key + ".qm"); entry->setStale(true); - auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->file_name), entry); - dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->file_sha1)); - dl->setProgress(dl->getProgress(), lang->file_size); + auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATION_FILES_URL + lang->fileName), entry); + dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, lang->fileSha1)); + dl->setProgress(dl->getProgress(), lang->fileSize); - d->m_dl_job.reset(new NetJob("Translation for " + key, APPLICATION->network())); - d->m_dl_job->addNetAction(dl); - d->m_dl_job->setAskRetry(false); + d->m_downloadJob.reset(new NetJob("Translation for " + key, APPLICATION->network())); + d->m_downloadJob->addNetAction(dl); + d->m_downloadJob->setAskRetry(false); - connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); - connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); + connect(d->m_downloadJob.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood); + connect(d->m_downloadJob.get(), &NetJob::failed, this, &TranslationsModel::dlFailed); - d->m_dl_job->start(); + d->m_downloadJob->start(); } void TranslationsModel::downloadNext() @@ -613,10 +620,10 @@ void TranslationsModel::downloadNext() } } -void TranslationsModel::dlFailed(QString reason) +void TranslationsModel::dlFailed(const QString& reason) { qCritical() << "Translations Download Failed:" << reason; - d->m_dl_job.reset(); + d->m_downloadJob.reset(); downloadNext(); } @@ -627,12 +634,12 @@ void TranslationsModel::dlGood() if (d->m_downloadingTranslation == d->m_selectedLanguage) { selectLanguage(d->m_selectedLanguage); } - d->m_dl_job.reset(); + d->m_downloadJob.reset(); downloadNext(); } -void TranslationsModel::indexFailed(QString reason) +void TranslationsModel::indexFailed(const QString& reason) const { qCritical() << "Translations Index Download Failed:" << reason; - d->m_index_job.reset(); + d->m_indexJob.reset(); } diff --git a/launcher/translations/TranslationsModel.h b/launcher/translations/TranslationsModel.h index 945e689fc..1dd0b601c 100644 --- a/launcher/translations/TranslationsModel.h +++ b/launcher/translations/TranslationsModel.h @@ -24,38 +24,38 @@ struct Language; class TranslationsModel : public QAbstractListModel { Q_OBJECT public: - explicit TranslationsModel(QString path, QObject* parent = 0); - virtual ~TranslationsModel(); - - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent) const override; - - bool selectLanguage(QString key); - void updateLanguage(QString key); - QModelIndex selectedIndex(); - QString selectedLanguage(); - - void downloadIndex(); - void setUseSystemLocale(bool useSystemLocale); - - private: - QList::Iterator findLanguage(const QString& key); - std::optional findLanguageAsOptional(const QString& key); - void reloadLocalFiles(); - void downloadTranslation(QString key); - void downloadNext(); + explicit TranslationsModel(const QString& path, QObject* parent = nullptr); + ~TranslationsModel() override; // hide copy constructor TranslationsModel(const TranslationsModel&) = delete; // hide assign op TranslationsModel& operator=(const TranslationsModel&) = delete; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; // NOLINT(*-default-arguments) + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; // NOLINT(*-default-arguments) + bool selectLanguage(QString key) const; + void updateLanguage(const QString& key); + QModelIndex selectedIndex() const; + QString selectedLanguage() const; + + void downloadIndex(); + void setUseSystemLocale(bool useSystemLocale) const; + + private: + int columnCount(const QModelIndex& parent) const override; + + QList::Iterator findLanguage(const QString& key) const; + std::optional findLanguageAsOptional(const QString& key) const; + void reloadLocalFiles(); + void downloadTranslation(const QString& key); + void downloadNext(); + private slots: void indexReceived(); - void indexFailed(QString reason); - void dlFailed(QString reason); + void indexFailed(const QString& reason) const; + void dlFailed(const QString& reason); void dlGood(); void translationDirChanged(const QString& path); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f4c170eb8..1bdcd3f68 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -105,6 +105,7 @@ #include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewsDialog.h" #include "ui/dialogs/ProgressDialog.h" +#include "ui/dialogs/skins/SkinManageDialog.h" #include "ui/instanceview/InstanceDelegate.h" #include "ui/instanceview/InstanceProxyModel.h" #include "ui/instanceview/InstanceView.h" @@ -180,7 +181,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance); // restore the instance toolbar settings - auto const setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); + const auto setting_name = QString("WideBarVisibility_%1").arg(ui->instanceToolBar->objectName()); instanceToolbarSetting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->instanceToolBar->setVisibilityState(QByteArray::fromBase64(instanceToolbarSetting->get().toString().toUtf8())); @@ -396,6 +397,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi // Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit. // Template hell sucks... connect(APPLICATION->accounts(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); }); + connect(APPLICATION->accounts(), &AccountList::listActivityChanged, [this] { defaultAccountChanged(); }); connect(APPLICATION->accounts(), &AccountList::listChanged, [this] { defaultAccountChanged(); }); // Show initial account @@ -653,6 +655,9 @@ void MainWindow::repopulateAccountsMenu() auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); + + bool canChangeSkin = defaultAccount && (defaultAccount->accountType() == AccountType::MSA) && !defaultAccount->isActive(); + ui->actionManageSkins->setEnabled(canChangeSkin); QString active_profileId = ""; if (defaultAccount) { @@ -709,6 +714,7 @@ void MainWindow::repopulateAccountsMenu() connect(ui->actionNoDefaultAccount, &QAction::triggered, this, &MainWindow::changeActiveAccount); ui->accountsMenu->addSeparator(); + ui->accountsMenu->addAction(ui->actionManageSkins); ui->accountsMenu->addAction(ui->actionManageAccounts); accountsButtonMenu->addActions(ui->accountsMenu->actions()); @@ -931,6 +937,9 @@ void MainWindow::processURLs(QList urls) { // NOTE: This loop only processes one dropped file! for (auto& url : urls) { + if (url.isEmpty() || url.toString().trimmed().isEmpty()) + continue; + qDebug() << "Processing" << url; // The isLocalFile() check below doesn't work as intended without an explicit scheme. @@ -942,9 +951,7 @@ void MainWindow::processURLs(QList urls) QUrl local_url; if (!url.isLocalFile()) { // download the remote resource and identify - const bool isExternalURLImport = - (url.host().toLower() == "import") || - (url.path().startsWith("/import", Qt::CaseInsensitive)); + const bool isExternalURLImport = (url.host().toLower() == "import") || (url.path().startsWith("/import", Qt::CaseInsensitive)); QUrl dl_url; if (url.scheme() == "curseforge" || (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME && url.host() == "install")) { @@ -952,7 +959,7 @@ void MainWindow::processURLs(QList urls) // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE // format of url binaryname://install?platform=curseforge&addonId=IDHERE&fileId=IDHERE QUrlQuery query(url); - + // check if this is a binaryname:// url if (url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) { // check this is an curseforge platform request @@ -1015,8 +1022,7 @@ void MainWindow::processURLs(QList urls) receivedData.insert(it->first, it->second); emit APPLICATION->oauthReplyRecieved(receivedData); continue; - } else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) - && isExternalURLImport) { + } else if ((url.scheme() == "prismlauncher" || url.scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME) && isExternalURLImport) { // PrismLauncher URL protocol modpack import // works for any prism fork // preferred import format: prismlauncher://import?url=ENCODED @@ -1035,7 +1041,6 @@ void MainWindow::processURLs(QList urls) // alternative import format: prismlauncher://import/ENCODED if (encodedTarget.isEmpty()) { - QString p = path; if (p.startsWith("/import/", Qt::CaseInsensitive)) { @@ -1050,12 +1055,9 @@ void MainWindow::processURLs(QList urls) } if (encodedTarget.isEmpty()) { - CustomMessageBox::selectable( - this, - tr("Error"), - tr("Invalid import link: missing 'url' parameter."), - QMessageBox::Critical - )->show(); + CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: missing 'url' parameter."), + QMessageBox::Critical) + ->show(); continue; } @@ -1065,23 +1067,15 @@ void MainWindow::processURLs(QList urls) // Validate: only allow http(s) if (!target.isValid() || (target.scheme() != "https" && target.scheme() != "http")) { - CustomMessageBox::selectable( - this, - tr("Error"), - tr("Invalid import link: URL must be http(s)."), - QMessageBox::Critical - )->show(); + CustomMessageBox::selectable(this, tr("Error"), tr("Invalid import link: URL must be http(s)."), QMessageBox::Critical) + ->show(); continue; } const auto res = QMessageBox::question( - this, - tr("Install modpack"), - tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2") - .arg(target.host(), target.toString()), - QMessageBox::Yes | QMessageBox::No, - QMessageBox::Yes - ); + this, tr("Install modpack"), + tr("Do you want to download and import a modpack from:\n%1\n\nURL:\n%2").arg(target.host(), target.toString()), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res != QMessageBox::Yes) { continue; } @@ -1125,6 +1119,11 @@ void MainWindow::processURLs(QList urls) auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile()); QFileInfo localFileInfo(localFileName); + if (localFileName.isEmpty() || !localFileInfo.exists()) { + qDebug() << "Ignoring invalid path" << localFileName; + continue; + } + auto type = ResourceUtils::identify(localFileInfo); if (ModPlatform::ResourceTypeUtils::VALID_RESOURCES.count(type) == 0) { // probably instance/modpack @@ -1396,6 +1395,16 @@ void MainWindow::on_actionEditInstance_triggered() } } +void MainWindow::on_actionManageSkins_triggered() +{ + auto account = APPLICATION->accounts()->defaultAccount(); + + if (account && (account->accountType() == AccountType::MSA) && !account->isActive()) { + SkinManageDialog dialog(this, account); + dialog.exec(); + } +} + void MainWindow::on_actionManageAccounts_triggered() { APPLICATION->ShowGlobalSettings(this, "accounts"); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index 7dcba885a..80860deef 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -130,6 +130,8 @@ class MainWindow : public QMainWindow { void on_actionSettings_triggered(); + void on_actionManageSkins_triggered(); + void on_actionManageAccounts_triggered(); void on_actionReportBug_triggered(); diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui index 353788518..e9b9aa442 100644 --- a/launcher/ui/MainWindow.ui +++ b/launcher/ui/MainWindow.ui @@ -322,6 +322,14 @@ QAction::PreferencesRole + + + + + + Manage &Skins... + + @@ -520,7 +528,7 @@ Close the current window - QAction::QuitRole + QAction::MenuRole::NoRole diff --git a/launcher/ui/dialogs/ExportToModListDialog.cpp b/launcher/ui/dialogs/ExportToModListDialog.cpp index e8873f9b4..a782a9190 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.cpp +++ b/launcher/ui/dialogs/ExportToModListDialog.cpp @@ -214,6 +214,8 @@ void ExportToModListDialog::addExtra(ExportToModList::OptionalData option) case ExportToModList::FileName: ui->templateText->insertPlainText("{filename}"); break; + case ExportToModList::None: + break; } } void ExportToModListDialog::enableCustom(bool enabled) diff --git a/launcher/ui/dialogs/IconPickerDialog.cpp b/launcher/ui/dialogs/IconPickerDialog.cpp index b56e95dba..b2269cc70 100644 --- a/launcher/ui/dialogs/IconPickerDialog.cpp +++ b/launcher/ui/dialogs/IconPickerDialog.cpp @@ -30,26 +30,106 @@ #include "icons/IconList.h" #include "icons/IconUtils.h" +class IconProxyModel : public QSortFilterProxyModel +{ +public: + explicit IconProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + } + + void setCategory(IconPickerDialog::IconPickerCategory category) + { + if (m_category == category) + return; + m_category = category; + invalidateFilter(); + } + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override + { + if (!QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) + return false; + + if (m_category == IconPickerDialog::Any) + return true; + + auto model = static_cast(sourceModel()); + QModelIndex index = model->index(source_row, 0, source_parent); + QString key = model->data(index, Qt::UserRole).toString(); + const MMCIcon* icon = model->icon(key); + + if (!icon) + return false; + + bool isModpack = false; + bool isBuiltin = icon->isBuiltIn(); + bool isLegacy = isBuiltin && icon->name().endsWith("_legacy", Qt::CaseInsensitive); + + if (!isBuiltin) { + const QString& name = icon->name(); + if (name.startsWith("curseforge_", Qt::CaseInsensitive) || + name.startsWith("modrinth_", Qt::CaseInsensitive) || + name.startsWith("ftb_", Qt::CaseInsensitive) || + name.startsWith("technic_", Qt::CaseInsensitive) || + name.startsWith("atl_", Qt::CaseInsensitive)) { + isModpack = true; + } + } + + switch (m_category) { + case IconPickerDialog::Legacy: + return isBuiltin && isLegacy; + case IconPickerDialog::Modpacks: + return isModpack; + case IconPickerDialog::Modern: + return isBuiltin && !isLegacy; + case IconPickerDialog::Custom: + return !isBuiltin && !isModpack; + default: + return true; + } + } + +private: + IconPickerDialog::IconPickerCategory m_category = IconPickerDialog::Any; +}; + IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::IconPickerDialog) { ui->setupUi(this); setWindowModality(Qt::WindowModal); - searchBar = new QLineEdit(this); - searchBar->setPlaceholderText(tr("Search...")); - ui->verticalLayout->insertWidget(0, searchBar); + static const QString context_text[] = { + tr("All"), + tr("Modern"), + tr("Legacy"), + tr("Modpacks"), + tr("Custom"), + }; + static const IconPickerCategory context_id[] = { + Any, + Modern, + Legacy, + Modpacks, + Custom, + }; + const int cnt = sizeof(context_text) / sizeof(context_text[0]); + for (int i = 0; i < cnt; ++i) { + ui->contextCombo->addItem(context_text[i], context_id[i]); + if (i == 0) { + ui->contextCombo->insertSeparator(i + 1); + } + } - proxyModel = new QSortFilterProxyModel(this); + proxyModel = new IconProxyModel(this); proxyModel->setSourceModel(APPLICATION->icons()); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); ui->iconView->setModel(proxyModel); auto contentsWidget = ui->iconView; - contentsWidget->setViewMode(QListView::IconMode); contentsWidget->setFlow(QListView::LeftToRight); contentsWidget->setIconSize(QSize(48, 48)); - contentsWidget->setMovement(QListView::Static); - contentsWidget->setResizeMode(QListView::Adjust); contentsWidget->setSelectionMode(QAbstractItemView::SingleSelection); contentsWidget->setSpacing(5); contentsWidget->setWordWrap(false); @@ -86,7 +166,11 @@ IconPickerDialog::IconPickerDialog(QWidget* parent) : QDialog(parent), ui(new Ui auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole); connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder); - connect(searchBar, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->searchLine, &QLineEdit::textChanged, this, &IconPickerDialog::filterIcons); + connect(ui->contextCombo, &QComboBox::currentIndexChanged, this, [this](int index) { + IconPickerCategory category = static_cast(ui->contextCombo->itemData(index).toInt()); + filterIconsByCategory(category); + }); // Prevent incorrect indices from e.g. filesystem changes connect(APPLICATION->icons(), &IconList::iconUpdated, this, [this]() { proxyModel->invalidate(); }); } @@ -182,3 +266,8 @@ void IconPickerDialog::filterIcons(const QString& query) { proxyModel->setFilterFixedString(query); } + +void IconPickerDialog::filterIconsByCategory(IconPickerCategory category) +{ + static_cast(proxyModel)->setCategory(category); +} diff --git a/launcher/ui/dialogs/IconPickerDialog.h b/launcher/ui/dialogs/IconPickerDialog.h index db1315338..063f9b905 100644 --- a/launcher/ui/dialogs/IconPickerDialog.h +++ b/launcher/ui/dialogs/IconPickerDialog.h @@ -32,6 +32,15 @@ class IconPickerDialog : public QDialog { int execWithSelection(QString selection); QString selectedIconKey; + enum IconPickerCategory { + Any, + Modern, + Legacy, + Modpacks, + Custom, + }; + Q_ENUM(IconPickerCategory) + protected: virtual bool eventFilter(QObject*, QEvent*); @@ -49,4 +58,5 @@ class IconPickerDialog : public QDialog { void removeSelectedIcon(); void openFolder(); void filterIcons(const QString& text); + void filterIconsByCategory(IconPickerCategory); }; diff --git a/launcher/ui/dialogs/IconPickerDialog.ui b/launcher/ui/dialogs/IconPickerDialog.ui index c548edfb7..948e7043f 100644 --- a/launcher/ui/dialogs/IconPickerDialog.ui +++ b/launcher/ui/dialogs/IconPickerDialog.ui @@ -15,7 +15,64 @@ - + + + + + + + Icon category + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Search Icons... + + + true + + + + + + + + + + + + 60 + 60 + + + + QListView::Static + + + QListView::Adjust + + + QListView::IconMode + + + true + + diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp index 2e9abe631..deb5358fb 100644 --- a/launcher/ui/dialogs/InstallLoaderDialog.cpp +++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp @@ -101,12 +101,12 @@ InstallLoaderDialog::InstallLoaderDialog(PackProfile* profile, const QString& ui buttonLayout->setContentsMargins(0, 0, 6, 6); #endif auto refreshButton = new QPushButton(tr("&Refresh"), this); - connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(); }); + connect(refreshButton, &QPushButton::clicked, this, [this] { pageCast(container->selectedPage())->loadList(true); }); buttonLayout->addWidget(refreshButton); buttons->setOrientation(Qt::Horizontal); buttons->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - buttons->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + buttons->button(QDialogButtonBox::Ok)->setText(tr("OK")); buttons->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 6aa0b4bdf..0a453bda5 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -252,10 +252,7 @@ void ProgressDialog::changeStepProgress(TaskStepProgress const& task_progress) task_bar->setValue(mapped_current); task_bar->setStatus(task_progress.status); task_bar->setDetails(task_progress.details); - - if (task_progress.isDone()) { - task_bar->setVisible(false); - } + task_bar->setVisible(!task_progress.isDone()); } void ProgressDialog::changeProgress(qint64 current, qint64 total) diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp index 002f85e0f..bcb30c761 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp +++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "Application.h" #include "ResourceDownloadTask.h" @@ -49,11 +50,12 @@ namespace ResourceDownload { -ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* base_model) +ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch) : QDialog(parent) - , m_base_model(base_model) + , m_base_model(baseModel) , m_buttons(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel) , m_vertical_layout(this) + , m_suppressInitialSearch(suppressInitialSearch) { setObjectName(QStringLiteral("ResourceDownloadDialog")); @@ -61,34 +63,35 @@ ResourceDownloadDialog::ResourceDownloadDialog(QWidget* parent, ResourceFolderMo setWindowIcon(QIcon::fromTheme("new")); - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS +// small margins look ugly on macOS on modal windows +#ifndef Q_OS_MACOS m_buttons.setContentsMargins(0, 0, 6, 6); - #endif +#endif // Bonk Qt over its stupid head and make sure it understands which button is the default one... // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button - auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setEnabled(false); - OkButton->setDefault(true); - OkButton->setAutoDefault(true); - OkButton->setText(tr("Review and confirm")); - OkButton->setShortcut(tr("Ctrl+Return")); + auto* okButton = m_buttons.button(QDialogButtonBox::Ok); + okButton->setEnabled(false); + okButton->setDefault(true); + okButton->setAutoDefault(true); + okButton->setText(tr("Review and confirm")); + okButton->setShortcut(tr("Ctrl+Return")); - auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); - CancelButton->setDefault(false); - CancelButton->setAutoDefault(false); + auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); + cancelButton->setDefault(false); + cancelButton->setAutoDefault(false); - auto HelpButton = m_buttons.button(QDialogButtonBox::Help); - HelpButton->setDefault(false); - HelpButton->setAutoDefault(false); + auto* helpButton = m_buttons.button(QDialogButtonBox::Help); + helpButton->setDefault(false); + helpButton->setAutoDefault(false); setWindowModality(Qt::WindowModal); } void ResourceDownloadDialog::accept() { - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); + } QDialog::accept(); } @@ -108,8 +111,9 @@ void ResourceDownloadDialog::reject() } } - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { APPLICATION->settings()->set(geometrySaveKey(), QString::fromUtf8(saveGeometry().toBase64())); + } QDialog::reject(); } @@ -118,10 +122,10 @@ void ResourceDownloadDialog::reject() // won't work with subclasses if we put it in this ctor. void ResourceDownloadDialog::initializeContainer() { - // small margins look ugly on macOS on modal windows - #ifndef Q_OS_MACOS +// small margins look ugly on macOS on modal windows +#ifndef Q_OS_MACOS layout()->setContentsMargins(0, 0, 0, 0); - #endif +#endif m_container = new PageContainer(this, {}, this); m_container->setSizePolicy(QSizePolicy::Policy::Preferred, QSizePolicy::Policy::Expanding); @@ -135,28 +139,28 @@ void ResourceDownloadDialog::initializeContainer() void ResourceDownloadDialog::connectButtons() { - auto OkButton = m_buttons.button(QDialogButtonBox::Ok); - OkButton->setToolTip( + auto* okButton = m_buttons.button(QDialogButtonBox::Ok); + okButton->setToolTip( tr("Opens a new popup to review your selected %1 and confirm your selection. Shortcut: Ctrl+Return").arg(resourcesString())); - connect(OkButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); + connect(okButton, &QPushButton::clicked, this, &ResourceDownloadDialog::confirm); - auto CancelButton = m_buttons.button(QDialogButtonBox::Cancel); - connect(CancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); + auto* cancelButton = m_buttons.button(QDialogButtonBox::Cancel); + connect(cancelButton, &QPushButton::clicked, this, &ResourceDownloadDialog::reject); - auto HelpButton = m_buttons.button(QDialogButtonBox::Help); - connect(HelpButton, &QPushButton::clicked, m_container, &PageContainer::help); + auto* helpButton = m_buttons.button(QDialogButtonBox::Help); + connect(helpButton, &QPushButton::clicked, m_container, &PageContainer::help); } void ResourceDownloadDialog::confirm() { - auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); - confirm_dialog->retranslateUi(resourcesString()); + auto* confirmDialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString())); + confirmDialog->retranslateUi(resourcesString()); QHash dependencyExtraInfo; QStringList depNames; if (auto task = getModDependenciesTask(); task) { connect(task.get(), &Task::failed, this, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); auto weak = task.toWeakRef(); connect(task.get(), &Task::succeeded, this, [this, weak]() { @@ -170,72 +174,77 @@ void ResourceDownloadDialog::confirm() }); // Check for updates - ProgressDialog progress_dialog(this); - progress_dialog.setSkipButton(true, tr("Abort")); - progress_dialog.setWindowTitle(tr("Checking for dependencies...")); - auto ret = progress_dialog.execWithTask(task.get()); + ProgressDialog progressDialog(this); + progressDialog.setSkipButton(true, tr("Abort")); + progressDialog.setWindowTitle(tr("Checking for dependencies...")); + auto ret = progressDialog.execWithTask(task.get()); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; - } else { - for (auto dep : task->getDependecies()) { - addResource(dep->pack, dep->version); - depNames << dep->pack->name; - } - dependencyExtraInfo = task->getExtraInfo(); } + for (const auto& dep : task->getDependecies()) { + addResource(dep->pack, dep->version, "dependency"); + depNames << dep->pack->name; + } + dependencyExtraInfo = task->getExtraInfo(); } auto selected = getTasks(); - std::sort(selected.begin(), selected.end(), [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { + std::ranges::sort(selected, [](const DownloadTaskPtr& a, const DownloadTaskPtr& b) { return QString::compare(a->getName(), b->getName(), Qt::CaseInsensitive) < 0; }); for (auto& task : selected) { auto extraInfo = dependencyExtraInfo.value(task->getPack()->addonId.toString()); - confirm_dialog->appendResource({ task->getName(), task->getFilename(), ModPlatform::ProviderCapabilities::name(task->getProvider()), - extraInfo.required_by, task->getVersion().version_type.toString(), !extraInfo.maybe_installed }); + confirmDialog->appendResource({ .name = task->getName(), + .filename = task->getFilename(), + .provider = ModPlatform::ProviderCapabilities::name(task->getProvider()), + .required_by = extraInfo.required_by, + .version_type = task->getVersion().version_type.toString(), + .enabled = !extraInfo.maybe_installed }); } - if (confirm_dialog->exec()) { - auto deselected = confirm_dialog->deselectedResources(); - for (auto page : m_container->getPages()) { - auto res = static_cast(page); - for (auto name : deselected) + if (confirmDialog->exec() != 0) { + auto deselected = confirmDialog->deselectedResources(); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); + for (const auto& name : deselected) { res->removeResourceFromPage(name); + } } this->accept(); } else { - for (auto name : depNames) + for (const auto& name : depNames) { removeResource(name); + } } } bool ResourceDownloadDialog::selectPage(QString pageId) { - return m_container->selectPage(pageId); + return m_container->selectPage(std::move(pageId)); } ResourcePage* ResourceDownloadDialog::selectedPage() { - ResourcePage* result = dynamic_cast(m_container->selectedPage()); + auto* result = dynamic_cast(m_container->selectedPage()); Q_ASSERT(result != nullptr); return result; } -void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver) +void ResourceDownloadDialog::addResource(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, QString downloadReason) { removeResource(pack->name); - selectedPage()->addResourceToPage(pack, ver, getBaseModel()); + selectedPage()->addResourceToPage(pack, ver, getBaseModel(), std::move(downloadReason)); setButtonStatus(); } -void ResourceDownloadDialog::removeResource(const QString& pack_name) +void ResourceDownloadDialog::removeResource(const QString& packName) { - for (auto page : m_container->getPages()) { - static_cast(page)->removeResourceFromPage(pack_name); + for (auto* page : m_container->getPages()) { + static_cast(page)->removeResourceFromPage(packName); } setButtonStatus(); } @@ -243,18 +252,18 @@ void ResourceDownloadDialog::removeResource(const QString& pack_name) void ResourceDownloadDialog::setButtonStatus() { auto selected = false; - for (auto page : m_container->getPages()) { - auto res = static_cast(page); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); selected = selected || res->hasSelectedPacks(); } m_buttons.button(QDialogButtonBox::Ok)->setEnabled(selected); } -const QList ResourceDownloadDialog::getTasks() +QList ResourceDownloadDialog::getTasks() { QList selected; - for (auto page : m_container->getPages()) { - auto res = static_cast(page); + for (auto* page : m_container->getPages()) { + auto* res = static_cast(page); selected.append(res->selectedPacks()); } return selected; @@ -262,28 +271,34 @@ const QList ResourceDownloadDialog::get void ResourceDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) { - auto* prev_page = dynamic_cast(previous); - if (!prev_page) { + // If previous is null (first selection), nothing to sync + if (!previous) { + return; + } + + auto* prevPage = dynamic_cast(previous); + if (!prevPage) { qCritical() << "Page '" << previous->displayName() << "' in ResourceDownloadDialog is not a ResourcePage!"; return; } // Same effect as having a global search bar - ResourcePage* result = dynamic_cast(selected); + auto* result = dynamic_cast(selected); Q_ASSERT(result != nullptr); - result->setSearchTerm(prev_page->getSearchTerm()); + result->setSearchTerm(prevPage->getSearchTerm()); } -ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance) - : ResourceDownloadDialog(parent, mods), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch) + : ResourceDownloadDialog(parent, mods, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ModDownloadDialog::getPages() @@ -292,10 +307,16 @@ QList ModDownloadDialog::getPages() auto loaders = static_cast(m_instance)->getPackProfile()->getSupportedModLoaders().value(); - if (ModrinthAPI::validateModLoaders(loaders)) - pages.append(ModrinthModPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) - pages.append(FlameModPage::create(this, *m_instance)); + if (ModrinthAPI::validateModLoaders(loaders)) { + auto* page = ModrinthModPage::create(this, *m_instance); + page->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(page); + } + if (APPLICATION->capabilities() & Application::SupportsFlame && FlameAPI::validateModLoaders(loaders)) { + auto* page = FlameModPage::create(this, *m_instance); + page->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(page); + } return pages; } @@ -303,9 +324,9 @@ QList ModDownloadDialog::getPages() GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() { if (!APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - if (auto model = dynamic_cast(getBaseModel()); model) { + if (auto* model = dynamic_cast(getBaseModel()); model) { QList> selectedVers; - for (auto& selected : getTasks()) { + for (const auto& selected : getTasks()) { selectedVers.append(std::make_shared(selected->getPack(), selected->getVersion())); } @@ -315,70 +336,97 @@ GetModDependenciesTask::Ptr ModDownloadDialog::getModDependenciesTask() return nullptr; } -ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resource_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +ResourcePackDownloadDialog::ResourcePackDownloadDialog(QWidget* parent, + ResourcePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ResourcePackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthResourcePackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameResourcePackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthResourcePackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); + if (APPLICATION->capabilities() & Application::SupportsFlame) { + auto* flamePage = FlameResourcePackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); + } return pages; } -TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resource_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, resource_packs), m_instance(instance) +TexturePackDownloadDialog::TexturePackDownloadDialog(QWidget* parent, + TexturePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, resourcePacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList TexturePackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthTexturePackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameTexturePackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthTexturePackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); + if (APPLICATION->capabilities() & Application::SupportsFlame) { + auto* flamePage = FlameTexturePackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); + } return pages; } -ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shaders, BaseInstance* instance) - : ResourceDownloadDialog(parent, shaders), m_instance(instance) +ShaderPackDownloadDialog::ShaderPackDownloadDialog(QWidget* parent, + ShaderPackFolderModel* shaders, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, shaders, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toString().toUtf8())); + } } QList ShaderPackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthShaderPackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameShaderPackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthShaderPackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); + if (APPLICATION->capabilities() & Application::SupportsFlame) { + auto* flamePage = FlameShaderPackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); + } return pages; } @@ -392,31 +440,41 @@ void ResourceDownloadDialog::setResourceMetadata(const std::shared_ptrname)); m_container->hidePageList(); m_buttons.hide(); - auto page = selectedPage(); + auto* page = selectedPage(); page->openProject(meta->project_id); } -DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* data_packs, BaseInstance* instance) - : ResourceDownloadDialog(parent, data_packs), m_instance(instance) +DataPackDownloadDialog::DataPackDownloadDialog(QWidget* parent, + DataPackFolderModel* dataPacks, + BaseInstance* instance, + bool suppressInitialSearch) + : ResourceDownloadDialog(parent, dataPacks, suppressInitialSearch), m_instance(instance) { setWindowTitle(dialogTitle()); initializeContainer(); connectButtons(); - if (!geometrySaveKey().isEmpty()) + if (!geometrySaveKey().isEmpty()) { restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get(geometrySaveKey()).toByteArray())); + } } QList DataPackDownloadDialog::getPages() { QList pages; - pages.append(ModrinthDataPackPage::create(this, *m_instance)); - if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(FlameDataPackPage::create(this, *m_instance)); + auto* modrinthPage = ModrinthDataPackPage::create(this, *m_instance); + modrinthPage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(modrinthPage); + if (APPLICATION->capabilities() & Application::SupportsFlame) { + auto* flamePage = FlameDataPackPage::create(this, *m_instance); + flamePage->setSuppressInitialSearch(m_suppressInitialSearch); + pages.append(flamePage); + } return pages; } diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.h b/launcher/ui/dialogs/ResourceDownloadDialog.h index a85a85a09..bea6c7689 100644 --- a/launcher/ui/dialogs/ResourceDownloadDialog.h +++ b/launcher/ui/dialogs/ResourceDownloadDialog.h @@ -51,7 +51,7 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { public: using DownloadTaskPtr = shared_qobject_ptr; - ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* base_model); + ResourceDownloadDialog(QWidget* parent, ResourceFolderModel* baseModel, bool suppressInitialSearch = false); void initializeContainer(); void connectButtons(); @@ -64,10 +64,10 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { bool selectPage(QString pageId); ResourcePage* selectedPage(); - void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); + void addResource(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, QString downloadReason = "standalone"); void removeResource(const QString&); - const QList getTasks(); + QList getTasks(); ResourceFolderModel* getBaseModel() const { return m_base_model; } void setResourceMetadata(const std::shared_ptr& meta); @@ -94,13 +94,16 @@ class ResourceDownloadDialog : public QDialog, public BasePageProvider { QDialogButtonBox m_buttons; QVBoxLayout m_vertical_layout; + + protected: + bool m_suppressInitialSearch = false; }; class ModDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance); + explicit ModDownloadDialog(QWidget* parent, ModFolderModel* mods, BaseInstance* instance, bool suppressInitialSearch = false); ~ModDownloadDialog() override = default; //: String that gets appended to the mod download dialog title ("Download " + resourcesString()) @@ -118,7 +121,10 @@ class ResourcePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ResourcePackDownloadDialog(QWidget* parent, ResourcePackFolderModel* resource_packs, BaseInstance* instance); + explicit ResourcePackDownloadDialog(QWidget* parent, + ResourcePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ResourcePackDownloadDialog() override = default; //: String that gets appended to the resource pack download dialog title ("Download " + resourcesString()) @@ -135,7 +141,10 @@ class TexturePackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit TexturePackDownloadDialog(QWidget* parent, TexturePackFolderModel* resource_packs, BaseInstance* instance); + explicit TexturePackDownloadDialog(QWidget* parent, + TexturePackFolderModel* resourcePacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~TexturePackDownloadDialog() override = default; //: String that gets appended to the texture pack download dialog title ("Download " + resourcesString()) @@ -152,7 +161,10 @@ class ShaderPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit ShaderPackDownloadDialog(QWidget* parent, ShaderPackFolderModel* shader_packs, BaseInstance* instance); + explicit ShaderPackDownloadDialog(QWidget* parent, + ShaderPackFolderModel* shaders, + BaseInstance* instance, + bool suppressInitialSearch = false); ~ShaderPackDownloadDialog() override = default; //: String that gets appended to the shader pack download dialog title ("Download " + resourcesString()) @@ -169,7 +181,10 @@ class DataPackDownloadDialog final : public ResourceDownloadDialog { Q_OBJECT public: - explicit DataPackDownloadDialog(QWidget* parent, DataPackFolderModel* data_packs, BaseInstance* instance); + explicit DataPackDownloadDialog(QWidget* parent, + DataPackFolderModel* dataPacks, + BaseInstance* instance, + bool suppressInitialSearch = false); ~DataPackDownloadDialog() override = default; //: String that gets appended to the data pack download dialog title ("Download " + resourcesString()) diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 99b01c35d..e9776c660 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -29,10 +29,12 @@ #include -static std::vector mcVersions(BaseInstance* inst) +namespace { +std::vector mcVersions(BaseInstance* inst) { return { static_cast(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; } +} // namespace ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, BaseInstance* instance, @@ -58,8 +60,8 @@ ResourceUpdateDialog::ResourceUpdateDialog(QWidget* parent, void ResourceUpdateDialog::checkCandidates() { // Ensure mods have valid metadata - auto went_well = ensureMetadata(); - if (!went_well) { + auto wentWell = ensureMetadata(); + if (!wentWell) { m_aborted = true; return; } @@ -73,12 +75,12 @@ void ResourceUpdateDialog::checkCandidates() text += tr("Mod name: %1
File name: %2
Reason: %3

").arg(mod->name(), mod->fileinfo().fileName(), reason); } - ScrollMessageBox message_dialog(m_parent, tr("Metadata generation failed"), - tr("Could not generate metadata for the following resources:
" - "Do you wish to proceed without those resources?"), - text); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Metadata generation failed"), + tr("Could not generate metadata for the following resources:
" + "Do you wish to proceed without those resources?"), + text); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; @@ -87,40 +89,41 @@ void ResourceUpdateDialog::checkCandidates() auto versions = mcVersions(m_instance); - SequentialTask check_task(tr("Checking for updates")); + SequentialTask checkTask(tr("Checking for updates")); if (!m_modrinthToUpdate.empty()) { m_modrinthCheckTask.reset(new ModrinthCheckUpdate(m_modrinthToUpdate, versions, m_loadersList, m_resourceModel)); connect(m_modrinthCheckTask.get(), &CheckUpdateTask::checkFailed, this, - [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); }); - check_task.addTask(m_modrinthCheckTask); + checkTask.addTask(m_modrinthCheckTask); } if (!m_flameToUpdate.empty()) { m_flameCheckTask.reset(new FlameCheckUpdate(m_flameToUpdate, versions, m_loadersList, m_resourceModel)); - connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, [this](Resource* resource, QString reason, QUrl recover_url) { - m_failedCheckUpdate.append({ resource, reason, recover_url }); - }); - check_task.addTask(m_flameCheckTask); + connect(m_flameCheckTask.get(), &CheckUpdateTask::checkFailed, this, + [this](Resource* resource, const QString& reason, const QUrl& recoverUrl) { + m_failedCheckUpdate.append({ resource, reason, recoverUrl }); + }); + checkTask.addTask(m_flameCheckTask); } - connect(&check_task, &Task::failed, this, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(&checkTask, &Task::failed, this, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - connect(&check_task, &Task::succeeded, this, [this, &check_task]() { - QStringList warnings = check_task.warnings(); + connect(&checkTask, &Task::succeeded, this, [this, &checkTask]() { + QStringList warnings = checkTask.warnings(); if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec(); } }); // Check for updates - ProgressDialog progress_dialog(m_parent); - progress_dialog.setSkipButton(true, tr("Abort")); - progress_dialog.setWindowTitle(tr("Checking for updates...")); - auto ret = progress_dialog.execWithTask(&check_task); + ProgressDialog progressDialog(m_parent); + progressDialog.setSkipButton(true, tr("Abort")); + progressDialog.setWindowTitle(tr("Checking for updates...")); + auto ret = progressDialog.execWithTask(&checkTask); // If the dialog was skipped / some download error happened if (ret == QDialog::DialogCode::Rejected) { @@ -133,8 +136,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updates for Modrinth if (m_modrinthCheckTask) { - auto modrinth_updates = m_modrinthCheckTask->getUpdates(); - for (auto& updatable : modrinth_updates) { + auto modrinthUpdates = m_modrinthCheckTask->getUpdates(); + for (auto& updatable : modrinthUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -145,8 +148,8 @@ void ResourceUpdateDialog::checkCandidates() // Add found updated for Flame if (m_flameCheckTask) { - auto flame_updates = m_flameCheckTask->getUpdates(); - for (auto& updatable : flame_updates) { + auto flameUpdates = m_flameCheckTask->getUpdates(); + for (auto& updatable : flameUpdates) { qDebug() << QString("Mod %1 has an update available!").arg(updatable.name); appendResource(updatable); @@ -161,33 +164,35 @@ void ResourceUpdateDialog::checkCandidates() for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); const auto& reason = std::get<1>(failed); - const auto& recover_url = std::get<2>(failed); + const auto& recoverUrl = std::get<2>(failed); qDebug() << mod->name() << "failed to check for updates!"; text += tr("Mod name: %1").arg(mod->name()) + "
"; - if (!reason.isEmpty()) + if (!reason.isEmpty()) { text += tr("Reason: %1").arg(reason) + "
"; - if (!recover_url.isEmpty()) + } + if (!recoverUrl.isEmpty()) { //: %1 is the link to download it manually text += tr("Possible solution: Getting the latest version manually:
%1
") - .arg(QString("%1").arg(recover_url.toString())); + .arg(QString("%1").arg(recoverUrl.toString())); + } text += "
"; } - ScrollMessageBox message_dialog(m_parent, tr("Failed to check for updates"), - tr("Could not check or get the following resources for updates:
" - "Do you wish to proceed without those resources?"), - text, "Disable unavailable mods"); - message_dialog.setModal(true); - if (message_dialog.exec() == QDialog::Rejected) { + ScrollMessageBox messageDialog(m_parent, tr("Failed to check for updates"), + tr("Could not check or get the following resources for updates:
" + "Do you wish to proceed without those resources?"), + text, "Disable unavailable mods"); + messageDialog.setModal(true); + if (messageDialog.exec() == QDialog::Rejected) { m_aborted = true; QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } // Disable unavailable mods - if (message_dialog.isOptionChecked()) { + if (messageDialog.isOptionChecked()) { for (const auto& failed : m_failedCheckUpdate) { const auto& mod = std::get<0>(failed); mod->enable(EnableAction::DISABLE); @@ -196,10 +201,10 @@ void ResourceUpdateDialog::checkCandidates() } if (m_includeDeps && !APPLICATION->settings()->get("ModDependenciesDisabled").toBool()) { // dependencies - auto* mod_model = dynamic_cast(m_resourceModel); + auto* modModel = dynamic_cast(m_resourceModel); - if (mod_model != nullptr) { - auto depTask = makeShared(m_instance, mod_model, selectedVers); + if (modModel != nullptr) { + auto depTask = makeShared(m_instance, modModel, selectedVers); connect(depTask.get(), &Task::failed, this, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); @@ -215,10 +220,10 @@ void ResourceUpdateDialog::checkCandidates() } }); - ProgressDialog progress_dialog_deps(m_parent); - progress_dialog_deps.setSkipButton(true, tr("Abort")); - progress_dialog_deps.setWindowTitle(tr("Checking for dependencies...")); - auto dret = progress_dialog_deps.execWithTask(depTask.get()); + ProgressDialog progressDialogDeps(m_parent); + progressDialogDeps.setSkipButton(true, tr("Abort")); + progressDialogDeps.setWindowTitle(tr("Checking for dependencies...")); + auto dret = progressDialogDeps.execWithTask(depTask.get()); // If the dialog was skipped / some download error happened if (dret == QDialog::DialogCode::Rejected) { @@ -226,19 +231,20 @@ void ResourceUpdateDialog::checkCandidates() QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); return; } - static FlameAPI api; + static FlameAPI s_api; auto dependencyExtraInfo = depTask->getExtraInfo(); for (const auto& dep : depTask->getDependecies()) { auto changelog = dep->version.changelog; - if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) - changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); - auto download_task = makeShared(dep->pack, dep->version, m_resourceModel); + if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME) { + changelog = s_api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt()); + } + auto downloadTask = makeShared(dep->pack, dep->version, m_resourceModel, true, "dependency"); auto extraInfo = dependencyExtraInfo.value(dep->version.addonId.toString()); CheckUpdateTask::Update updatable = { dep->pack->name, dep->version.hash, tr("Not installed"), dep->version.version, dep->version.version_type, - changelog, dep->pack->provider, download_task, !extraInfo.maybe_installed + changelog, dep->pack->provider, downloadTask, !extraInfo.maybe_installed }; appendResource(updatable, extraInfo.required_by); @@ -264,56 +270,58 @@ void ResourceUpdateDialog::checkCandidates() } } - if (m_aborted || m_noUpdates) + if (m_aborted || m_noUpdates) { QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); + } } // Part 1: Ensure we have a valid metadata auto ResourceUpdateDialog::ensureMetadata() -> bool { - auto index_dir = indexDir(); + auto indexDir2 = indexDir(); SequentialTask seq(tr("Looking for metadata")); // A better use of data structures here could remove the need for this QHash - QHash should_try_others; - QList modrinth_tmp; - QList flame_tmp; + QHash shouldTryOthers; + QList modrinthTmp; + QList flameTmp; - bool confirm_rest = false; - bool try_others_rest = false; - bool skip_rest = false; - ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH; + bool confirmRest = false; + bool tryOthersRest = false; + bool skipRest = false; + ModPlatform::ResourceProvider providerRest = ModPlatform::ResourceProvider::MODRINTH; // adds resource to list based on provider - auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) { + auto addToTmp = [&modrinthTmp, &flameTmp](Resource* resource, ModPlatform::ResourceProvider p) { switch (p) { case ModPlatform::ResourceProvider::MODRINTH: - modrinth_tmp.push_back(resource); + modrinthTmp.push_back(resource); break; case ModPlatform::ResourceProvider::FLAME: - flame_tmp.push_back(resource); + flameTmp.push_back(resource); break; } }; // ask the user on what provider to seach for the mod first - for (auto candidate : m_candidates) { - if (candidate->status() != ResourceStatus::NO_METADATA) { + for (auto* candidate : m_candidates) { + if (candidate->status() != ResourceStatus::NoMetadata) { onMetadataEnsured(candidate); continue; } - if (skip_rest) + if (skipRest) { continue; + } if (candidate->type() == ResourceType::FOLDER) { continue; } - if (confirm_rest) { - addToTmp(candidate, provider_rest); - should_try_others.insert(candidate->internal_id(), try_others_rest); + if (confirmRest) { + addToTmp(candidate, providerRest); + shouldTryOthers.insert(candidate->internalId(), tryOthersRest); continue; } @@ -326,68 +334,73 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool auto response = chooser.getResponse(); - if (response.skip_all) - skip_rest = true; + if (response.skip_all) { + skipRest = true; + } if (response.confirm_all) { - confirm_rest = true; - provider_rest = response.chosen; - try_others_rest = response.try_others; + confirmRest = true; + providerRest = response.chosen; + tryOthersRest = response.try_others; } - should_try_others.insert(candidate->internal_id(), response.try_others); + shouldTryOthers.insert(candidate->internalId(), response.try_others); - if (confirmed) + if (confirmed) { addToTmp(candidate, response.chosen); + } } // prepare task for the modrinth mods - if (!modrinth_tmp.empty()) { - auto modrinth_task = makeShared(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(modrinth_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::MODRINTH); + if (!modrinthTmp.empty()) { + auto modrinthTask = makeShared(modrinthTmp, indexDir2, ModPlatform::ResourceProvider::MODRINTH); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(modrinthTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::MODRINTH); }); - connect(modrinth_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(modrinthTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (modrinth_task->getHashingTask()) - seq.addTask(modrinth_task->getHashingTask()); + if (modrinthTask->getHashingTask()) { + seq.addTask(modrinthTask->getHashingTask()); + } - seq.addTask(modrinth_task); + seq.addTask(modrinthTask); } // prepare task for the flame mods - if (!flame_tmp.empty()) { - auto flame_task = makeShared(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME); - connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); - connect(flame_task.get(), &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Resource* candidate) { - onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::ResourceProvider::FLAME); + if (!flameTmp.empty()) { + auto flameTask = makeShared(flameTmp, indexDir2, ModPlatform::ResourceProvider::FLAME); + connect(flameTask.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); + connect(flameTask.get(), &EnsureMetadataTask::metadataFailed, [this, &shouldTryOthers](Resource* candidate) { + onMetadataFailed(candidate, shouldTryOthers.find(candidate->internalId()).value(), ModPlatform::ResourceProvider::FLAME); }); - connect(flame_task.get(), &EnsureMetadataTask::failed, - [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); + connect(flameTask.get(), &EnsureMetadataTask::failed, + [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); }); - if (flame_task->getHashingTask()) - seq.addTask(flame_task->getHashingTask()); + if (flameTask->getHashingTask()) { + seq.addTask(flameTask->getHashingTask()); + } - seq.addTask(flame_task); + seq.addTask(flameTask); } seq.addTask(m_secondTryMetadata); // execute all the tasks - ProgressDialog checking_dialog(m_parent); - checking_dialog.setSkipButton(true, tr("Abort")); - checking_dialog.setWindowTitle(tr("Generating metadata...")); - auto ret_metadata = checking_dialog.execWithTask(&seq); + ProgressDialog checkingDialog(m_parent); + checkingDialog.setSkipButton(true, tr("Abort")); + checkingDialog.setWindowTitle(tr("Generating metadata...")); + auto retMetadata = checkingDialog.execWithTask(&seq); - return (ret_metadata != QDialog::DialogCode::Rejected); + return (retMetadata != QDialog::DialogCode::Rejected); } void ResourceUpdateDialog::onMetadataEnsured(Resource* resource) { // When the mod is a folder, for instance - if (!resource->metadata()) + if (!resource->metadata()) { return; + } switch (resource->metadata()->provider) { case ModPlatform::ResourceProvider::MODRINTH: @@ -411,12 +424,12 @@ ModPlatform::ResourceProvider next(ModPlatform::ResourceProvider p) return ModPlatform::ResourceProvider::FLAME; } -void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, ModPlatform::ResourceProvider first_choice) +void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool tryOthers, ModPlatform::ResourceProvider firstChoice) { - if (try_others) { - auto index_dir = indexDir(); + if (tryOthers) { + auto indexDir2 = indexDir(); - auto task = makeShared(resource, index_dir, next(first_choice)); + auto task = makeShared(resource, indexDir2, next(firstChoice)); connect(task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); }); connect(task.get(), &EnsureMetadataTask::metadataFailed, [this](Resource* candidate) { onMetadataFailed(candidate, false); }); connect(task.get(), &EnsureMetadataTask::failed, @@ -436,57 +449,57 @@ void ResourceUpdateDialog::onMetadataFailed(Resource* resource, bool try_others, } } -void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, QStringList requiredBy) +void ResourceUpdateDialog::appendResource(const CheckUpdateTask::Update& info, QStringList requiredBy) { - auto item_top = new QTreeWidgetItem(ui->modTreeWidget); - item_top->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); + auto* itemTop = new QTreeWidgetItem(ui->modTreeWidget); + itemTop->setCheckState(0, info.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); if (!info.enabled) { - item_top->setToolTip(0, tr("Mod was disabled as it may be already installed.")); + itemTop->setToolTip(0, tr("Mod was disabled as it may be already installed.")); } - item_top->setText(0, info.name); - item_top->setExpanded(true); + itemTop->setText(0, info.name); + itemTop->setExpanded(true); - auto provider_item = new QTreeWidgetItem(item_top); - QString provider_name = ModPlatform::ProviderCapabilities::readableName(info.provider); - provider_item->setText(0, tr("Provider: %1").arg(provider_name)); - provider_item->setData(0, Qt::UserRole, provider_name); + auto* providerItem = new QTreeWidgetItem(itemTop); + QString providerName = ModPlatform::ProviderCapabilities::readableName(info.provider); + providerItem->setText(0, tr("Provider: %1").arg(providerName)); + providerItem->setData(0, Qt::UserRole, providerName); - auto old_version_item = new QTreeWidgetItem(item_top); - old_version_item->setText(0, tr("Old version: %1").arg(info.old_version)); - old_version_item->setData(0, Qt::UserRole, info.old_version); + auto* oldVersionItem = new QTreeWidgetItem(itemTop); + oldVersionItem->setText(0, tr("Old version: %1").arg(info.oldVersion)); + oldVersionItem->setData(0, Qt::UserRole, info.oldVersion); - auto new_version_item = new QTreeWidgetItem(item_top); - new_version_item->setText(0, tr("New version: %1").arg(info.new_version)); - new_version_item->setData(0, Qt::UserRole, info.new_version); + auto* newVersionItem = new QTreeWidgetItem(itemTop); + newVersionItem->setText(0, tr("New version: %1").arg(info.newVersion)); + newVersionItem->setData(0, Qt::UserRole, info.newVersion); - if (info.new_version_type.has_value()) { - auto new_version_type_item = new QTreeWidgetItem(item_top); - new_version_type_item->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString())); - new_version_type_item->setData(0, Qt::UserRole, info.new_version_type.value().toString()); + if (info.newVersionType.has_value()) { + auto* newVersionTypeItem = new QTreeWidgetItem(itemTop); + newVersionTypeItem->setText(0, tr("New Version Type: %1").arg(info.newVersionType.value().toString())); + newVersionTypeItem->setData(0, Qt::UserRole, info.newVersionType.value().toString()); } if (!requiredBy.isEmpty()) { - auto requiredByItem = new QTreeWidgetItem(item_top); + auto* requiredByItem = new QTreeWidgetItem(itemTop); if (requiredBy.length() == 1) { requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back())); requiredByItem->setData(0, Qt::UserRole, requiredBy.back()); } else { requiredByItem->setText(0, tr("Required by:")); - for (auto req : requiredBy) { - auto reqItem = new QTreeWidgetItem(requiredByItem); + for (const auto& req : requiredBy) { + auto* reqItem = new QTreeWidgetItem(requiredByItem); reqItem->setText(0, req); } } ui->toggleDepsButton->show(); - m_deps << item_top; + m_deps << itemTop; } - auto changelog_item = new QTreeWidgetItem(item_top); - changelog_item->setText(0, tr("Changelog of the latest version")); + auto* changelogItem = new QTreeWidgetItem(itemTop); + changelogItem->setText(0, tr("Changelog of the latest version")); - auto changelog = new QTreeWidgetItem(changelog_item); - auto changelog_area = new QTextBrowser(); + auto* changelog = new QTreeWidgetItem(changelogItem); + auto* changelogArea = new QTextBrowser(); QString text = info.changelog; changelog->setData(0, Qt::UserRole, text); @@ -494,14 +507,14 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q text = markdownToHTML(info.changelog.toUtf8()); } - changelog_area->setHtml(StringUtils::htmlListPatch(text)); - changelog_area->setOpenExternalLinks(true); - changelog_area->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); - changelog_area->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + changelogArea->setHtml(StringUtils::htmlListPatch(text)); + changelogArea->setOpenExternalLinks(true); + changelogArea->setLineWrapMode(QTextBrowser::LineWrapMode::WidgetWidth); + changelogArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); - ui->modTreeWidget->setItemWidget(changelog, 0, changelog_area); + ui->modTreeWidget->setItemWidget(changelog, 0, changelogArea); - ui->modTreeWidget->addTopLevelItem(item_top); + ui->modTreeWidget->addTopLevelItem(itemTop); } auto ResourceUpdateDialog::getTasks() -> const QList diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.h b/launcher/ui/dialogs/ResourceUpdateDialog.h index ea81aeb7a..9de1ee246 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.h +++ b/launcher/ui/dialogs/ResourceUpdateDialog.h @@ -39,7 +39,7 @@ class ResourceUpdateDialog final : public ReviewMessageBox { private slots: void onMetadataEnsured(Resource* resource); void onMetadataFailed(Resource* resource, - bool try_others = false, + bool tryOthers = false, ModPlatform::ResourceProvider firstChoice = ModPlatform::ResourceProvider::MODRINTH); private: diff --git a/launcher/ui/dialogs/VersionSelectDialog.cpp b/launcher/ui/dialogs/VersionSelectDialog.cpp index 30377288b..db9b08096 100644 --- a/launcher/ui/dialogs/VersionSelectDialog.cpp +++ b/launcher/ui/dialogs/VersionSelectDialog.cpp @@ -69,7 +69,7 @@ VersionSelectDialog::VersionSelectDialog(BaseVersionList* vlist, QString title, m_buttonBox->setOrientation(Qt::Horizontal); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); - m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Ok")); + m_buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK")); m_buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); m_horizontalLayout->addWidget(m_buttonBox); @@ -144,7 +144,7 @@ BaseVersion::Ptr VersionSelectDialog::selectedVersion() const void VersionSelectDialog::on_refreshButton_clicked() { - m_versionWidget->loadList(); + m_versionWidget->loadList(true); } void VersionSelectDialog::setExactFilter(BaseVersionList::ModelRoles role, QString filter) diff --git a/launcher/ui/instanceview/InstanceProxyModel.cpp b/launcher/ui/instanceview/InstanceProxyModel.cpp index ab6bef696..f28149a56 100644 --- a/launcher/ui/instanceview/InstanceProxyModel.cpp +++ b/launcher/ui/instanceview/InstanceProxyModel.cpp @@ -62,6 +62,11 @@ bool InstanceProxyModel::subSortLessThan(const QModelIndex& left, const QModelIn QString sortMode = APPLICATION->settings()->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { return pdataLeft->lastLaunch() > pdataRight->lastLaunch(); + } else if (sortMode == "Playtime") { + if (pdataLeft->totalTimePlayed() == pdataRight->totalTimePlayed()) { + return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; + } + return pdataLeft->totalTimePlayed() > pdataRight->totalTimePlayed(); } else { return m_naturalSort.compare(pdataLeft->name(), pdataRight->name()) < 0; } diff --git a/launcher/ui/instanceview/InstanceView.cpp b/launcher/ui/instanceview/InstanceView.cpp index aff61cb3b..9a24b7990 100644 --- a/launcher/ui/instanceview/InstanceView.cpp +++ b/launcher/ui/instanceview/InstanceView.cpp @@ -511,8 +511,7 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) int wpWidth = viewport()->width(); option.rect.setWidth(wpWidth); - for (int i = 0; i < m_groups.size(); ++i) { - VisualGroup* category = m_groups.at(i); + for (auto* category : m_groups) { int y = category->verticalPosition(); y -= verticalOffset(); QRect backup = option.rect; @@ -522,7 +521,6 @@ void InstanceView::paintEvent([[maybe_unused]] QPaintEvent* event) option.rect.setLeft(m_leftMargin); option.rect.setRight(wpWidth - m_rightMargin); category->drawHeader(&painter, option); - y += category->totalHeight() + m_categoryMargin; option.rect = backup; } diff --git a/launcher/ui/java/InstallJavaDialog.cpp b/launcher/ui/java/InstallJavaDialog.cpp index 81ffcfa83..fd8e43398 100644 --- a/launcher/ui/java/InstallJavaDialog.cpp +++ b/launcher/ui/java/InstallJavaDialog.cpp @@ -121,8 +121,8 @@ class InstallJavaPage : public QWidget, public BasePage { void selectSearch() { javaVersionSelect->selectSearch(); } void loadList() { - majorVersionSelect->loadList(); - javaVersionSelect->loadList(); + majorVersionSelect->loadList(true); + javaVersionSelect->loadList(true); } public slots: diff --git a/launcher/ui/java/VersionList.cpp b/launcher/ui/java/VersionList.cpp index f958f064f..7a4e84f33 100644 --- a/launcher/ui/java/VersionList.cpp +++ b/launcher/ui/java/VersionList.cpp @@ -33,9 +33,9 @@ VersionList::VersionList(Meta::Version::Ptr version, QObject* parent) : BaseVers sortVersions(); } -Task::Ptr VersionList::getLoadTask() +Task::Ptr VersionList::getLoadTask(bool forceReload) { - auto task = m_version->loadTask(Net::Mode::Online); + auto task = m_version->loadTask(Net::Mode::Online, forceReload); connect(task.get(), &Task::finished, this, &VersionList::sortVersions); return task; } diff --git a/launcher/ui/java/VersionList.h b/launcher/ui/java/VersionList.h index d334ed564..cf8a7448d 100644 --- a/launcher/ui/java/VersionList.h +++ b/launcher/ui/java/VersionList.h @@ -30,7 +30,7 @@ class VersionList : public BaseVersionList { public: explicit VersionList(Meta::Version::Ptr m_version, QObject* parent = 0); - Task::Ptr getLoadTask() override; + Task::Ptr getLoadTask(bool forceReload = false) override; bool isLoaded() override; const BaseVersion::Ptr at(int i) const override; int count() const override; diff --git a/launcher/ui/pages/global/APIPage.cpp b/launcher/ui/pages/global/APIPage.cpp index c399df469..4ed05f98e 100644 --- a/launcher/ui/pages/global/APIPage.cpp +++ b/launcher/ui/pages/global/APIPage.cpp @@ -143,6 +143,7 @@ void APIPage::loadSettings() ui->msaClientID->setText(msaClientID); QString metaURL = s->get("MetaURLOverride").toString(); ui->metaURL->setText(metaURL); + ui->metaRefreshOnLaunchCB->setCheckState(s->get("MetaRefreshOnLaunch").toBool() ? Qt::Checked : Qt::Unchecked); QString resourceURL = s->get("ResourceURLOverride").toString(); ui->resourceURL->setText(resourceURL); QString fmlLibsURL = s->get("LegacyFMLLibsURLOverride").toString(); @@ -194,6 +195,7 @@ void APIPage::applySettings() s->set("FallbackMRBlockedMods", ui->FallbackMRBlockedMods->checkState()); s->set("MetaURLOverride", metaURL.toString()); + s->set("MetaRefreshOnLaunch", ui->metaRefreshOnLaunchCB->checkState() == Qt::Checked); s->set("ResourceURLOverride", resourceURL.toString()); s->set("LegacyFMLLibsURLOverride", fmlLibsURL.toString()); QString flameKey = ui->flameKey->text(); diff --git a/launcher/ui/pages/global/APIPage.ui b/launcher/ui/pages/global/APIPage.ui index 7d759d256..25d78a04d 100644 --- a/launcher/ui/pages/global/APIPage.ui +++ b/launcher/ui/pages/global/APIPage.ui @@ -32,9 +32,9 @@ 0 - -262 - 820 - 908 + 0 + 825 + 1236 @@ -126,6 +126,13 @@
+ + + + Refresh on launch + + +
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index d6d15a2c4..1e1fed8f8 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -62,7 +62,9 @@ enum InstSortMode { // Sort alphabetically by name. Sort_Name, // Sort by which instance was launched most recently. - Sort_LastLaunch + Sort_LastLaunch, + // Sort by which instance has the most playtime. + Sort_Playtime, }; LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage) @@ -71,6 +73,7 @@ LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::Launch ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name); ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch); + ui->sortingModeGroup->setId(ui->sortByPlaytimeBtn, Sort_Playtime); loadSettings(); @@ -90,12 +93,12 @@ bool LauncherPage::apply() void LauncherPage::on_instDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - if (FS::checkProblemticPathJava(QDir(cooked_dir))) { + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + if (FS::checkProblemticPathJava(QDir(cookedDir))) { QMessageBox warning; warning.setText( tr("You're trying to specify an instance folder which\'s path " @@ -108,9 +111,9 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } - } else if (DesktopServices::isFlatpak() && raw_dir.startsWith("/run/user")) { + } else if (DesktopServices::isFlatpak() && rawDir.startsWith("/run/user")) { QMessageBox warning; warning.setText(tr("You're trying to specify an instance folder " "which was granted temporarily via Flatpak.\n" @@ -123,64 +126,64 @@ void LauncherPage::on_instDirBrowseBtn_clicked() warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); int result = warning.exec(); if (result == QMessageBox::Ok) { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } } else { - ui->instDirTextBox->setText(cooked_dir); + ui->instDirTextBox->setText(cookedDir); } } } void LauncherPage::on_iconsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->iconsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->iconsDirTextBox->setText(cookedDir); } } void LauncherPage::on_modsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->modsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->modsDirTextBox->setText(cookedDir); } } void LauncherPage::on_downloadsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Downloads Folder"), ui->downloadsDirTextBox->text()); - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->downloadsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->downloadsDirTextBox->setText(cookedDir); } } void LauncherPage::on_javaDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Java Folder"), ui->javaDirTextBox->text()); - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->javaDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->javaDirTextBox->setText(cookedDir); } } void LauncherPage::on_skinsDirBrowseBtn_clicked() { - QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); + QString rawDir = QFileDialog::getExistingDirectory(this, tr("Skins Folder"), ui->skinsDirTextBox->text()); // do not allow current dir - it's dirty. Do not allow dirs that don't exist - if (!raw_dir.isEmpty() && QDir(raw_dir).exists()) { - QString cooked_dir = FS::NormalizePath(raw_dir); - ui->skinsDirTextBox->setText(cooked_dir); + if (!rawDir.isEmpty() && QDir(rawDir).exists()) { + QString cookedDir = FS::NormalizePath(rawDir); + ui->skinsDirTextBox->setText(cookedDir); } } @@ -191,7 +194,7 @@ void LauncherPage::on_metadataEnableBtn_clicked() void LauncherPage::applySettings() { - auto s = APPLICATION->settings(); + auto* s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { @@ -227,6 +230,9 @@ void LauncherPage::applySettings() case Sort_LastLaunch: s->set("InstSortMode", "LastLaunch"); break; + case Sort_Playtime: + s->set("InstSortMode", "Playtime"); + break; case Sort_Name: default: s->set("InstSortMode", "Name"); @@ -246,10 +252,11 @@ void LauncherPage::applySettings() s->set("ModDependenciesDisabled", !ui->dependenciesEnableBtn->isChecked()); s->set("ShowModIncompat", ui->showModIncompatCheckBox->isChecked()); s->set("SkipModpackUpdatePrompt", !ui->modpackUpdatePromptBtn->isChecked()); + s->set("DownloadGameFilesDuringInstanceCreation", ui->downloadGameFilesBtn->isChecked()); } void LauncherPage::loadSettings() { - auto s = APPLICATION->settings(); + auto* s = APPLICATION->settings(); // Updates if (APPLICATION->updater()) { ui->autoUpdateCheckBox->setChecked(APPLICATION->updater()->getAutomaticallyChecksForUpdates()); @@ -281,6 +288,8 @@ void LauncherPage::loadSettings() QString sortMode = s->get("InstSortMode").toString(); if (sortMode == "LastLaunch") { ui->sortLastLaunchedBtn->setChecked(true); + } else if (sortMode == "Playtime"){ + ui->sortByPlaytimeBtn->setChecked(true); } else { ui->sortByNameBtn->setChecked(true); } @@ -296,6 +305,7 @@ void LauncherPage::loadSettings() ui->dependenciesEnableBtn->setChecked(!s->get("ModDependenciesDisabled").toBool()); ui->showModIncompatCheckBox->setChecked(s->get("ShowModIncompat").toBool()); ui->modpackUpdatePromptBtn->setChecked(!s->get("SkipModpackUpdatePrompt").toBool()); + ui->downloadGameFilesBtn->setChecked(s->get("DownloadGameFilesDuringInstanceCreation").toBool()); } void LauncherPage::retranslate() diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index f5cfacf96..c98cb1032 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -83,6 +83,16 @@ + + + + By total time &played + + + sortingModeGroup + + + @@ -251,12 +261,12 @@ - - &Auto Java Download: - Folder where Prism Launcher stores automatically downloaded Java versions. Do NOT set this to your system Java installation. + + &Auto Java Download: + javaDirTextBox @@ -444,6 +454,25 @@ + + + + Instance Creation + + + + + + Downloads required game files while creating the instance. Disable this to skip the initial download and fetch files when the instance is launched instead. + + + Download game files during instance creation + + + + + + @@ -654,6 +683,12 @@ scrollArea + sortByNameBtn + sortLastLaunchedBtn + sortByPlaytimeBtn + askToRenameDirBtn + alwaysRenameDirBtn + neverRenameDirBtn preferMenuBarCheckBox autoUpdateCheckBox updateIntervalSpinBox @@ -673,7 +708,9 @@ downloadsDirMoveCheckBox metadataEnableBtn dependenciesEnableBtn + showModIncompatCheckBox modpackUpdatePromptBtn + downloadGameFilesBtn lineLimitSpinBox checkStopLogging numberOfConcurrentTasksSpinBox diff --git a/launcher/ui/pages/instance/DataPackPage.cpp b/launcher/ui/pages/instance/DataPackPage.cpp index fb07a768b..eb59fbb1e 100644 --- a/launcher/ui/pages/instance/DataPackPage.cpp +++ b/launcher/ui/pages/instance/DataPackPage.cpp @@ -39,9 +39,9 @@ DataPackPage::DataPackPage(BaseInstance* instance, DataPackFolderModel* model, Q connect(ui->actionUpdateItem, &QAction::triggered, this, &DataPackPage::updateDataPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &DataPackPage::updateDataPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -64,8 +64,9 @@ void DataPackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] cons void DataPackPage::downloadDataPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::DataPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -76,9 +77,9 @@ void DataPackPage::downloadDataPacks() void DataPackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask(tr("Download Data Packs"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -88,8 +89,9 @@ void DataPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -108,14 +110,16 @@ void DataPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void DataPackPage::updateDataPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); @@ -130,27 +134,29 @@ void DataPackPage::updateDataPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false, { ModPlatform::ModLoaderType::DataPack }); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false, { ModPlatform::ModLoaderType::DataPack }); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The data pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All data packs are up-to-date! :)"); } else { message = tr("All selected data packs are up-to-date! :)"); @@ -160,9 +166,9 @@ void DataPackPage::updateDataPacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -178,7 +184,7 @@ void DataPackPage::updateDataPacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -194,8 +200,9 @@ void DataPackPage::deleteDataPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedDataPacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 data packs.\n" @@ -204,8 +211,9 @@ void DataPackPage::deleteDataPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -213,8 +221,9 @@ void DataPackPage::deleteDataPackMetadata() void DataPackPage::changeDataPackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Data pack updates are unavailable when metadata is disabled!")); @@ -223,19 +232,21 @@ void DataPackPage::changeDataPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } - ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance); + ResourceDownload::DataPackDownloadDialog mdownload(this, m_model, m_instance, true); mdownload.setResourceMetadata(resource.metadata()); - if (mdownload.exec()) { - auto tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (mdownload.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Data Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -245,8 +256,9 @@ void DataPackPage::changeDataPackVersion() }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -265,14 +277,15 @@ void DataPackPage::changeDataPackVersion() GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* parent) : QWidget(parent), m_instance(instance) { - auto layout = new QVBoxLayout(this); + auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); connect(instance->settings()->getSetting("GlobalDataPacksEnabled").get(), &Setting::SettingChanged, this, [this] { updateContent(); - if (m_container != nullptr) + if (m_container != nullptr) { m_container->refreshContainer(); + } }); connect(instance->settings()->getSetting("GlobalDataPacksPath").get(), &Setting::SettingChanged, this, @@ -281,24 +294,27 @@ GlobalDataPackPage::GlobalDataPackPage(MinecraftInstance* instance, QWidget* par QString GlobalDataPackPage::displayName() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->displayName(); } QIcon GlobalDataPackPage::icon() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->icon(); } QString GlobalDataPackPage::helpPage() const { - if (m_underlyingPage == nullptr) + if (m_underlyingPage == nullptr) { return {}; + } return m_underlyingPage->helpPage(); } @@ -315,21 +331,24 @@ bool GlobalDataPackPage::apply() void GlobalDataPackPage::openedImpl() { - if (m_underlyingPage != nullptr) + if (m_underlyingPage != nullptr) { m_underlyingPage->openedImpl(); + } } void GlobalDataPackPage::closedImpl() { - if (m_underlyingPage != nullptr) + if (m_underlyingPage != nullptr) { m_underlyingPage->closedImpl(); + } } void GlobalDataPackPage::updateContent() { if (m_underlyingPage != nullptr) { - if (m_container->selectedPage() == this) + if (m_container->selectedPage() == this) { m_underlyingPage->closedImpl(); + } m_underlyingPage->apply(); @@ -344,8 +363,9 @@ void GlobalDataPackPage::updateContent() m_underlyingPage->setParentContainer(m_container); m_underlyingPage->updateExtraInfo = [this](QString id, QString value) { updateExtraInfo(std::move(id), std::move(value)); }; - if (m_container->selectedPage() == this) + if (m_container->selectedPage() == this) { m_underlyingPage->openedImpl(); + } layout()->addWidget(m_underlyingPage); } diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index 777dfbaa1..d2683fa92 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -128,7 +128,7 @@ ManagedPackPage::ManagedPackPage(BaseInstance* inst, InstanceWindow* instance_wi QDesktopServices::openUrl(url); }); - connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text); }); + connect(ui->urlLine, &QLineEdit::textChanged, this, [this](QString text) { m_inst->settings()->set("ManagedPackURL", text.trimmed()); }); } ManagedPackPage::~ManagedPackPage() @@ -147,7 +147,7 @@ void ManagedPackPage::openedImpl() ui->updateToVersionLabel->setText(tr("URL:")); ui->updateButton->setText(tr("Update Pack")); ui->updateButton->setDisabled(false); - ui->urlLine->setText(m_inst->settings()->get("ManagedPackURL").toString()); + ui->urlLine->setText(m_inst->settings()->get("ManagedPackURL").toString().trimmed()); ui->packName->setText(m_inst->name()); ui->changelogTextBrowser->setText(tr("This is a local modpack.\n" @@ -357,7 +357,7 @@ void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const void ModrinthManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString(); + auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { updatePack(customURL); return; @@ -486,7 +486,7 @@ void FlameManagedPackPage::suggestVersion() void FlameManagedPackPage::update() { - auto customURL = m_inst->settings()->get("ManagedPackURL").toString(); + auto customURL = m_inst->settings()->get("ManagedPackURL").toString().trimmed(); if (m_inst->getManagedPackID().isEmpty() && !customURL.isEmpty()) { updatePack(customURL); return; diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 7ba72a9b0..99c78647c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -81,9 +81,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, ModFolderModel* model, QWidget* connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(tr("Check for Updates")); + auto* update = updateMenu->addAction(tr("Check for Updates")); connect(update, &QAction::triggered, this, &ModFolderPage::updateMods); updateMenu->addAction(ui->actionVerifyItemDependencies); @@ -134,8 +134,9 @@ void ModFolderPage::removeItems(const QItemSelection& selection) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto indexes = selection.indexes(); @@ -161,10 +162,11 @@ void ModFolderPage::removeItems(const QItemSelection& selection) void ModFolderPage::downloadMods() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -180,9 +182,9 @@ void ModFolderPage::downloadMods() void ModFolderPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask(tr("Download Mods"), APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -192,8 +194,9 @@ void ModFolderPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -212,16 +215,18 @@ void ModFolderPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ModFolderPage::updateMods(bool includeDeps) { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -240,27 +245,29 @@ void ModFolderPage::updateMods(bool includeDeps) QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, includeDeps, profile->getModLoadersList()); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, includeDeps, profile->getModLoadersList()); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The mod updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All mods are up-to-date! :)"); } else { message = tr("All selected mods are up-to-date! :)"); @@ -270,9 +277,9 @@ void ModFolderPage::updateMods(bool includeDeps) return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -288,7 +295,7 @@ void ModFolderPage::updateMods(bool includeDeps) tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -304,8 +311,9 @@ void ModFolderPage::deleteModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedMods(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 mods.\n" @@ -314,8 +322,9 @@ void ModFolderPage::deleteModMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -323,10 +332,11 @@ void ModFolderPage::deleteModMetadata() void ModFolderPage::changeModVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } - auto profile = static_cast(m_instance)->getPackProfile(); + auto* profile = static_cast(m_instance)->getPackProfile(); if (!profile->getModLoaders().has_value()) { if (handleNoModLoader()) { return; @@ -337,15 +347,16 @@ void ModFolderPage::changeModVersion() return; } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedMods(selection); - if (mods_list.length() != 1 || mods_list[0]->metadata() == nullptr) + auto modsList = m_model->selectedMods(selection); + if (modsList.length() != 1 || modsList[0]->metadata() == nullptr) { return; + } - m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ModDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ModFolderPage::downloadDialogFinished); - m_downloadDialog->setResourceMetadata((*mods_list.begin())->metadata()); + m_downloadDialog->setResourceMetadata((*modsList.begin())->metadata()); m_downloadDialog->open(); } @@ -353,20 +364,21 @@ void ModFolderPage::exportModMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectedMods = m_model->selectedMods(selection); - if (selectedMods.length() == 0) + if (selectedMods.length() == 0) { selectedMods = m_model->allMods(); + } - std::sort(selectedMods.begin(), selectedMods.end(), [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); + std::ranges::sort(selectedMods, [](const Mod* a, const Mod* b) { return a->name() < b->name(); }); ExportToModListDialog dlg(m_instance->name(), selectedMods, this); dlg.exec(); } CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, QWidget* parent) : ModFolderPage(inst, mods, parent) { - auto mcInst = dynamic_cast(m_instance); + auto* mcInst = dynamic_cast(m_instance); if (mcInst) { - auto version = mcInst->getPackProfile(); - if (version && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { + auto* version = mcInst->getPackProfile(); + if ((version != nullptr) && version->getComponent("net.minecraftforge") && version->getComponent("net.minecraft")) { auto minecraftCmp = version->getComponent("net.minecraft"); if (!minecraftCmp->m_loaded) { version->reload(Net::Mode::Offline); @@ -389,13 +401,15 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, ModFolderModel* mods, Q bool CoreModFolderPage::shouldDisplay() const { if (ModFolderPage::shouldDisplay()) { - auto inst = dynamic_cast(m_instance); - if (!inst) + auto* inst = dynamic_cast(m_instance); + if (!inst) { return true; + } - auto version = inst->getPackProfile(); - if (!version || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) + auto* version = inst->getPackProfile(); + if ((version == nullptr) || !version->getComponent("net.minecraftforge") || !version->getComponent("net.minecraft")) { return false; + } auto minecraftCmp = version->getComponent("net.minecraft"); return minecraftCmp->m_loaded && minecraftCmp->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate; } @@ -412,31 +426,22 @@ bool NilModFolderPage::shouldDisplay() const // Helper function so this doesn't need to be duplicated 3 times inline bool ModFolderPage::handleNoModLoader() { - int resp = - QMessageBox::question(this, this->tr("Missing Mod Loader"), - this->tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - switch (resp) { - case QMessageBox::Yes: { - // Should be safe - auto profile = static_cast(this->m_instance)->getPackProfile(); - InstallLoaderDialog dialog(profile, QString(), this); - bool ret = dialog.exec(); - this->m_container->refreshContainer(); + int resp = QMessageBox::question( + this, ModFolderPage::tr("Missing Mod Loader"), + ModFolderPage::tr("You need to install a compatible mod loader before installing mods. Would you like to do so?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (resp == QMessageBox::Yes) { + // Should be safe + auto* profile = static_cast(this->m_instance)->getPackProfile(); + InstallLoaderDialog dialog(profile, QString(), this); + bool ret = dialog.exec() != 0; + this->m_container->refreshContainer(); - // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed - // and false if the user went through and installed a loader - return !ret; - } - case QMessageBox::No: { - // Nothing happens the dialog is already closing - // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader - return true; - } - default: { - // Unreachable - // returning true as a safety measure - return true; - } + // returning negation of dialog.exec which'll be true if the install loader dialog got canceled/closed + // and false if the user went through and installed a loader + return !ret; } + // Nothing happens the dialog is already closing + // returning true so the caller doesn't go and continue with opening it's dialog without a mod loader + return true; } diff --git a/launcher/ui/pages/instance/OtherLogsPage.cpp b/launcher/ui/pages/instance/OtherLogsPage.cpp index 19a9db04f..b9f943777 100644 --- a/launcher/ui/pages/instance/OtherLogsPage.cpp +++ b/launcher/ui/pages/instance/OtherLogsPage.cpp @@ -277,12 +277,15 @@ void OtherLogsPage::reload() MessageLevel last = MessageLevel::Unknown; auto handleLine = [this, &last](QString line) { - if (line.isEmpty()) + if (!line.isEmpty() && line.back() == '\n') { + line.resize(line.size() - 1); + } + if (!line.isEmpty() && line.back() == '\r') { + line.resize(line.size() - 1); + } + if (line.isEmpty()) { return false; - if (line.back() == '\n') - line.resize(line.size() - 1); - if (line.back() == '\r') - line.resize(line.size() - 1); + } MessageLevel level = MessageLevel::Unknown; QString lineTemp = line; // don't edit out the time and level for clarity diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp index eb085e29b..e4709ab2b 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.cpp +++ b/launcher/ui/pages/instance/ResourcePackPage.cpp @@ -56,9 +56,9 @@ ResourcePackPage::ResourcePackPage(MinecraftInstance* instance, ResourcePackFold connect(ui->actionUpdateItem, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ResourcePackPage::updateResourcePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -75,14 +75,15 @@ void ResourcePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); + auto& rp = m_model->at(row); ui->frame->updateWithResourcePack(rp); } void ResourcePackPage::downloadResourcePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -93,9 +94,9 @@ void ResourcePackPage::downloadResourcePacks() void ResourcePackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -105,8 +106,9 @@ void ResourcePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -125,14 +127,16 @@ void ResourcePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ResourcePackPage::updateResourcePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); @@ -147,27 +151,29 @@ void ResourcePackPage::updateResourcePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The resource pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All resource packs are up-to-date! :)"); } else { message = tr("All selected resource packs are up-to-date! :)"); @@ -177,9 +183,9 @@ void ResourcePackPage::updateResourcePacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Resource Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -195,7 +201,7 @@ void ResourcePackPage::updateResourcePacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -211,8 +217,9 @@ void ResourcePackPage::deleteResourcePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedResourcePacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 resource packs.\n" @@ -221,8 +228,9 @@ void ResourcePackPage::deleteResourcePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -230,8 +238,9 @@ void ResourcePackPage::deleteResourcePackMetadata() void ResourcePackPage::changeResourcePackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Resource pack updates are unavailable when metadata is disabled!")); @@ -240,15 +249,17 @@ void ResourcePackPage::changeResourcePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } - m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ResourcePackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ResourcePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index 70647bed7..71dc7218e 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -53,6 +52,8 @@ #include #include #include +#include +#include #include #include "settings/SettingsObject.h" @@ -70,9 +71,14 @@ #include "RWStorage.h" class ScreenshotsFSModel : public QFileSystemModel { - bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override + public: + bool canDropMimeData(const QMimeData* data, + const Qt::DropAction action, + const int row, + const int column, + const QModelIndex& parent) const override { - QUrl root = QUrl::fromLocalFile(rootPath()); + const QUrl root = QUrl::fromLocalFile(rootPath()); // this disables reordering items inside the model // by rejecting drops if the file is already inside the folder if (data->hasUrls()) { @@ -92,8 +98,8 @@ using SharedIconCachePtr = std::shared_ptr; class ThumbnailingResult : public QObject { Q_OBJECT public slots: - inline void emitResultsReady(const QString& path) { emit resultsReady(path); } - inline void emitResultsFailed(const QString& path) { emit resultsFailed(path); } + void emitResultsReady(const QString& path) { emit resultsReady(path); } + void emitResultsFailed(const QString& path) { emit resultsFailed(path); } signals: void resultsReady(const QString& path); void resultsFailed(const QString& path); @@ -101,32 +107,32 @@ class ThumbnailingResult : public QObject { class ThumbnailRunnable : public QRunnable { public: - ThumbnailRunnable(QString path, SharedIconCachePtr cache) + ThumbnailRunnable(QString path, SharedIconCachePtr cache) : m_path(std::move(path)), m_cache(std::move(cache)) {} + void run() override { - m_path = path; - m_cache = cache; - } - void run() - { - QFileInfo info(m_path); - if (info.isDir()) + const QFileInfo info(m_path); + if (info.isDir()) { return; - if ((info.suffix().compare("png", Qt::CaseInsensitive) != 0)) + } + if (info.suffix().compare("png", Qt::CaseInsensitive) != 0) { return; - if (!m_cache->stale(m_path)) + } + if (!m_cache->stale(m_path)) { return; - QImage image(m_path); + } + const QImage image(m_path); if (image.isNull()) { m_resultEmitter.emitResultsFailed(m_path); qDebug() << "Error loading screenshot (perhaps too large?):" + m_path; return; } QImage small; - if (image.width() > image.height()) + if (image.width() > image.height()) { small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation); - else + } else { small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation); - QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); + } + const QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2); QImage square(QSize(256, 256), QImage::Format_ARGB32); square.fill(Qt::transparent); @@ -134,7 +140,7 @@ class ThumbnailRunnable : public QRunnable { painter.drawImage(offset, small); painter.end(); - QIcon icon(QPixmap::fromImage(square)); + const QIcon icon(QPixmap::fromImage(square)); m_cache->add(m_path, icon); m_resultEmitter.emitResultsReady(m_path); } @@ -148,59 +154,62 @@ class ThumbnailRunnable : public QRunnable { class FilterModel : public QIdentityProxyModel { Q_OBJECT public: - explicit FilterModel(QObject* parent = 0) : QIdentityProxyModel(parent) + explicit FilterModel(QObject* parent = nullptr) : QIdentityProxyModel(parent) { m_thumbnailingPool.setMaxThreadCount(4); m_thumbnailCache = std::make_shared(); m_thumbnailCache->add("placeholder", QIcon::fromTheme("screenshot-placeholder")); connect(&watcher, &QFileSystemWatcher::fileChanged, this, &FilterModel::fileChanged); } - virtual ~FilterModel() + ~FilterModel() override { m_thumbnailingPool.clear(); - if (!m_thumbnailingPool.waitForDone(500)) + if (!m_thumbnailingPool.waitForDone(500)) { qDebug() << "Thumbnail pool took longer than 500ms to finish"; + } } - virtual QVariant data(const QModelIndex& proxyIndex, int role = Qt::DisplayRole) const + QVariant data(const QModelIndex& proxyIndex, const int role = Qt::DisplayRole) const override // NOLINT(*-default-arguments) { - auto model = sourceModel(); - if (!model) - return QVariant(); + const auto* model = sourceModel(); + if (!model) { + return {}; + } if (role == Qt::DisplayRole || role == Qt::EditRole) { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), role); + const QVariant result = model->data(mapToSource(proxyIndex), role); static const QRegularExpression s_removeChars("\\.png$"); return result.toString().remove(s_removeChars); } if (role == Qt::DecorationRole) { - QVariant result = sourceModel()->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); - QString filePath = result.toString(); - QIcon temp; + const QVariant result = model->data(mapToSource(proxyIndex), QFileSystemModel::FilePathRole); + const QString filePath = result.toString(); if (!watched.contains(filePath)) { - ((QFileSystemWatcher&)watcher).addPath(filePath); - ((QSet&)watched).insert(filePath); + const_cast(watcher).addPath(filePath); + const_cast&>(watched).insert(filePath); } - if (m_thumbnailCache->get(filePath, temp)) { + if (QIcon temp; m_thumbnailCache->get(filePath, temp)) { return temp; } if (!m_failed.contains(filePath)) { - ((FilterModel*)this)->thumbnailImage(filePath); + const_cast(this)->thumbnailImage(filePath); } return (m_thumbnailCache->get("placeholder")); } - return sourceModel()->data(mapToSource(proxyIndex), role); + return model->data(mapToSource(proxyIndex), role); } - virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) + bool setData(const QModelIndex& index, const QVariant& value, const int role = Qt::EditRole) override // NOLINT(*-default-arguments) { - auto model = sourceModel(); - if (!model) + auto* model = sourceModel(); + if (!model) { return false; - if (role != Qt::EditRole) + } + if (role != Qt::EditRole) { return false; + } // FIXME: this is a workaround for a bug in QFileSystemModel, where it doesn't // sort after renames { - ((QFileSystemModel*)model)->setNameFilterDisables(true); - ((QFileSystemModel*)model)->setNameFilterDisables(false); + static_cast(model)->setNameFilterDisables(true); + static_cast(model)->setNameFilterDisables(false); } return model->setData(mapToSource(index), value.toString() + ".png", role); } @@ -208,15 +217,15 @@ class FilterModel : public QIdentityProxyModel { private: void thumbnailImage(QString path) { - auto runnable = new ThumbnailRunnable(path, m_thumbnailCache); + auto* runnable = new ThumbnailRunnable(std::move(path), m_thumbnailCache); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsReady, this, &FilterModel::thumbnailReady); connect(&runnable->m_resultEmitter, &ThumbnailingResult::resultsFailed, this, &FilterModel::thumbnailFailed); m_thumbnailingPool.start(runnable); } private slots: - void thumbnailReady(QString path) { emit layoutChanged(); } - void thumbnailFailed(QString path) { m_failed.insert(path); } - void fileChanged(QString filepath) + void thumbnailReady(const QString& /*path*/) { emit layoutChanged(); } + void thumbnailFailed(const QString& path) { m_failed.insert(path); } + void fileChanged(const QString& filepath) { m_thumbnailCache->setStale(filepath); // reinsert the path... @@ -237,13 +246,12 @@ class FilterModel : public QIdentityProxyModel { class CenteredEditingDelegate : public QStyledItemDelegate { public: - explicit CenteredEditingDelegate(QObject* parent = 0) : QStyledItemDelegate(parent) {} - virtual ~CenteredEditingDelegate() {} - virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const + explicit CenteredEditingDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + ~CenteredEditingDelegate() override = default; + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override { - auto widget = QStyledItemDelegate::createEditor(parent, option, index); - auto foo = dynamic_cast(widget); - if (foo) { + auto* widget = QStyledItemDelegate::createEditor(parent, option, index); + if (auto* foo = dynamic_cast(widget)) { foo->setAlignment(Qt::AlignHCenter); foo->setFrame(true); foo->setMaximumWidth(192); @@ -252,10 +260,11 @@ class CenteredEditingDelegate : public QStyledItemDelegate { } }; -ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(parent), ui(new Ui::ScreenshotsPage) +ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) + : QMainWindow(parent), ui(new Ui::ScreenshotsPage), m_folder(std::move(path)) { - m_model.reset(new ScreenshotsFSModel()); - m_filterModel.reset(new FilterModel()); + m_model = std::make_shared(); + m_filterModel = std::make_shared(); m_filterModel->setSourceModel(m_model.get()); m_model->setFilter(QDir::Files); m_model->setReadOnly(false); @@ -266,7 +275,6 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa constexpr int file_modified_column_index = 3; m_model->sort(file_modified_column_index, Qt::DescendingOrder); - m_folder = path; m_valid = FS::ensureFolderPathExists(m_folder); ui->setupUi(this); @@ -283,18 +291,19 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->listView->setItemDelegate(new CenteredEditingDelegate(this)); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu); + connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::showContextMenu); connect(ui->listView, &QAbstractItemView::activated, this, &ScreenshotsPage::onItemActivated); } bool ScreenshotsPage::eventFilter(QObject* obj, QEvent* evt) { - if (obj != ui->listView) + if (obj != ui->listView) { return QWidget::eventFilter(obj, evt); + } if (evt->type() != QEvent::KeyPress) { return QWidget::eventFilter(obj, evt); } - QKeyEvent* keyEvent = static_cast(evt); + const auto* keyEvent = static_cast(evt); if (keyEvent->matches(QKeySequence::Copy)) { on_actionCopy_File_s_triggered(); @@ -324,11 +333,11 @@ ScreenshotsPage::~ScreenshotsPage() delete ui; } -void ScreenshotsPage::ShowContextMenu(const QPoint& pos) +void ScreenshotsPage::showContextMenu(const QPoint& pos) { - auto menu = ui->toolBar->createContextMenu(this, tr("Context menu")); + auto* menu = ui->toolBar->createContextMenu(this, tr("Context menu")); - if (ui->listView->selectionModel()->selectedRows().size() > 1) { + if (ui->listView->selectionModel()->selectedIndexes().size() > 1) { menu->removeAction(ui->actionCopy_Image); } @@ -343,66 +352,75 @@ QMenu* ScreenshotsPage::createPopupMenu() return filteredMenu; } -void ScreenshotsPage::onItemActivated(QModelIndex index) +void ScreenshotsPage::onItemActivated(QModelIndex index) const { - if (!index.isValid()) + if (!index.isValid()) { return; - auto info = m_model->fileInfo(index); + } + const auto info = m_model->fileInfo(index); DesktopServices::openPath(info); } -void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& selected) +void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection& /*selected*/) const { + const auto selected = ui->listView->selectionModel()->selectedIndexes(); + bool allReadable = !selected.isEmpty(); bool allWritable = !selected.isEmpty(); - for (auto index : selected.indexes()) { - if (!index.isValid()) + for (auto index : selected) { + if (!index.isValid()) { break; + } auto info = m_model->fileInfo(index); - if (!info.isReadable()) + if (!info.isReadable()) { allReadable = false; - if (!info.isWritable()) + } + if (!info.isWritable()) { allWritable = false; + } } ui->actionUpload->setEnabled(allReadable); - ui->actionCopy_Image->setEnabled(allReadable); + ui->actionCopy_Image->setEnabled(allReadable && selected.size() == 1); ui->actionCopy_File_s->setEnabled(allReadable); ui->actionDelete->setEnabled(allWritable); ui->actionRename->setEnabled(allWritable); } -void ScreenshotsPage::on_actionView_Folder_triggered() +void ScreenshotsPage::on_actionView_Folder_triggered() const { DesktopServices::openPath(m_folder, true); } void ScreenshotsPage::on_actionUpload_triggered() { - auto selection = ui->listView->selectionModel()->selectedRows(); - if (selection.isEmpty()) + auto selection = ui->listView->selectionModel()->selectedIndexes(); + if (selection.isEmpty()) { return; + } QString text; - QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); - if (selection.size() > 1) + const QUrl baseUrl(BuildConfig.IMGUR_BASE_URL); + if (selection.size() > 1) { text = tr("You are about to upload %1 screenshots to %2.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(QString::number(selection.size()), baseUrl.host()); - else + } else { text = tr("You are about to upload the selected screenshot to %1.\n" "You should double-check for personal information.\n\n" "Are you sure?") .arg(baseUrl.host()); + } auto response = CustomMessageBox::selectable(this, "Confirm Upload", text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } QList uploaded; auto job = NetJob::Ptr(new NetJob("Screenshot Upload", APPLICATION->network())); @@ -416,7 +434,7 @@ void ScreenshotsPage::on_actionUpload_triggered() auto screenshot = std::make_shared(info); job->addNetAction(ImgurUpload::make(screenshot)); - connect(job.get(), &Task::failed, [this](QString reason) { + connect(job.get(), &Task::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(job.get(), &Task::aborted, [this] { @@ -457,7 +475,7 @@ void ScreenshotsPage::on_actionUpload_triggered() task.addTask(job); task.addTask(albumTask); - connect(&task, &Task::failed, [this](QString reason) { + connect(&task, &Task::failed, [this](const QString& reason) { CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"), reason, QMessageBox::Critical)->show(); }); connect(&task, &Task::aborted, [this] { @@ -485,24 +503,24 @@ void ScreenshotsPage::on_actionUpload_triggered() m_uploadActive = false; } -void ScreenshotsPage::on_actionCopy_Image_triggered() +void ScreenshotsPage::on_actionCopy_Image_triggered() const { - auto selection = ui->listView->selectionModel()->selectedRows(); + auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { return; } // You can only copy one image to the clipboard. In the case of multiple selected files, only the first one gets copied. - auto item = selection[0]; - auto info = m_model->fileInfo(item); - QImage image(info.absoluteFilePath()); + const auto item = selection.first(); + const auto info = m_model->fileInfo(item); + const QImage image(info.absoluteFilePath()); Q_ASSERT(!image.isNull()); QApplication::clipboard()->setImage(image, QClipboard::Clipboard); } -void ScreenshotsPage::on_actionCopy_File_s_triggered() +void ScreenshotsPage::on_actionCopy_File_s_triggered() const { - auto selection = ui->listView->selectionModel()->selectedRows(); + auto selection = ui->listView->selectionModel()->selectedIndexes(); if (selection.size() < 1) { // Don't do anything so we don't empty the users clipboard return; @@ -513,7 +531,7 @@ void ScreenshotsPage::on_actionCopy_File_s_triggered() auto info = m_model->fileInfo(item); buf += "file:///" + info.absoluteFilePath() + "\r\n"; } - QMimeData* mimeData = new QMimeData(); + auto* mimeData = new QMimeData(); mimeData->setData("text/uri-list", buf.toLocal8Bit()); QApplication::clipboard()->setMimeData(mimeData); } @@ -522,39 +540,43 @@ void ScreenshotsPage::on_actionDelete_triggered() { auto selected = ui->listView->selectionModel()->selectedIndexes(); - int count = ui->listView->selectionModel()->selectedRows().size(); + const qsizetype count = selected.size(); QString text; - if (count > 1) + if (count > 1) { text = tr("You are about to delete %1 screenshots.\n" "This may be permanent and they will be gone from the folder.\n\n" "Are you sure?") .arg(count); - else - text = tr("You are about to delete the selected screenshot.\n" - "This may be permanent and it will be gone from the folder.\n\n" - "Are you sure?") - .arg(count); + } else { + text = + tr("You are about to delete the selected screenshot.\n" + "This may be permanent and it will be gone from the folder.\n\n" + "Are you sure?"); + } - auto response = + const auto response = CustomMessageBox::selectable(this, tr("Confirm Deletion"), text, QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } for (auto item : selected) { - if (FS::trash(m_model->filePath(item))) + if (FS::trash(m_model->filePath(item))) { continue; + } m_model->remove(item); } } -void ScreenshotsPage::on_actionRename_triggered() +void ScreenshotsPage::on_actionRename_triggered() const { auto selection = ui->listView->selectionModel()->selectedIndexes(); - if (selection.isEmpty()) + if (selection.isEmpty()) { return; - ui->listView->edit(selection[0]); + } + ui->listView->edit(selection.first()); // TODO: mass renaming } @@ -564,8 +586,8 @@ void ScreenshotsPage::openedImpl() m_valid = FS::ensureFolderPathExists(m_folder); } if (m_valid) { - QString path = QDir(m_folder).absolutePath(); - auto idx = m_model->setRootPath(path); + const QString path = QDir(m_folder).absolutePath(); + const auto idx = m_model->setRootPath(path); if (idx.isValid()) { ui->listView->setModel(m_filterModel.get()); connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, @@ -577,7 +599,7 @@ void ScreenshotsPage::openedImpl() } } - auto const setting_name = QString("WideBarVisibility_%1").arg(id()); + const auto setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index 0b068aa0a..7d1cf4fcc 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -78,14 +78,14 @@ class ScreenshotsPage : public QMainWindow, public BasePage { private slots: void on_actionUpload_triggered(); - void on_actionCopy_Image_triggered(); - void on_actionCopy_File_s_triggered(); + void on_actionCopy_Image_triggered() const; + void on_actionCopy_File_s_triggered() const; void on_actionDelete_triggered(); - void on_actionRename_triggered(); - void on_actionView_Folder_triggered(); - void onItemActivated(QModelIndex); - void onCurrentSelectionChanged(const QItemSelection& selected); - void ShowContextMenu(const QPoint& pos); + void on_actionRename_triggered() const; + void on_actionView_Folder_triggered() const; + void onItemActivated(QModelIndex) const; + void onCurrentSelectionChanged(const QItemSelection& selected) const; + void showContextMenu(const QPoint& pos); private: Ui::ScreenshotsPage* ui; diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp index 3120d9013..a29564abc 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.cpp +++ b/launcher/ui/pages/instance/ShaderPackPage.cpp @@ -61,9 +61,9 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode connect(ui->actionUpdateItem, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &ShaderPackPage::updateShaderPacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -78,8 +78,9 @@ ShaderPackPage::ShaderPackPage(MinecraftInstance* instance, ShaderPackFolderMode void ShaderPackPage::downloadShaderPack() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -90,9 +91,9 @@ void ShaderPackPage::downloadShaderPack() void ShaderPackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -102,8 +103,9 @@ void ShaderPackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -122,14 +124,16 @@ void ShaderPackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void ShaderPackPage::updateShaderPacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); @@ -144,27 +148,29 @@ void ShaderPackPage::updateShaderPacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The shader pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All shader packs are up-to-date! :)"); } else { message = tr("All selected shader packs are up-to-date! :)"); @@ -174,9 +180,9 @@ void ShaderPackPage::updateShaderPacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Shader Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -192,7 +198,7 @@ void ShaderPackPage::updateShaderPacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -208,8 +214,9 @@ void ShaderPackPage::deleteShaderPackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedShaderPacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 shader packs.\n" @@ -218,8 +225,9 @@ void ShaderPackPage::deleteShaderPackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -227,8 +235,9 @@ void ShaderPackPage::deleteShaderPackMetadata() void ShaderPackPage::changeShaderPackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Shader pack updates are unavailable when metadata is disabled!")); @@ -237,15 +246,17 @@ void ShaderPackPage::changeShaderPackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } - m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::ShaderPackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &ShaderPackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp index ec0486fe4..01325e3f6 100644 --- a/launcher/ui/pages/instance/TexturePackPage.cpp +++ b/launcher/ui/pages/instance/TexturePackPage.cpp @@ -60,9 +60,9 @@ TexturePackPage::TexturePackPage(MinecraftInstance* instance, TexturePackFolderM connect(ui->actionUpdateItem, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); ui->actionsToolbar->insertActionBefore(ui->actionAddItem, ui->actionUpdateItem); - auto updateMenu = new QMenu(this); + auto* updateMenu = new QMenu(this); - auto update = updateMenu->addAction(ui->actionUpdateItem->text()); + auto* update = updateMenu->addAction(ui->actionUpdateItem->text()); connect(update, &QAction::triggered, this, &TexturePackPage::updateTexturePacks); updateMenu->addAction(ui->actionResetItemMetadata); @@ -81,14 +81,15 @@ void TexturePackPage::updateFrame(const QModelIndex& current, [[maybe_unused]] c { auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - auto& rp = static_cast(m_model->at(row)); + auto& rp = m_model->at(row); ui->frame->updateWithTexturePack(rp); } void TexturePackPage::downloadTexturePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); @@ -98,9 +99,9 @@ void TexturePackPage::downloadTexturePacks() void TexturePackPage::downloadDialogFinished(int result) { - if (result) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (result != 0) { + auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -110,8 +111,9 @@ void TexturePackPage::downloadDialogFinished(int result) }); connect(tasks, &Task::succeeded, [this, tasks]() { QStringList warnings = tasks->warnings(); - if (warnings.count()) + if (warnings.count()) { CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); + } tasks->deleteLater(); }); @@ -130,14 +132,16 @@ void TexturePackPage::downloadDialogFinished(int result) m_model->update(); } - if (m_downloadDialog) + if (m_downloadDialog) { m_downloadDialog->deleteLater(); + } } void TexturePackPage::updateTexturePacks() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); @@ -152,27 +156,29 @@ void TexturePackPage::updateTexturePacks() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); - auto mods_list = m_model->selectedResources(selection); - bool use_all = mods_list.empty(); - if (use_all) - mods_list = m_model->allResources(); + auto modsList = m_model->selectedResources(selection); + bool useAll = modsList.empty(); + if (useAll) { + modsList = m_model->allResources(); + } - ResourceUpdateDialog update_dialog(this, m_instance, m_model, mods_list, false); - update_dialog.checkCandidates(); + ResourceUpdateDialog updateDialog(this, m_instance, m_model, modsList, false); + updateDialog.checkCandidates(); - if (update_dialog.aborted()) { + if (updateDialog.aborted()) { CustomMessageBox::selectable(this, tr("Aborted"), tr("The texture pack updater was aborted!"), QMessageBox::Warning)->show(); return; } - if (update_dialog.noUpdates()) { - QString message{ tr("'%1' is up-to-date! :)").arg(mods_list.front()->name()) }; - if (mods_list.size() > 1) { - if (use_all) { + if (updateDialog.noUpdates()) { + QString message{ tr("'%1' is up-to-date! :)").arg(modsList.front()->name()) }; + if (modsList.size() > 1) { + if (useAll) { message = tr("All texture packs are up-to-date! :)"); } else { message = tr("All selected texture packs are up-to-date! :)"); @@ -182,9 +188,9 @@ void TexturePackPage::updateTexturePacks() return; } - if (update_dialog.exec()) { - auto tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); - connect(tasks, &Task::failed, [this, tasks](QString reason) { + if (updateDialog.exec() != 0) { + auto* tasks = new ConcurrentTask("Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()); + connect(tasks, &Task::failed, [this, tasks](const QString& reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); tasks->deleteLater(); }); @@ -200,7 +206,7 @@ void TexturePackPage::updateTexturePacks() tasks->deleteLater(); }); - for (auto task : update_dialog.getTasks()) { + for (const auto& task : updateDialog.getTasks()) { tasks->addTask(task); } @@ -216,8 +222,9 @@ void TexturePackPage::deleteTexturePackMetadata() { auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes(); auto selectionCount = m_model->selectedTexturePacks(selection).length(); - if (selectionCount == 0) + if (selectionCount == 0) { return; + } if (selectionCount > 1) { auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"), tr("You are about to remove the metadata for %1 texture packs.\n" @@ -226,8 +233,9 @@ void TexturePackPage::deleteTexturePackMetadata() QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) ->exec(); - if (response != QMessageBox::Yes) + if (response != QMessageBox::Yes) { return; + } } m_model->deleteMetadata(selection); @@ -235,8 +243,9 @@ void TexturePackPage::deleteTexturePackMetadata() void TexturePackPage::changeTexturePackVersion() { - if (m_instance->typeName() != "Minecraft") + if (m_instance->typeName() != "Minecraft") { return; // this is a null instance or a legacy instance + } if (APPLICATION->settings()->get("ModMetadataDisabled").toBool()) { QMessageBox::critical(this, tr("Error"), tr("Texture pack updates are unavailable when metadata is disabled!")); @@ -245,15 +254,17 @@ void TexturePackPage::changeTexturePackVersion() const QModelIndexList rows = ui->treeView->selectionModel()->selectedRows(); - if (rows.count() != 1) + if (rows.count() != 1) { return; + } Resource& resource = m_model->at(m_filterModel->mapToSource(rows[0]).row()); - if (resource.metadata() == nullptr) + if (resource.metadata() == nullptr) { return; + } - m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance); + m_downloadDialog = new ResourceDownload::TexturePackDownloadDialog(this, m_model, m_instance, true); connect(this, &QObject::destroyed, m_downloadDialog, &QDialog::close); connect(m_downloadDialog, &QDialog::finished, this, &TexturePackPage::downloadDialogFinished); diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index e56e9c731..71c370ab2 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -121,7 +121,7 @@ void WorldListPage::openedImpl() ui->toolBar->removeAction(ui->actionJoin); } - auto const setting_name = QString("WideBarVisibility_%1").arg(id()); + const auto setting_name = QString("WideBarVisibility_%1").arg(id()); m_wide_bar_setting = APPLICATION->settings()->getOrRegisterSetting(setting_name); ui->toolBar->setVisibilityState(QByteArray::fromBase64(m_wide_bar_setting->get().toString().toUtf8())); @@ -259,9 +259,12 @@ void WorldListPage::on_actionData_Packs_triggered() dialog->setLayout(layout); - dialog->exec(); + dialog->setAttribute(Qt::WA_DeleteOnClose); - APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); + connect(dialog, &QDialog::finished, this, + [dialog]() { APPLICATION->settings()->set("DataPackDownloadGeometry", dialog->saveGeometry().toBase64()); }); + + dialog->open(); } void WorldListPage::on_actionReset_Icon_triggered() diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp index 87e126fd7..f24abf9fb 100644 --- a/launcher/ui/pages/modplatform/CustomPage.cpp +++ b/launcher/ui/pages/modplatform/CustomPage.cpp @@ -80,14 +80,14 @@ void CustomPage::openedImpl() void CustomPage::refresh() { - ui->versionList->loadList(); + ui->versionList->loadList(true); } void CustomPage::loaderRefresh() { if (ui->noneFilter->isChecked()) return; - ui->loaderVersionList->loadList(); + ui->loaderVersionList->loadList(true); } void CustomPage::filterChanged() diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 706d35378..1cdce5e33 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -50,7 +50,6 @@ #include "ResourceDownloadTask.h" #include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "ui/dialogs/ResourceDownloadDialog.h" @@ -63,15 +62,15 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance& instance) : ResourcePa void ModPage::setFilterWidget(std::unique_ptr& widget) { - if (m_filter_widget) + if (m_filter_widget) { disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); - - auto old = m_ui->splitter->replaceWidget(0, widget.get()); - // because we replaced the widget we also need to delete it - if (old) { - delete old; } + auto* old = m_ui->splitter->replaceWidget(0, widget.get()); + // because we replaced the widget we also need to delete it + + delete old; + m_filter_widget.swap(widget); m_filter = m_filter_widget->getFilter(); @@ -112,10 +111,13 @@ QMap ModPage::urlHandlers() const /******** Make changes to the UI ********/ -void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* base_model) +void ModPage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& version, + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index a69ee53f7..7f75995a3 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -29,10 +29,10 @@ class ModPage : public ResourcePage { static T* create(ModDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); - auto filter_widget = page->createFilterWidget(); - page->setFilterWidget(filter_widget); + auto filterWidget = page->createFilterWidget(); + page->setFilterWidget(filterWidget); model->setFilter(page->getFilter()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); @@ -43,18 +43,21 @@ class ModPage : public ResourcePage { } //: The plural version of 'mod' - inline QString resourcesString() const override { return tr("mods"); } + QString resourcesString() const override { return tr("mods"); } //: The singular version of 'mods' - inline QString resourceString() const override { return tr("mod"); } + QString resourceString() const override { return tr("mod"); } QMap urlHandlers() const override; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; virtual std::unique_ptr createFilterWidget() = 0; bool supportsFiltering() const override { return true; }; - auto getFilter() const -> const std::shared_ptr { return m_filter; } + auto getFilter() const -> std::shared_ptr { return m_filter; } void setFilterWidget(std::unique_ptr&); protected: diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp index e90eafbf2..edd8564b6 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.cpp +++ b/launcher/ui/pages/modplatform/ResourceModel.cpp @@ -12,10 +12,11 @@ #include #include #include +#include #include "Application.h" -#include "settings/SettingsObject.h" #include "BuildConfig.h" +#include "settings/SettingsObject.h" #include "modplatform/ResourceAPI.h" #include "net/ApiDownload.h" @@ -29,7 +30,7 @@ namespace ResourceDownload { QHash ResourceModel::s_running_models; -ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api) +ResourceModel::ResourceModel(ResourceAPI* api) : m_api(api) { s_running_models.insert(this, true); if (APPLICATION_DYN) { @@ -62,14 +63,14 @@ auto ResourceModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { if (APPLICATION_DYN) { - if (auto icon_or_none = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); - icon_or_none.has_value()) - return icon_or_none.value(); + if (auto iconOrNone = const_cast(this)->getIcon(const_cast(index), pack->logoUrl); + iconOrNone.has_value()) { + return iconOrNone.value(); + } return QIcon::fromTheme("screenshot-placeholder"); - } else { - return {}; } + return {}; } case Qt::SizeHintRole: return QSize(0, 58); @@ -112,8 +113,9 @@ QHash ResourceModel::roleNames() const bool ResourceModel::setData(const QModelIndex& index, const QVariant& value, [[maybe_unused]] int role) { int pos = index.row(); - if (pos >= m_packs.size() || pos < 0 || !index.isValid()) + if (pos >= m_packs.size() || pos < 0 || !index.isValid()) { return false; + } m_packs[pos] = value.value(); emit dataChanged(index, index); @@ -128,42 +130,51 @@ QString ResourceModel::debugName() const void ResourceModel::fetchMore(const QModelIndex& parent) { - if (parent.isValid() || m_search_state == SearchState::Finished) + if (parent.isValid() || m_search_state == SearchState::Finished) { return; + } search(); } void ResourceModel::search() { - if (hasActiveSearchJob()) + if (hasActiveSearchJob()) { return; + } - if (m_search_term.startsWith("#")) { + if (m_search_state != SearchState::ResetRequested && m_search_term.startsWith("#")) { auto projectId = m_search_term.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - searchRequestFailed(reason, -1); + } + if (networkErrorCode == 404) { + m_search_state = SearchState::ResetRequested; + } + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; callbacks.on_succeed = [this](auto& pack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestForOneSucceeded(pack); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks)); job) + if (auto job = m_api->getProjectInfo({ project }, std::move(callbacks), false); job) { runSearchJob(job); + } return; } } @@ -172,31 +183,36 @@ void ResourceModel::search() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestSucceeded(doc); }; - callbacks.on_fail = [this](QString reason, int network_error_code) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](QString reason, int networkErrorCode) { + if (!s_running_models.constFind(this).value()) { return; - searchRequestFailed(reason, network_error_code); + } + searchRequestFailed(std::move(reason), networkErrorCode); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } searchRequestAborted(); }; - if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->searchProjects(std::move(args), std::move(callbacks)); job) { runSearchJob(job); + } } void ResourceModel::loadEntry(const QModelIndex& entry) { - auto const& pack = m_packs[entry.row()]; + const auto& pack = m_packs[entry.row()]; - if (!hasActiveInfoJob()) + if (!hasActiveInfoJob()) { m_current_info_job.clear(); + } if (!pack->versionsLoaded) { auto args{ createVersionsArguments(entry) }; @@ -204,20 +220,24 @@ void ResourceModel::loadEntry(const QModelIndex& entry) auto addonId = pack->addonId; // Use default if no callbacks are set - if (!callbacks.on_succeed) + if (!callbacks.on_succeed) { callbacks.on_succeed = [this, entry, addonId](auto& doc) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } versionRequestSucceeded(doc, addonId, entry); }; - if (!callbacks.on_fail) - callbacks.on_fail = [](QString reason, int) { + } + if (!callbacks.on_fail) { + callbacks.on_fail = [](const QString& reason, int) { QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project versions: %1").arg(reason)); }; + } - if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectVersions(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } if (!pack->extraDataLoaded) { @@ -225,41 +245,45 @@ void ResourceModel::loadEntry(const QModelIndex& entry) ResourceAPI::Callback callbacks{}; callbacks.on_succeed = [this, entry](auto& newpack) { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } infoRequestSucceeded(newpack, entry); }; - callbacks.on_fail = [this](QString reason, int) { - if (!s_running_models.constFind(this).value()) + callbacks.on_fail = [this](const QString& reason, int) { + if (!s_running_models.constFind(this).value()) { return; + } QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info: %1").arg(reason)); }; callbacks.on_abort = [this] { - if (!s_running_models.constFind(this).value()) + if (!s_running_models.constFind(this).value()) { return; + } qCritical() << tr("The request was aborted for an unknown reason"); }; - if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) + if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job) { runInfoJob(job); + } } } void ResourceModel::refresh() { - bool reset_requested = false; + bool resetRequested = false; if (hasActiveInfoJob()) { m_current_info_job.abort(); - reset_requested = true; + resetRequested = true; } if (hasActiveSearchJob()) { m_current_search_job->abort(); - reset_requested = true; + resetRequested = true; } - if (reset_requested) { + if (resetRequested) { m_search_state = SearchState::ResetRequested; return; } @@ -285,13 +309,15 @@ void ResourceModel::runSearchJob(Task::Ptr ptr) } void ResourceModel::runInfoJob(Task::Ptr ptr) { - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.clear(); + } - m_current_info_job.addTask(ptr); + m_current_info_job.addTask(std::move(ptr)); - if (!m_current_info_job.isRunning()) + if (!m_current_info_job.isRunning()) { m_current_info_job.run(); + } } std::optional ResourceModel::getCurrentSortingMethodByIndex() const @@ -299,11 +325,12 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional sort{}; { // Find sorting method by ID - auto sorting_methods = getSortingMethods(); - auto method = std::find_if(sorting_methods.constBegin(), sorting_methods.constEnd(), - [this](auto const& e) { return m_current_sort_index == e.index; }); - if (method != sorting_methods.constEnd()) + auto sortingMethods = getSortingMethods(); + auto method = std::find_if(sortingMethods.constBegin(), sortingMethods.constEnd(), + [this](const auto& e) { return m_current_sort_index == e.index; }); + if (method != sortingMethods.constEnd()) { sort = *method; + } } return sort; @@ -312,43 +339,47 @@ std::optional ResourceModel::getCurrentSortingMethod std::optional ResourceModel::getIcon(QModelIndex& index, const QUrl& url) { QPixmap pixmap; - if (QPixmapCache::find(url.toString(), &pixmap)) + if (QPixmapCache::find(url.toString(), &pixmap)) { return { pixmap }; + } if (!m_current_icon_job) { m_current_icon_job.reset(new NetJob("IconJob", APPLICATION->network())); m_current_icon_job->setAskRetry(false); } - if (m_currently_running_icon_actions.contains(url)) + if (m_currently_running_icon_actions.contains(url)) { return {}; - if (m_failed_icon_actions.contains(url)) + } + if (m_failed_icon_actions.contains(url)) { return {}; + } - auto cache_entry = APPLICATION->metacache()->resolveEntry( + auto cacheEntry = APPLICATION->metacache()->resolveEntry( metaEntryBase(), QString("logos/%1").arg(QString(QCryptographicHash::hash(url.toEncoded(), QCryptographicHash::Algorithm::Sha1).toHex()))); - auto icon_fetch_action = Net::ApiDownload::makeCached(url, cache_entry); + auto iconFetchAction = Net::ApiDownload::makeCached(url, cacheEntry); - auto full_file_path = cache_entry->getFullPath(); - connect(icon_fetch_action.get(), &Task::succeeded, this, [this, url, full_file_path, index] { - auto icon = QIcon(full_file_path); + auto fullFilePath = cacheEntry->getFullPath(); + connect(iconFetchAction.get(), &Task::succeeded, this, [this, url, fullFilePath, index] { + auto icon = QIcon(fullFilePath); QPixmapCache::insert(url.toString(), icon.pixmap(icon.actualSize({ 64, 64 }))); m_currently_running_icon_actions.remove(url); emit dataChanged(index, index, { Qt::DecorationRole }); }); - connect(icon_fetch_action.get(), &Task::failed, this, [this, url] { + connect(iconFetchAction.get(), &Task::failed, this, [this, url] { m_currently_running_icon_actions.remove(url); m_failed_icon_actions.insert(url); }); m_currently_running_icon_actions.insert(url); - m_current_icon_job->addNetAction(icon_fetch_action); - if (!m_current_icon_job->isRunning()) + m_current_icon_job->addNetAction(iconFetchAction); + if (!m_current_icon_job->isRunning()) { QMetaObject::invokeMethod(m_current_icon_job.get(), &NetJob::start); + } return {}; } @@ -360,11 +391,11 @@ void ResourceModel::searchRequestSucceeded(QList& QList filteredNewList; for (auto pack : newList) { ModPlatform::IndexedPack::Ptr p; - if (auto sel = std::find_if(m_selected.begin(), m_selected.end(), - [&pack](const DownloadTaskPtr i) { - const auto ipack = i->getPack(); - return ipack->provider == pack->provider && ipack->addonId == pack->addonId; - }); + if (auto sel = std::ranges::find_if(m_selected, + [&pack](const DownloadTaskPtr& i) { + const auto ipack = i->getPack(); + return ipack->provider == pack->provider && ipack->addonId == pack->addonId; + }); sel != m_selected.end()) { p = sel->get()->getPack(); } else { @@ -383,8 +414,9 @@ void ResourceModel::searchRequestSucceeded(QList& } // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (filteredNewList.size() == 0) + if (filteredNewList.size() == 0) { return; + } beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + filteredNewList.size() - 1); m_packs.append(filteredNewList); @@ -400,13 +432,16 @@ void ResourceModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr p endInsertRows(); } -void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code) +void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int networkErrorCode) { - switch (network_error_code) { + switch (networkErrorCode) { default: // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); break; + case 404: + // 404 Not Found, some APIs return this when nothing is found, no need to bother the user + break; case 409: // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), @@ -414,13 +449,21 @@ void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int net break; } - m_search_state = SearchState::Finished; + if (m_search_state == SearchState::ResetRequested) { + clearData(); + + m_next_search_offset = 0; + search(); + } else { + m_search_state = SearchState::Finished; + } } void ResourceModel::searchRequestAborted() { - if (m_search_state != SearchState::ResetRequested) + if (m_search_state != SearchState::ResetRequested) { qCritical() << "Search task in" << debugName() << "aborted by an unknown reason!"; + } // Retry fetching clearData(); @@ -431,19 +474,20 @@ void ResourceModel::searchRequestAborted() void ResourceModel::versionRequestSucceeded(QVector& doc, QVariant pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack != current_pack->addonId) + if (pack != currentPack->addonId) { return; + } - current_pack->versions = doc; - current_pack->versionsLoaded = true; + currentPack->versions = doc; + currentPack->versionsLoaded = true; // Cache info :^) - QVariant new_pack; - new_pack.setValue(current_pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(currentPack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource versions!"; return; } @@ -453,16 +497,17 @@ void ResourceModel::versionRequestSucceeded(QVector void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, const QModelIndex& index) { - auto current_pack = data(index, Qt::UserRole).value(); + auto currentPack = data(index, Qt::UserRole).value(); // Check if the index is still valid for this resource or not - if (pack->addonId != current_pack->addonId) + if (pack->addonId != currentPack->addonId) { return; + } // Cache info :^) - QVariant new_pack; - new_pack.setValue(pack); - if (!setData(index, new_pack, Qt::UserRole)) { + QVariant newPack; + newPack.setValue(pack); + if (!setData(index, newPack, Qt::UserRole)) { qWarning() << "Failed to cache resource info!"; return; } @@ -473,15 +518,16 @@ void ResourceModel::infoRequestSucceeded(ModPlatform::IndexedPack::Ptr pack, con void ResourceModel::addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed) + bool isIndexed, + QString downloadReason) { version.is_currently_selected = true; - m_selected.append(makeShared(pack, version, packs, is_indexed)); + m_selected.append(makeShared(std::move(pack), version, packs, isIndexed, std::move(downloadReason))); } void ResourceModel::removePack(const QString& rem) { - auto pred = [&rem](const DownloadTaskPtr i) { return rem == i->getName(); }; + auto pred = [&rem](const DownloadTaskPtr& i) { return rem == i->getName(); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_selected.removeIf(pred); #else @@ -493,15 +539,16 @@ void ResourceModel::removePack(const QString& rem) ++it; } #endif - auto pack = std::find_if(m_packs.begin(), m_packs.end(), [&rem](const ModPlatform::IndexedPack::Ptr i) { return rem == i->name; }); + auto pack = std::ranges::find_if(m_packs, [&rem](const ModPlatform::IndexedPack::Ptr& i) { return rem == i->name; }); if (pack == m_packs.end()) { // ignore it if is not in the current search return; } if (!pack->get()->versionsLoaded) { return; } - for (auto& ver : pack->get()->versions) + for (auto& ver : pack->get()->versions) { ver.is_currently_selected = false; + } } bool ResourceModel::checkVersionFilters(const ModPlatform::IndexedVersion& v) diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h index 573ad8b75..9124c0e66 100644 --- a/launcher/ui/pages/modplatform/ResourceModel.h +++ b/launcher/ui/pages/modplatform/ResourceModel.h @@ -36,16 +36,16 @@ class ResourceModel : public QAbstractListModel { ResourceModel(ResourceAPI* api); ~ResourceModel() override; - auto data(const QModelIndex&, int role) const -> QVariant override; + auto data(const QModelIndex& /*index*/, int role) const -> QVariant override; auto roleNames() const -> QHash override; bool setData(const QModelIndex& index, const QVariant& value, int role) override; virtual auto debugName() const -> QString; virtual auto metaEntryBase() const -> QString = 0; - inline int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } - inline int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } - inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } + int rowCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : static_cast(m_packs.size()); } + int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : 1; } + auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); } bool hasActiveSearchJob() const { return m_current_search_job && m_current_search_job->isRunning(); } bool hasActiveInfoJob() const { return m_current_info_job.isRunning(); } @@ -66,7 +66,7 @@ class ResourceModel : public QAbstractListModel { public slots: void fetchMore(const QModelIndex& parent) override; - inline bool canFetchMore(const QModelIndex& parent) const override + bool canFetchMore(const QModelIndex& parent) const override { return parent.isValid() ? false : m_search_state == SearchState::CanFetchMore; } @@ -94,7 +94,8 @@ class ResourceModel : public QAbstractListModel { void addPack(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, ResourceFolderModel* packs, - bool is_indexed = false); + bool isIndexed = false, + QString downloadReason = "standalone"); void removePack(const QString& rem); QList selectedPacks() { return m_selected; } diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp index 98aa650e0..8baa0bbec 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.cpp +++ b/launcher/ui/pages/modplatform/ResourcePage.cpp @@ -39,12 +39,14 @@ #include "ResourcePage.h" #include "modplatform/ModIndex.h" -#include "ui/dialogs/CustomMessageBox.h" #include "ui_ResourcePage.h" #include #include #include +#include +#include +#include #include "Markdown.h" @@ -55,8 +57,8 @@ namespace ResourceDownload { -ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_instance) - : QWidget(parent), m_baseInstance(base_instance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) +ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& baseInstance) + : QWidget(parent), m_baseInstance(baseInstance), m_ui(new Ui::ResourcePage), m_parentDialog(parent), m_fetchProgress(this, false) { m_ui->setupUi(this); @@ -79,7 +81,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in m_ui->verticalLayout->insertWidget(1, &m_fetchProgress); - auto delegate = new ProjectItemDelegate(this); + auto* delegate = new ProjectItemDelegate(this); m_ui->packView->setItemDelegate(delegate); m_ui->packView->installEventFilter(this); m_ui->packView->viewport()->installEventFilter(this); @@ -93,8 +95,7 @@ ResourcePage::ResourcePage(ResourceDownloadDialog* parent, BaseInstance& base_in ResourcePage::~ResourcePage() { delete m_ui; - if (m_model) - delete m_model; + delete m_model; } void ResourcePage::retranslate() @@ -114,10 +115,19 @@ void ResourcePage::openedImpl() m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); updateSelectionButton(); - triggerSearch(); + if (!m_suppressInitialSearch) { + triggerSearch(); + } else { + m_suppressInitialSearch = false; + } m_ui->searchEdit->setFocus(); } +void ResourcePage::setSuppressInitialSearch(bool suppress) +{ + m_suppressInitialSearch = suppress; +} + auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool { if (event->type() == QEvent::KeyPress) { @@ -127,12 +137,13 @@ auto ResourcePage::eventFilter(QObject* watched, QEvent* event) -> bool triggerSearch(); keyEvent->accept(); return true; - } else { - if (m_searchTimer.isActive()) - m_searchTimer.stop(); - - m_searchTimer.start(350); } + if (m_searchTimer.isActive()) { + m_searchTimer.stop(); + } + + m_searchTimer.start(350); + } else if (watched == m_ui->packView) { // stop the event from going to the confirm button if (keyEvent->key() == Qt::Key_Return) { @@ -158,7 +169,7 @@ QString ResourcePage::getSearchTerm() const return m_ui->searchEdit->text(); } -void ResourcePage::setSearchTerm(QString term) +void ResourcePage::setSearchTerm(const QString& term) { m_ui->searchEdit->setText(term); } @@ -168,10 +179,11 @@ void ResourcePage::addSortings() Q_ASSERT(m_model); auto sorts = m_model->getSortingMethods(); - std::sort(sorts.begin(), sorts.end(), [](auto const& l, auto const& r) { return l.index < r.index; }); + std::ranges::sort(sorts, [](const auto& l, const auto& r) { return l.index < r.index; }); - for (auto&& sorting : sorts) + for (auto&& sorting : sorts) { m_ui->sortByBox->addItem(sorting.readable_name, QVariant(sorting.index)); + } } bool ResourcePage::setCurrentPack(ModPlatform::IndexedPack::Ptr pack) @@ -188,24 +200,26 @@ ModPlatform::IndexedPack::Ptr ResourcePage::getCurrentPack() const void ResourcePage::updateUi(const QModelIndex& index) { - if (index != m_ui->packView->currentIndex()) + if (index != m_ui->packView->currentIndex()) { return; + } - auto current_pack = getCurrentPack(); - if (!current_pack) { + auto currentPack = getCurrentPack(); + if (!currentPack) { m_ui->packDescription->setHtml({}); m_ui->packDescription->flush(); return; } QString text = ""; - QString name = current_pack->name; + QString name = currentPack->name; - if (current_pack->websiteUrl.isEmpty()) + if (currentPack->websiteUrl.isEmpty()) { text = name; - else - text = "websiteUrl + "\">" + name + ""; + } else { + text = "websiteUrl + "\">" + name + ""; + } - if (!current_pack->authors.empty()) { + if (!currentPack->authors.empty()) { auto authorToStr = [](ModPlatform::ModpackAuthor& author) -> QString { if (author.url.isEmpty()) { return author.name; @@ -213,49 +227,53 @@ void ResourcePage::updateUi(const QModelIndex& index) return QString("%2").arg(author.url, author.name); }; QStringList authorStrs; - for (auto& author : current_pack->authors) { + for (auto& author : currentPack->authors) { authorStrs.push_back(authorToStr(author)); } text += "
" + tr(" by ") + authorStrs.join(", "); } - if (current_pack->extraDataLoaded) { - if (current_pack->extraData.status == "archived") { + if (currentPack->extraDataLoaded) { + if (currentPack->extraData.status == "archived") { text += "

" + tr("This project has been archived. It will not receive any further updates unless the author decides " "to unarchive the project."); } - if (!current_pack->extraData.donate.isEmpty()) { + if (!currentPack->extraData.donate.isEmpty()) { text += "

" + tr("Donate information: "); auto donateToStr = [](ModPlatform::DonationData& donate) -> QString { return QString("%2").arg(donate.url, donate.platform); }; QStringList donates; - for (auto& donate : current_pack->extraData.donate) { + for (auto& donate : currentPack->extraData.donate) { donates.append(donateToStr(donate)); } text += donates.join(", "); } - if (!current_pack->extraData.issuesUrl.isEmpty() || !current_pack->extraData.sourceUrl.isEmpty() || - !current_pack->extraData.wikiUrl.isEmpty() || !current_pack->extraData.discordUrl.isEmpty()) { + if (!currentPack->extraData.issuesUrl.isEmpty() || !currentPack->extraData.sourceUrl.isEmpty() || + !currentPack->extraData.wikiUrl.isEmpty() || !currentPack->extraData.discordUrl.isEmpty()) { text += "

" + tr("External links:") + "
"; } - if (!current_pack->extraData.issuesUrl.isEmpty()) - text += "- " + tr("Issues: %1").arg(current_pack->extraData.issuesUrl) + "
"; - if (!current_pack->extraData.wikiUrl.isEmpty()) - text += "- " + tr("Wiki: %1").arg(current_pack->extraData.wikiUrl) + "
"; - if (!current_pack->extraData.sourceUrl.isEmpty()) - text += "- " + tr("Source code: %1").arg(current_pack->extraData.sourceUrl) + "
"; - if (!current_pack->extraData.discordUrl.isEmpty()) - text += "- " + tr("Discord: %1").arg(current_pack->extraData.discordUrl) + "
"; + if (!currentPack->extraData.issuesUrl.isEmpty()) { + text += "- " + tr("Issues: %1").arg(currentPack->extraData.issuesUrl) + "
"; + } + if (!currentPack->extraData.wikiUrl.isEmpty()) { + text += "- " + tr("Wiki: %1").arg(currentPack->extraData.wikiUrl) + "
"; + } + if (!currentPack->extraData.sourceUrl.isEmpty()) { + text += "- " + tr("Source code: %1").arg(currentPack->extraData.sourceUrl) + "
"; + } + if (!currentPack->extraData.discordUrl.isEmpty()) { + text += "- " + tr("Discord: %1").arg(currentPack->extraData.discordUrl) + "
"; + } } text += "
"; m_ui->packDescription->setHtml(StringUtils::htmlListPatch( - text + (current_pack->extraData.body.isEmpty() ? current_pack->description : markdownToHTML(current_pack->extraData.body)))); + text + (currentPack->extraData.body.isEmpty() ? currentPack->description : markdownToHTML(currentPack->extraData.body)))); m_ui->packDescription->flush(); } @@ -267,14 +285,15 @@ void ResourcePage::updateSelectionButton() } m_ui->resourceSelectionButton->setEnabled(true); - if (auto current_pack = getCurrentPack(); current_pack) { - if (current_pack->versionsLoaded && current_pack->versions.empty()) { + if (auto currentPack = getCurrentPack(); currentPack) { + if (currentPack->versionsLoaded && currentPack->versions.empty()) { m_ui->resourceSelectionButton->setEnabled(false); qWarning() << tr("No version available for the selected pack"); - } else if (!current_pack->isVersionSelected(m_selectedVersionIndex)) + } else if (!currentPack->isVersionSelected(m_selectedVersionIndex)) { m_ui->resourceSelectionButton->setText(tr("Select %1 for download").arg(resourceString())); - else + } else { m_ui->resourceSelectionButton->setText(tr("Deselect %1 for download").arg(resourceString())); + } } else { qWarning() << "Tried to update the selected button but there is not a pack selected"; } @@ -283,19 +302,20 @@ void ResourcePage::updateSelectionButton() void ResourcePage::versionListUpdated(const QModelIndex& index) { if (index == m_ui->packView->currentIndex()) { - auto current_pack = getCurrentPack(); + auto currentPack = getCurrentPack(); m_ui->versionSelectionBox->blockSignals(true); m_ui->versionSelectionBox->clear(); m_ui->versionSelectionBox->blockSignals(false); - if (current_pack) { - auto installedVersion = m_model->getInstalledPackVersion(current_pack); + if (currentPack) { + auto installedVersion = m_model->getInstalledPackVersion(currentPack); - for (int i = 0; i < current_pack->versions.size(); i++) { - auto& version = current_pack->versions[i]; - if (!m_model->checkVersionFilters(version)) + for (int i = 0; i < currentPack->versions.size(); i++) { + auto& version = currentPack->versions[i]; + if (!m_model->checkVersionFilters(version)) { continue; + } auto versionText = version.version; if (version.version_type.isValid()) { @@ -316,8 +336,9 @@ void ResourcePage::versionListUpdated(const QModelIndex& index) if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); - } else + } else { updateSelectionButton(); + } } else if (m_enableQueue.contains(index.row())) { m_enableQueue.remove(index.row()); onResourceToggle(index); @@ -330,27 +351,30 @@ void ResourcePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI return; } - auto current_pack = getCurrentPack(); + auto currentPack = getCurrentPack(); - bool request_load = false; - if (!current_pack || !current_pack->versionsLoaded) { + bool requestLoad = false; + if (!currentPack || !currentPack->versionsLoaded) { m_ui->resourceSelectionButton->setText(tr("Loading versions...")); m_ui->resourceSelectionButton->setEnabled(false); - request_load = true; + requestLoad = true; } else { versionListUpdated(curr); } - if (current_pack && !current_pack->extraDataLoaded) - request_load = true; + if (currentPack && !currentPack->extraDataLoaded) { + requestLoad = true; + } // we are already requesting this - if (m_enableQueue.contains(curr.row())) - request_load = false; + if (m_enableQueue.contains(curr.row())) { + requestLoad = false; + } - if (request_load) + if (requestLoad) { m_model->loadEntry(curr); + } updateUi(curr); } @@ -363,18 +387,21 @@ void ResourcePage::onVersionSelectionChanged(int index) void ResourcePage::addResourceToDialog(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version) { - m_parentDialog->addResource(pack, version); + m_parentDialog->addResource(std::move(pack), version); } -void ResourcePage::removeResourceFromDialog(const QString& pack_name) +void ResourcePage::removeResourceFromDialog(const QString& packName) { - m_parentDialog->removeResource(pack_name); + m_parentDialog->removeResource(packName); } -void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& ver, ResourceFolderModel* base_model) +void ResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, + ModPlatform::IndexedVersion& ver, + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, ver, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(std::move(pack), ver, baseModel, isIndexed, std::move(downloadReason)); } void ResourcePage::modelReset() @@ -389,22 +416,25 @@ void ResourcePage::removeResourceFromPage(const QString& name) void ResourcePage::onResourceSelected() { - if (m_selectedVersionIndex < 0) + if (m_selectedVersionIndex < 0) { return; + } - auto current_pack = getCurrentPack(); - if (!current_pack || !current_pack->versionsLoaded || current_pack->versions.size() < m_selectedVersionIndex) + auto currentPack = getCurrentPack(); + if (!currentPack || !currentPack->versionsLoaded || currentPack->versions.size() < m_selectedVersionIndex) { return; + } - auto& version = current_pack->versions[m_selectedVersionIndex]; + auto& version = currentPack->versions[m_selectedVersionIndex]; Q_ASSERT(!version.downloadUrl.isNull()); - if (version.is_currently_selected) - removeResourceFromDialog(current_pack->name); - else - addResourceToDialog(current_pack, version); + if (version.is_currently_selected) { + removeResourceFromDialog(currentPack->name); + } else { + addResourceToDialog(currentPack, version); + } // Save the modified pack (and prevent warning in release build) - [[maybe_unused]] bool set = setCurrentPack(current_pack); + [[maybe_unused]] bool set = setCurrentPack(currentPack); Q_ASSERT(set); updateSelectionButton(); @@ -419,26 +449,28 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) auto pack = m_model->data(index, Qt::UserRole).value(); if (pack->versionsLoaded) { - if (pack->isAnyVersionSelected()) + if (pack->isAnyVersionSelected()) { removeResourceFromDialog(pack->name); - else { + } else { auto version = std::find_if(pack->versions.begin(), pack->versions.end(), [this](const ModPlatform::IndexedVersion& version) { return m_model->checkVersionFilters(version); }); if (version == pack->versions.end()) { - auto errorMessage = new QMessageBox( + auto* errorMessage = new QMessageBox( QMessageBox::Warning, tr("No versions available"), tr("No versions for '%1' are available.\nThe author likely blocked third-party launchers.").arg(pack->name), QMessageBox::Ok, this); errorMessage->open(); - } else + } else { addResourceToDialog(pack, *version); + } } - if (isSelected) + if (isSelected) { updateSelectionButton(); + } // force update QVariant variant; @@ -450,8 +482,9 @@ void ResourcePage::onResourceToggle(const QModelIndex& index) // we can't be sure that this hasn't already been requested... // but this does the job well enough and there's not much point preventing edgecases - if (!isSelected) + if (!isSelected) { m_model->loadEntry(index); + } } } @@ -482,13 +515,13 @@ void ResourcePage::openUrl(const QUrl& url) const QString slug = match.captured(1); // ensure the user isn't opening the same mod - if (auto current_pack = getCurrentPack(); current_pack && slug != current_pack->slug) { + if (auto currentPack = getCurrentPack(); currentPack && slug != currentPack->slug) { m_parentDialog->selectPage(page); - auto newPage = m_parentDialog->selectedPage(); + auto* newPage = m_parentDialog->selectedPage(); QLineEdit* searchEdit = newPage->m_ui->searchEdit; - auto model = newPage->m_model; + auto* model = newPage->m_model; QListView* view = newPage->m_ui->packView; auto jump = [url, slug, model, view] { @@ -509,10 +542,11 @@ void ResourcePage::openUrl(const QUrl& url) searchEdit->setText(slug); newPage->triggerSearch(); - if (model->hasActiveSearchJob()) + if (model->hasActiveSearchJob()) { connect(model->activeSearchJob().get(), &Task::finished, jump); - else + } else { jump(); + } return; } @@ -522,7 +556,7 @@ void ResourcePage::openUrl(const QUrl& url) QDesktopServices::openUrl(url); } -void ResourcePage::openProject(QVariant projectID) +void ResourcePage::openProject(const QVariant& projectID) { m_ui->sortByBox->hide(); m_ui->searchEdit->hide(); @@ -531,16 +565,16 @@ void ResourcePage::openProject(QVariant projectID) m_ui->resourceSelectionButton->hide(); m_doNotJumpToMod = true; - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - auto okBtn = buttonBox->button(QDialogButtonBox::Ok); + auto* okBtn = buttonBox->button(QDialogButtonBox::Ok); okBtn->setDefault(true); okBtn->setAutoDefault(true); okBtn->setText(tr("Reinstall")); okBtn->setShortcut(tr("Ctrl+Return")); okBtn->setEnabled(false); - auto cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); + auto* cancelBtn = buttonBox->button(QDialogButtonBox::Cancel); cancelBtn->setDefault(false); cancelBtn->setAutoDefault(false); cancelBtn->setText(tr("Cancel")); @@ -567,9 +601,10 @@ void ResourcePage::openProject(QVariant projectID) m_ui->searchEdit->setText("#" + projectID.toString()); triggerSearch(); - if (m_model->hasActiveSearchJob()) + if (m_model->hasActiveSearchJob()) { connect(m_model->activeSearchJob().get(), &Task::finished, jump); - else + } else { jump(); + } } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ResourcePage.h b/launcher/ui/pages/modplatform/ResourcePage.h index 6e219bf22..03895dcd4 100644 --- a/launcher/ui/pages/modplatform/ResourcePage.h +++ b/launcher/ui/pages/modplatform/ResourcePage.h @@ -9,7 +9,6 @@ #include "ResourceDownloadTask.h" #include "modplatform/ModIndex.h" -#include "modplatform/ResourceAPI.h" #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ResourceModel.h" @@ -44,9 +43,9 @@ class ResourcePage : public QWidget, public BasePage { virtual auto debugName() const -> QString = 0; //: The plural version of 'resource' - virtual inline QString resourcesString() const { return tr("resources"); } + virtual QString resourcesString() const { return tr("resources"); } //: The singular version of 'resources' - virtual inline QString resourceString() const { return tr("resource"); } + virtual QString resourceString() const { return tr("resource"); } /* Features this resource's page supports */ virtual bool supportsFiltering() const = 0; @@ -58,7 +57,7 @@ class ResourcePage : public QWidget, public BasePage { /** Get the current term in the search bar. */ auto getSearchTerm() const -> QString; /** Programatically set the term in the search bar. */ - void setSearchTerm(QString); + void setSearchTerm(const QString&); bool setCurrentPack(ModPlatform::IndexedPack::Ptr); auto getCurrentPack() const -> ModPlatform::IndexedPack::Ptr; @@ -76,21 +75,26 @@ class ResourcePage : public QWidget, public BasePage { virtual void versionListUpdated(const QModelIndex& index); void addResourceToDialog(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&); - void removeResourceFromDialog(const QString& pack_name); + void removeResourceFromDialog(const QString& packName); virtual void removeResourceFromPage(const QString& name); - virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*); + virtual void addResourceToPage(ModPlatform::IndexedPack::Ptr, + ModPlatform::IndexedVersion&, + ResourceFolderModel*, + QString downloadReason = "standalone"); virtual void modelReset(); QList selectedPacks() { return m_model->selectedPacks(); } bool hasSelectedPacks() { return !(m_model->selectedPacks().isEmpty()); } - virtual void openProject(QVariant projectID); + virtual void openProject(const QVariant& projectID); + + void setSuppressInitialSearch(bool suppress); protected slots: virtual void triggerSearch() = 0; - void onSelectionChanged(QModelIndex first, QModelIndex second); + void onSelectionChanged(QModelIndex curr, QModelIndex prev); void onVersionSelectionChanged(int index); void onResourceSelected(); void onResourceToggle(const QModelIndex& index); @@ -118,6 +122,9 @@ class ResourcePage : public QWidget, public BasePage { bool m_doNotJumpToMod = false; QSet m_enableQueue; + + private: + bool m_suppressInitialSearch = false; }; } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp index 99c50352a..ec5fe7967 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp +++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp @@ -36,18 +36,18 @@ QMap ShaderPackResourcePage::urlHandlers() const { QMap map; map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/shaders\\/([^\\/]+)\\/?"), "modrinth"); - map.insert(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/customization\\/([^\\/]+)\\/?"), - "curseforge"); - map.insert(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"((?:www\.)?curseforge\.com\/minecraft\/customization\/([^\/]+)\/?)"), "curseforge"); + map.insert(QRegularExpression::anchoredPattern(R"(minecraft\.curseforge\.com\/projects\/([^\/]+)\/?)"), "curseforge"); return map; } void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pack, ModPlatform::IndexedVersion& version, - ResourceFolderModel* base_model) + ResourceFolderModel* baseModel, + QString downloadReason) { - bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); - m_model->addPack(pack, version, base_model, is_indexed); + bool isIndexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool(); + m_model->addPack(pack, version, baseModel, isIndexed, downloadReason); } } // namespace ResourceDownload diff --git a/launcher/ui/pages/modplatform/ShaderPackPage.h b/launcher/ui/pages/modplatform/ShaderPackPage.h index 92ddd9f8a..37e3cadef 100644 --- a/launcher/ui/pages/modplatform/ShaderPackPage.h +++ b/launcher/ui/pages/modplatform/ShaderPackPage.h @@ -23,7 +23,7 @@ class ShaderPackResourcePage : public ResourcePage { static T* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance) { auto page = new T(dialog, instance); - auto model = static_cast(page->getModel()); + auto* model = static_cast(page->getModel()); connect(model, &ResourceModel::versionListUpdated, page, &ResourcePage::versionListUpdated); connect(model, &ResourceModel::projectInfoUpdated, page, &ResourcePage::updateUi); @@ -33,17 +33,20 @@ class ShaderPackResourcePage : public ResourcePage { } //: The plural version of 'shader pack' - inline QString resourcesString() const override { return tr("shader packs"); } + QString resourcesString() const override { return tr("shader packs"); } //: The singular version of 'shader packs' - inline QString resourceString() const override { return tr("shader pack"); } + QString resourceString() const override { return tr("shader pack"); } bool supportsFiltering() const override { return false; }; - void addResourceToPage(ModPlatform::IndexedPack::Ptr, ModPlatform::IndexedVersion&, ResourceFolderModel*) override; + void addResourceToPage(ModPlatform::IndexedPack::Ptr /*unused*/, + ModPlatform::IndexedVersion& /*unused*/, + ResourceFolderModel* /*unused*/, + QString downloadReason = "standalone") override; QMap urlHandlers() const override; - inline auto helpPage() const -> QString override { return "shaderpack-platform"; } + auto helpPage() const -> QString override { return "shaderpack-platform"; } protected: ShaderPackResourcePage(ShaderPackDownloadDialog* dialog, BaseInstance& instance); diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp index 5d968d65a..861dd9f22 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp @@ -165,12 +165,20 @@ void ListModel::fetchMore(const QModelIndex& parent) void ListModel::performPaginatedSearch() { static const FlameAPI api; - if (m_currentSearchTerm.startsWith("#")) { + + // activate search by id only for numerical values because all CurseForge ids are numerical + static const QRegularExpression s_projectIdExpr("^\\#[0-9]+$"); + if (m_searchState != ResetRequested && s_projectIdExpr.match(m_currentSearchTerm).hasMatch()) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (network_error_code == 404) { + m_searchState = ResetRequested; + } + searchRequestFailed(reason); + }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; @@ -178,7 +186,7 @@ void ListModel::performPaginatedSearch() }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { m_jobPtr = job; m_jobPtr->start(); } diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp index 99a57f2bf..acdce29b6 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp @@ -41,7 +41,7 @@ #include #include #include "modplatform/flame/FlameAPI.h" -#include "ui_ResourcePage.h" +#include "../ui_ResourcePage.h" #include "FlameResourceModels.h" #include "ui/dialogs/ResourceDownloadDialog.h" diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 05cd2970c..03518c3af 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -135,20 +135,26 @@ void ModpackListModel::performPaginatedSearch() return; static const ModrinthAPI api; - if (m_currentSearchTerm.startsWith("#")) { + // Modrinth ids are not limited to numbers and can be any length + if (m_searchState != ResetRequested && m_currentSearchTerm.startsWith("#")) { auto projectId = m_currentSearchTerm.mid(1); if (!projectId.isEmpty()) { ResourceAPI::Callback callbacks; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int network_error_code) { + if (network_error_code == 404) { + m_searchState = ResetRequested; + } + searchRequestFailed(reason, network_error_code); + }; callbacks.on_succeed = [this](auto& pack) { searchRequestForOneSucceeded(pack); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; - searchRequestFailed("Aborted"); + searchRequestFailed("Aborted", 0); }; auto project = std::make_shared(); project->addonId = projectId; - if (auto job = api.getProjectInfo({ project }, std::move(callbacks)); job) { + if (auto job = api.getProjectInfo({ project }, std::move(callbacks), false); job) { m_jobPtr = job; m_jobPtr->start(); } @@ -161,10 +167,10 @@ void ModpackListModel::performPaginatedSearch() ResourceAPI::Callback> callbacks{}; callbacks.on_succeed = [this](auto& doc) { searchRequestFinished(doc); }; - callbacks.on_fail = [this](QString reason, int) { searchRequestFailed(reason); }; + callbacks.on_fail = [this](QString reason, int network_error_code) { searchRequestFailed(reason, network_error_code); }; callbacks.on_abort = [this] { qCritical() << "Search task aborted by an unknown reason!"; - searchRequestFailed("Aborted"); + searchRequestFailed("Aborted", 0); }; auto netJob = api.searchProjects({ ModPlatform::ResourceType::Modpack, m_nextSearchOffset, m_currentSearchTerm, sort, m_filter->loaders, @@ -316,13 +322,12 @@ void ModpackListModel::searchRequestForOneSucceeded(ModPlatform::IndexedPack::Pt endInsertRows(); } -void ModpackListModel::searchRequestFailed(QString) +void ModpackListModel::searchRequestFailed(QString reason, int network_error_code) { - auto failed_action = dynamic_cast(m_jobPtr.get())->getFailedActions().at(0); - if (failed_action->replyStatusCode() == -1) { - // Network error + if (network_error_code == -1) { + // Unknown error in network stack QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); - } else if (failed_action->replyStatusCode() == 409) { + } else if (network_error_code == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 96f6fd128..3e0fc1686 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -85,7 +85,7 @@ class ModpackListModel : public QAbstractListModel { public slots: void searchRequestFinished(QList& doc_all); - void searchRequestFailed(QString reason); + void searchRequestFailed(QString reason, int network_error_code); void searchRequestForOneSucceeded(ModPlatform::IndexedPack::Ptr); protected slots: diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp index c290b6715..a1a7390bb 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp @@ -38,7 +38,7 @@ #include "ModrinthResourcePages.h" #include "ui/pages/modplatform/DataPackModel.h" -#include "ui_ResourcePage.h" +#include "../ui_ResourcePage.h" #include "modplatform/modrinth/ModrinthAPI.h" diff --git a/launcher/ui/widgets/EnvironmentVariables.cpp b/launcher/ui/widgets/EnvironmentVariables.cpp index 9387ef2e2..33bb00c63 100644 --- a/launcher/ui/widgets/EnvironmentVariables.cpp +++ b/launcher/ui/widgets/EnvironmentVariables.cpp @@ -104,7 +104,7 @@ QMap EnvironmentVariables::value() const QMap result; QTreeWidgetItem* item = ui->list->topLevelItem(0); for (int i = 1; item != nullptr; item = ui->list->topLevelItem(i++)) - result[item->text(0)] = item->text(1); + result[item->text(0).trimmed()] = item->text(1).trimmed(); return result; } diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 354211439..3bf9fef82 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -87,7 +87,7 @@ void InfoFrame::updateWithMod(const Mod& m) QString name = ""; QString link = m.homepage(); if (m.name().isEmpty()) - name = m.internal_id(); + name = m.internalId(); else name = renderColorCodes(m.name()); diff --git a/launcher/ui/widgets/JavaSettingsWidget.ui b/launcher/ui/widgets/JavaSettingsWidget.ui index 14638cf4e..03d632ad9 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.ui +++ b/launcher/ui/widgets/JavaSettingsWidget.ui @@ -392,7 +392,10 @@ autodownloadJavaCheckBox javaTestBtn javaDownloadBtn + minMemSpinBox maxMemSpinBox + permGenSpinBox + lowMemWarningCheckBox jvmArgsTextBox
diff --git a/launcher/ui/widgets/LanguageSelectionWidget.cpp b/launcher/ui/widgets/LanguageSelectionWidget.cpp index a32f7034d..3f35df7b0 100644 --- a/launcher/ui/widgets/LanguageSelectionWidget.cpp +++ b/launcher/ui/widgets/LanguageSelectionWidget.cpp @@ -63,7 +63,7 @@ void LanguageSelectionWidget::retranslate() QString text = tr("Don't see your language or the quality is poor?
Help us with translations!") .arg(BuildConfig.TRANSLATIONS_URL); helpUsLabel->setText(text); - formatCheckbox->setText(tr("Use system locales")); + formatCheckbox->setText(tr("Use system regional standards")); } void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous) diff --git a/launcher/ui/widgets/MinecraftSettingsWidget.ui b/launcher/ui/widgets/MinecraftSettingsWidget.ui index 80fb8530d..a063f9660 100644 --- a/launcher/ui/widgets/MinecraftSettingsWidget.ui +++ b/launcher/ui/widgets/MinecraftSettingsWidget.ui @@ -858,32 +858,71 @@ It is most likely you will need to change the path - please refer to the mod's w openGlobalSettingsButton settingsTabs scrollArea + + + windowSizeGroupBox maximizedCheckBox - windowHeightSpinBox windowWidthSpinBox + windowHeightSpinBox closeAfterLaunchCheck quitAfterGameStopCheck + + + consoleSettingsBox showConsoleCheck showConsoleErrorCheck autoCloseConsoleCheck + + + globalDataPacksGroupBox + dataPacksPathEdit + dataPacksPathBrowse + + --> + gameTimeGroupBox showGameTime recordGameTime showGlobalGameTime showGameTimeWithoutDays + + instanceAccountGroupBox instanceAccountSelector + + serverJoinGroupBox serverJoinAddressButton serverJoinAddress worldJoinButton worldsCb + + + loaderGroup + neoForge + forge + fabric + quilt + liteLoader + babric + btaBabric + legacyFabric + ornithe + rift + javaScrollArea scrollArea_2 + + + legacySettingsGroupBox onlineFixes + + + nativeWorkaroundsGroupBox useNativeGLFWCheck lineEditGLFWPath useNativeOpenALCheck lineEditOpenALPath + enableFeralGamemodeCheck enableMangoHud useDiscreteGpuCheck diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 58b092275..ad43e95e4 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include "settings/SettingsObject.h" @@ -59,40 +60,43 @@ class PageEntryFilterModel : public QSortFilterProxyModel { public: - explicit PageEntryFilterModel(QObject* parent = 0) : QSortFilterProxyModel(parent) {} + explicit PageEntryFilterModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { const QString pattern = filterRegularExpression().pattern(); - const auto model = static_cast(sourceModel()); - const auto page = model->pages().at(sourceRow); - if (!page->shouldDisplay()) + auto* const model = static_cast(sourceModel()); + auto* const page = model->pages().at(sourceRow); + if (!page->shouldDisplay()) { return false; + } // Regular contents check, then check page-filter. return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } }; -PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) : QWidget(parent) +PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, QWidget* parent) + : QWidget(parent) + , m_proxyModel(new PageEntryFilterModel(this)) + , m_model(new PageModel(this)) { createUI(); useSidebarStyle(true); - m_model = new PageModel(this); - m_proxyModel = new PageEntryFilterModel(this); int counter = 0; auto pages = pageProvider->getPages(); - for (auto page : pages) { - auto widget = dynamic_cast(page); + for (auto* page : pages) { + auto* widget = dynamic_cast(page); widget->setParent(this); page->stackIndex = m_pageStack->addWidget(widget); page->listIndex = counter; page->setParentContainer(this); counter++; - page->updateExtraInfo = [this](QString id, QString info) { - if (m_currentPage && id == m_currentPage->id()) + page->updateExtraInfo = [this](const QString& id, const QString& info) { + if (m_currentPage && id == m_currentPage->id()) { m_header->setText(m_currentPage->displayName() + info); + } }; } m_model->setPages(pages); @@ -108,13 +112,13 @@ PageContainer::PageContainer(BasePageProvider* pageProvider, QString defaultId, connect(m_pageList->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &PageContainer::currentChanged); m_pageStack->setStackingMode(QStackedLayout::StackOne); m_pageList->setFocus(); - selectPage(defaultId); + selectPage(std::move(defaultId)); } bool PageContainer::selectPage(QString pageId) { // now find what we want to have selected... - auto page = m_model->findPageEntryById(pageId); + auto* page = m_model->findPageEntryById(pageId); QModelIndex index; if (page) { index = m_proxyModel->mapFromSource(m_model->index(page->listIndex)); @@ -166,11 +170,12 @@ void PageContainer::createUI() QFont headerLabelFont = m_header->font(); headerLabelFont.setBold(true); const int pointSize = headerLabelFont.pointSize(); - if (pointSize > 0) + if (pointSize > 0) { headerLabelFont.setPointSize(pointSize + 2); + } m_header->setFont(headerLabelFont); - QHBoxLayout* headerHLayout = new QHBoxLayout; + auto* headerHLayout = new QHBoxLayout; const int leftMargin = APPLICATION->style()->pixelMetric(QStyle::PM_LayoutLeftMargin); headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); headerHLayout->addWidget(m_header); @@ -190,11 +195,13 @@ void PageContainer::createUI() void PageContainer::retranslate() { - if (m_currentPage) + if (m_currentPage) { m_header->setText(m_currentPage->displayName()); + } - for (auto page : m_model->pages()) + for (auto* page : m_model->pages()) { page->retranslate(); + } } void PageContainer::addButtons(QWidget* buttons) @@ -236,22 +243,23 @@ void PageContainer::help() { if (m_currentPage) { QString pageId = m_currentPage->helpPage(); - if (pageId.isEmpty()) + if (pageId.isEmpty()) { return; + } DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(pageId))); } } void PageContainer::currentChanged(const QModelIndex& current) { - int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; + int selectedIndex = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; - auto* selected = m_model->pages().at(selected_index); + auto* selected = m_model->pages().at(selectedIndex); auto* previous = m_currentPage; emit selectedPageChanged(previous, selected); - showPage(selected_index); + showPage(selectedIndex); } bool PageContainer::prepareToClose() @@ -267,9 +275,10 @@ bool PageContainer::prepareToClose() bool PageContainer::saveAll() { - for (auto page : m_model->pages()) { - if (!page->apply()) + for (auto* page : m_model->pages()) { + if (!page->apply()) { return false; + } } return true; } diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 2c7ca9e39..2ca5e6f08 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -56,8 +56,8 @@ class QGridLayout; class PageContainer : public QWidget, public BasePageContainer { Q_OBJECT public: - explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = 0); - virtual ~PageContainer() {} + explicit PageContainer(BasePageProvider* pageProvider, QString defaultId = QString(), QWidget* parent = nullptr); + ~PageContainer() override = default; void addButtons(QWidget* buttons); void addButtons(QLayout* buttons); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 2fd5c97c2..1c4fa3596 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -47,30 +47,31 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->setOpacity(0.4); // Fade out the entire item } // The default icon size will be a square (and height is usually the lower value). - auto icon_width = rect.height(), icon_height = rect.height(); + auto icon_width = rect.height(); int icon_x_margin = (rect.height() - icon_width) / 2; - int icon_y_margin = (rect.height() - icon_height) / 2; if (!opt.icon.isNull()) { // Icon painting + auto icon_height = 0; { auto icon_size = opt.decorationSize; icon_width = icon_size.width(); icon_height = icon_size.height(); - icon_y_margin = (rect.height() - icon_height) / 2; - icon_x_margin = icon_y_margin; // use same margins for consistency + icon_x_margin = (rect.height() - icon_height) / 2; // use same margins for consistency } // Centralize icon with a margin to separate from the other elements int x = rect.x() + icon_x_margin; - int y = rect.y() + icon_y_margin; + int y = rect.y() + icon_x_margin; - if (opt.features & QStyleOptionViewItem::HasCheckIndicator) + if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { rect.translate(icon_x_margin / 2, 0); + } // Prevent 'scaling null pixmap' warnings - if (icon_width > 0 && icon_height > 0) + if (icon_width > 0 && icon_height > 0) { opt.icon.paint(painter, x, y, icon_width, icon_height); + } } // Change the rect so that funther painting is easier @@ -142,7 +143,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o description_y -= opt.fontMetrics.height(); // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) - painter->drawText(description_x, description_y, remaining_width, cut_text.size() * opt.fontMetrics.height(), Qt::TextWordWrap, + painter->drawText(description_x, description_y, remaining_width, num_lines * opt.fontMetrics.height(), Qt::TextWordWrap, description); } diff --git a/launcher/ui/widgets/VersionSelectWidget.cpp b/launcher/ui/widgets/VersionSelectWidget.cpp index 040355f4b..01b876146 100644 --- a/launcher/ui/widgets/VersionSelectWidget.cpp +++ b/launcher/ui/widgets/VersionSelectWidget.cpp @@ -127,9 +127,9 @@ void VersionSelectWidget::closeEvent(QCloseEvent* event) QWidget::closeEvent(event); } -void VersionSelectWidget::loadList() +void VersionSelectWidget::loadList(bool forceReload) { - m_load_task = m_vlist->getLoadTask(); + m_load_task = m_vlist->getLoadTask(forceReload); connect(m_load_task.get(), &Task::succeeded, this, &VersionSelectWidget::onTaskSucceeded); connect(m_load_task.get(), &Task::failed, this, &VersionSelectWidget::onTaskFailed); connect(m_load_task.get(), &Task::progress, this, &VersionSelectWidget::changeProgress); diff --git a/launcher/ui/widgets/VersionSelectWidget.h b/launcher/ui/widgets/VersionSelectWidget.h index c66d7e98e..ee9dbead7 100644 --- a/launcher/ui/widgets/VersionSelectWidget.h +++ b/launcher/ui/widgets/VersionSelectWidget.h @@ -57,7 +57,7 @@ class VersionSelectWidget : public QWidget { void initialize(BaseVersionList* vlist, bool forceLoad = false); //! Starts a task that loads the list. - void loadList(); + void loadList(bool forceReload = false); bool hasVersions() const; BaseVersion::Ptr selectedVersion() const; diff --git a/launcher/updater/PrismExternalUpdater.cpp b/launcher/updater/PrismExternalUpdater.cpp index a4e34e10a..00f8404cf 100644 --- a/launcher/updater/PrismExternalUpdater.cpp +++ b/launcher/updater/PrismExternalUpdater.cpp @@ -21,9 +21,8 @@ */ #include "PrismExternalUpdater.h" + #include -#include -#include #include #include #include @@ -58,19 +57,18 @@ PrismExternalUpdater::PrismExternalUpdater(QWidget* parent, const QString& appDi { priv->appDir = QDir(appDir); priv->dataDir = QDir(dataDir); - auto settings_file = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); - priv->settings = std::make_unique(settings_file, QSettings::Format::IniFormat); + auto settingsFile = priv->dataDir.absoluteFilePath("prismlauncher_update.cfg"); + priv->settings = std::make_unique(settingsFile, QSettings::Format::IniFormat); priv->allowBeta = priv->settings->value("allow_beta", false).toBool(); priv->autoCheck = priv->settings->value("auto_check", true).toBool(); - bool interval_ok = false; + bool intervalOk = false; // default once per day - priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&interval_ok); - if (!interval_ok) { + priv->updateInterval = priv->settings->value("update_interval", 86400).toInt(&intervalOk); + if (!intervalOk) { priv->updateInterval = 86400; } - auto last_check = priv->settings->value("last_check"); - if (!last_check.isNull() && last_check.isValid()) { - priv->lastCheck = QDateTime::fromString(last_check.toString(), Qt::ISODate); + if (const auto lastCheck = priv->settings->value("last_check"); !lastCheck.isNull() && lastCheck.isValid()) { + priv->lastCheck = QDateTime::fromString(lastCheck.toString(), Qt::ISODate); } priv->parent = parent; connectTimer(); @@ -95,9 +93,10 @@ void PrismExternalUpdater::checkForUpdates() checkForUpdates(true); } -void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) +void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) const { QProgressDialog progress(tr("Checking for updates..."), "", 0, 0, priv->parent); + progress.setMinimumDuration(0); // Appear immediately without waiting progress.setCancelButton(nullptr); progress.adjustSize(); if (triggeredByUser) { @@ -106,15 +105,15 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) QCoreApplication::processEvents(); QProcess proc; - auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#if defined Q_OS_WIN32 - exe_name.append(".exe"); + auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#ifdef Q_OS_WIN32 + exeName.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exe_name = QString("bin/%1").arg(exe_name); + exeName = QString("bin/%1").arg(exeName); #endif QStringList args = { "--check-only", "--dir", priv->dataDir.absolutePath(), "--debug" }; @@ -122,9 +121,8 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) args.append("--pre-release"); } - proc.start(priv->appDir.absoluteFilePath(exe_name), args); - auto result_start = proc.waitForStarted(5000); - if (!result_start) { + proc.start(priv->appDir.absoluteFilePath(exeName), args); + if (auto resultStart = proc.waitForStarted(5000); !resultStart) { auto err = proc.error(); qDebug() << "Failed to start updater after 5 seconds." << "reason:" << err << proc.errorString(); @@ -142,8 +140,7 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) } QCoreApplication::processEvents(); - auto result_finished = proc.waitForFinished(60000); - if (!result_finished) { + if (auto resultFinished = proc.waitForFinished(60000); !resultFinished) { proc.kill(); auto err = proc.error(); auto output = proc.readAll(); @@ -163,15 +160,15 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) return; } - auto exit_code = proc.exitCode(); + auto exitCode = proc.exitCode(); - auto std_output = proc.readAllStandardOutput(); - auto std_error = proc.readAllStandardError(); + auto stdOutput = proc.readAllStandardOutput(); + auto stdError = proc.readAllStandardError(); - progress.hide(); + progress.cancel(); QCoreApplication::processEvents(); - switch (exit_code) { + switch (exitCode) { case 0: // no update available if (triggeredByUser) { @@ -186,10 +183,10 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) case 1: // there was an error { - qDebug() << "Updater subprocess error" << qPrintable(std_error); + qDebug() << "Updater subprocess error" << qPrintable(stdError); auto msgBox = QMessageBox(QMessageBox::Warning, tr("Update Check Error"), tr("There was an error running the update check."), QMessageBox::Ok, priv->parent); - msgBox.setDetailedText(QString(std_error)); + msgBox.setDetailedText(QString(stdError)); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -198,28 +195,27 @@ void PrismExternalUpdater::checkForUpdates(bool triggeredByUser) case 100: // update available { - auto [first_line, remainder1] = StringUtils::splitFirst(std_output, '\n'); - auto [second_line, remainder2] = StringUtils::splitFirst(remainder1, '\n'); - auto [third_line, release_notes] = StringUtils::splitFirst(remainder2, '\n'); - auto version_name = StringUtils::splitFirst(first_line, ": ").second.trimmed(); - auto version_tag = StringUtils::splitFirst(second_line, ": ").second.trimmed(); - auto release_timestamp = QDateTime::fromString(StringUtils::splitFirst(third_line, ": ").second.trimmed(), Qt::ISODate); - qDebug() << "Update available:" << version_name << version_tag << release_timestamp; - qDebug() << "Update release notes:" << release_notes; + auto [firstLine, remainder1] = StringUtils::splitFirst(stdOutput, '\n'); + auto [secondLine, remainder2] = StringUtils::splitFirst(remainder1, '\n'); + auto [thirdLine, releaseNotes] = StringUtils::splitFirst(remainder2, '\n'); + auto versionName = StringUtils::splitFirst(firstLine, ": ").second.trimmed(); + auto versionTag = StringUtils::splitFirst(secondLine, ": ").second.trimmed(); + auto releaseTimestamp = QDateTime::fromString(StringUtils::splitFirst(thirdLine, ": ").second.trimmed(), Qt::ISODate); + qDebug() << "Update available:" << versionName << versionTag << releaseTimestamp; + qDebug() << "Update release notes:" << releaseNotes; - offerUpdate(version_name, version_tag, release_notes); + offerUpdate(versionName, versionTag, releaseNotes, triggeredByUser); } break; default: // unknown error code { - qDebug() << "Updater exited with unknown code" << exit_code; - auto msgBox = - QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), - tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exit_code)), - QMessageBox::Ok, priv->parent); - auto detail_txt = tr("StdOut: %1\nStdErr: %2").arg(QString(std_output)).arg(QString(std_error)); - msgBox.setDetailedText(detail_txt); + qDebug() << "Updater exited with unknown code" << exitCode; + auto msgBox = QMessageBox(QMessageBox::Information, tr("Unknown Update Error"), + tr("The updater exited with an unknown condition.\nExit Code: %1").arg(QString::number(exitCode)), + QMessageBox::Ok, priv->parent); + auto detailTxt = tr("StdOut: %1\nStdErr: %2").arg(QString(stdOutput)).arg(QString(stdError)); + msgBox.setDetailedText(detailTxt); msgBox.setMinimumWidth(460); msgBox.adjustSize(); msgBox.exec(); @@ -269,7 +265,7 @@ void PrismExternalUpdater::setBetaAllowed(bool allowed) priv->settings->sync(); } -void PrismExternalUpdater::resetAutoCheckTimer() +void PrismExternalUpdater::resetAutoCheckTimer() const { if (priv->autoCheck && priv->updateInterval > 0) { auto now = QDateTime::currentDateTime(); @@ -277,9 +273,9 @@ void PrismExternalUpdater::resetAutoCheckTimer() qint64 timeoutMs = 0; if (priv->lastCheck.isValid()) { - qint64 diff = priv->lastCheck.secsTo(now); - qint64 secs_left = std::max(priv->updateInterval - diff, 0); - timeoutMs = secs_left * 1000; + const qint64 diff = priv->lastCheck.secsTo(now); + const qint64 secsLeft = std::max(priv->updateInterval - diff, 0); + timeoutMs = secsLeft * 1000; } timeoutMs = std::min(timeoutMs, static_cast(INT_MAX)); @@ -303,69 +299,70 @@ void PrismExternalUpdater::disconnectTimer() disconnect(&priv->updateTimer, &QTimer::timeout, this, &PrismExternalUpdater::autoCheckTimerFired); } -void PrismExternalUpdater::autoCheckTimerFired() +void PrismExternalUpdater::autoCheckTimerFired() const { qDebug() << "Auto update Timer fired"; checkForUpdates(false); } -void PrismExternalUpdater::offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes) +void PrismExternalUpdater::offerUpdate(const QString& versionName, + const QString& versionTag, + const QString& releaseNotes, + const bool triggeredByUser) const { priv->settings->beginGroup("skip"); - auto should_skip = priv->settings->value(version_tag, false).toBool(); + auto shouldSkip = !triggeredByUser && priv->settings->value(versionTag, false).toBool(); priv->settings->endGroup(); - if (should_skip) { - auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), - QMessageBox::Ok, priv->parent); - msgBox.setMinimumWidth(460); - msgBox.adjustSize(); - msgBox.exec(); + if (shouldSkip) { + if (triggeredByUser) { + auto msgBox = QMessageBox(QMessageBox::Information, tr("No Update Available"), tr("There are no new updates available."), + QMessageBox::Ok, priv->parent); + msgBox.setMinimumWidth(460); + msgBox.adjustSize(); + msgBox.exec(); + } return; } - UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), version_name, release_notes); + UpdateAvailableDialog dlg(BuildConfig.printableVersionString(), versionName, releaseNotes); auto result = dlg.exec(); qDebug() << "offer dlg result" << result; - switch (result) { - case UpdateAvailableDialog::Install: { - performUpdate(version_tag); - return; - } - case UpdateAvailableDialog::Skip: { - priv->settings->beginGroup("skip"); - priv->settings->setValue(version_tag, true); - priv->settings->endGroup(); - priv->settings->sync(); - return; - } - default: { - return; + + priv->settings->beginGroup("skip"); + if (result == UpdateAvailableDialog::Skip) { + priv->settings->setValue(versionTag, true); + } else { + if (result == UpdateAvailableDialog::Install) { + performUpdate(versionTag); } + priv->settings->remove(versionTag); } + priv->settings->endGroup(); + priv->settings->sync(); } -void PrismExternalUpdater::performUpdate(const QString& version_tag) +void PrismExternalUpdater::performUpdate(const QString& versionTag) const { QProcess proc; - auto exe_name = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); -#if defined Q_OS_WIN32 - exe_name.append(".exe"); + auto exeName = QStringLiteral("%1_updater").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME); +#ifdef Q_OS_WIN32 + exeName.append(".exe"); auto env = QProcessEnvironment::systemEnvironment(); env.insert("__COMPAT_LAYER", "RUNASINVOKER"); proc.setProcessEnvironment(env); #else - exe_name = QString("bin/%1").arg(exe_name); + exeName = QString("bin/%1").arg(exeName); #endif - QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", version_tag }; + QStringList args = { "--dir", priv->dataDir.absolutePath(), "--install-version", versionTag }; if (priv->allowBeta) { args.append("--pre-release"); } - proc.setProgram(priv->appDir.absoluteFilePath(exe_name)); + proc.setProgram(priv->appDir.absoluteFilePath(exeName)); proc.setArguments(args); auto result = proc.startDetached(); if (!result) { diff --git a/launcher/updater/PrismExternalUpdater.h b/launcher/updater/PrismExternalUpdater.h index b88676028..b3f284b33 100644 --- a/launcher/updater/PrismExternalUpdater.h +++ b/launcher/updater/PrismExternalUpdater.h @@ -22,12 +22,10 @@ #pragma once -#include - #include "ExternalUpdater.h" /*! - * An implementation for the updater on windows and linux that uses out external updater. + * An implementation for the updater on Windows and linux that uses out external updater. */ class PrismExternalUpdater : public ExternalUpdater { @@ -41,7 +39,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Check for updates manually, showing the user a progress bar and an alert if no updates are found. */ void checkForUpdates() override; - void checkForUpdates(bool triggeredByUser); + void checkForUpdates(bool triggeredByUser) const; /*! * Indicates whether or not to check for updates automatically. @@ -62,7 +60,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set whether or not to check for updates automatically. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." + * reverting this property without kicking off a schedule change immediately. */ void setAutomaticallyChecksForUpdates(bool check) override; @@ -70,7 +68,7 @@ class PrismExternalUpdater : public ExternalUpdater { * Set the current automatic update check interval in seconds. * * The update schedule cycle will be reset in a short delay after the property’s new value is set. This is to allow - * reverting this property without kicking off a schedule change immediately." + * reverting this property without kicking off a schedule change immediately. */ void setUpdateCheckInterval(double seconds) override; @@ -79,15 +77,15 @@ class PrismExternalUpdater : public ExternalUpdater { */ void setBetaAllowed(bool allowed) override; - void resetAutoCheckTimer(); + void resetAutoCheckTimer() const; void disconnectTimer(); void connectTimer(); - void offerUpdate(const QString& version_name, const QString& version_tag, const QString& release_notes); - void performUpdate(const QString& version_tag); + void offerUpdate(const QString& versionName, const QString& versionTag, const QString& releaseNotes, bool triggeredByUser) const; + void performUpdate(const QString& versionTag) const; public slots: - void autoCheckTimerFired(); + void autoCheckTimerFired() const; private: class Private; diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 478eb7b3e..829a4843c 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -3,12 +3,12 @@ stdenv, cmake, cmark, - extra-cmake-modules, gamemode, jdk17, kdePackages, libnbtplusplus, ninja, + pkg-config, qrencode, self, stripJavaArchivesHook, @@ -35,6 +35,13 @@ let ] else "unknown"; + + # Remove once https://github.com/NixOS/nixpkgs/pull/518987 lands + extra-cmake-modules = kdePackages.extra-cmake-modules.overrideAttrs (prevAttrs: { + meta = prevAttrs.meta // { + platforms = lib.platforms.all; + }; + }); in stdenv.mkDerivation { @@ -65,6 +72,7 @@ stdenv.mkDerivation { cmake ninja extra-cmake-modules + pkg-config jdk17 stripJavaArchivesHook ]; diff --git a/nix/wrapper.nix b/nix/wrapper.nix index 00752a8c4..23fc04d9f 100644 --- a/nix/wrapper.nix +++ b/nix/wrapper.nix @@ -6,6 +6,7 @@ glfw3-minecraft, jdk17, jdk21, + jdk25, jdk8, kdePackages, lib, @@ -34,6 +35,7 @@ controllerSupport ? stdenv.hostPlatform.isLinux, gamemodeSupport ? stdenv.hostPlatform.isLinux, jdks ? [ + jdk25 jdk21 jdk17 jdk8 diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index ba6b7e061..83335ce9d 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -4,6 +4,7 @@ !include "x64.nsh" +AllowSkipFiles off Unicode true Name "@Launcher_DisplayName@" diff --git a/renovate.json b/renovate.json index 0a74c6de7..856b2e91c 100644 --- a/renovate.json +++ b/renovate.json @@ -4,7 +4,7 @@ "config:recommended" ], "labels": [ - "area: CI", + "area: actions", "complexity: low", "priority: low", "type: robot", diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 145e6b3d7..26fe5ab16 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -217,8 +217,8 @@ class ResourceFolderModelTest : public QObject { auto& res_1 = model.at(0).type() != ResourceType::FOLDER ? model.at(0) : model.at(1); auto& res_2 = model.at(0).type() == ResourceType::FOLDER ? model.at(0) : model.at(1); - auto id_1 = res_1.internal_id(); - auto id_2 = res_2.internal_id(); + auto id_1 = res_1.internalId(); + auto id_2 = res_2.internalId(); bool initial_enabled_res_2 = res_2.enabled(); bool initial_enabled_res_1 = res_1.enabled(); @@ -236,12 +236,12 @@ class ResourceFolderModelTest : public QObject { qDebug() << "res_1 got successfully toggled again."; QVERIFY(res_1.enabled() == initial_enabled_res_1); - QVERIFY(res_1.internal_id() == id_1); + QVERIFY(res_1.internalId() == id_1); qDebug() << "res_1 got back to its initial state."; QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE)); QVERIFY(res_2.enabled() == initial_enabled_res_2); - QVERIFY(res_2.internal_id() == id_2); + QVERIFY(res_2.internalId() == id_2); } }; diff --git a/vcpkg.json b/vcpkg.json index 5fd336fff..942e6d9e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -26,7 +26,6 @@ ] }, "tomlplusplus", - "zlib", - "vulkan-headers" + "zlib" ] }