Compare commits

..

23 commits

Author SHA1 Message Date
Alexandru Ionut Tripon
9a9e8573aa
[release-11.x] chore: bump version (#5376) 2026-04-12 18:38:53 +03:00
Octol1ttle
399482270f
chore: bump version
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
2026-04-12 20:35:15 +05:00
Alexandru Ionut Tripon
50df409b28
[Backport release-11.x] Updater: Do not reset current task in finished signal (#5372) 2026-04-12 11:52:52 +03:00
Octol1ttle
add3d01f84 fix(updater): do not reset current task in finished signal
The order of signals in case of a success is "succeeded"->"finished"

The "succeeded" signal may launch another download if the updater needs to fetch more pages
But if we reset the task then the newly started download will be disposed and the updater will softlock

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

View file

@ -23,14 +23,14 @@ body:
- macOS
- Linux
- Other
- type: input
- type: textarea
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: input
- type: textarea
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.

View file

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

View file

@ -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.23
uses: hendrikmuhs/ccache-action@v1.2.22
with:
variant: sccache
create-symlink: ${{ runner.os != 'Windows' }}

View file

@ -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
libxcb-cursor-dev libtomlplusplus-dev libvulkan-dev
- name: Setup AppImage tooling
shell: bash

View file

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

View file

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

View file

@ -28,14 +28,19 @@ 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 source generators
- name: Run build
# 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 && cmake --build build --target autogen autorcc
cmake -B build -D Launcher_USE_PCH=OFF -D CMAKE_CXX_COMPILER_LAUNCHER=sccache && cmake --build build
'
# TODO: Use SARIF after https://github.com/psastras/sarif-rs/issues/638 is fixed

View file

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

View file

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

View file

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

View file

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

View file

@ -13,10 +13,6 @@ 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/")
@ -183,9 +179,9 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
set(Launcher_LEGACY_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for legacy (<=1.5.2) FML Libraries.")
######## Set version numbers ########
set(Launcher_VERSION_MAJOR 12)
set(Launcher_VERSION_MAJOR 11)
set(Launcher_VERSION_MINOR 0)
set(Launcher_VERSION_PATCH 0)
set(Launcher_VERSION_PATCH 2)
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")

View file

@ -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 scdoc zlib1g-dev \
cmark gamemode-dev libarchive-dev libcmark-dev libgamemode0 libgl1-mesa-dev libqrencode-dev libtomlplusplus-dev libvulkan-dev scdoc zlib1g-dev \
# Tooling
clang-format clang-tidy git

View file

@ -194,10 +194,8 @@ class Config {
QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2";
QString MODRINTH_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;
/**

8
flake.lock generated
View file

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

View file

@ -578,14 +578,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
{
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");
}
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");
}
{
@ -777,7 +779,6 @@ 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", "");
@ -870,7 +871,6 @@ 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,6 +935,15 @@ 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");
@ -1013,13 +1022,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
qInfo() << "<> Cache initialized.";
}
// load translations
{
m_translations.reset(new TranslationsModel("translations"));
m_translations->downloadIndex();
qInfo() << "Your language is" << m_translations->selectedLanguage();
qInfo() << "<> Translations loaded.";
}
// now we have network, download translation updates
m_translations->downloadIndex();
// FIXME: what to do with these?
m_profilers.insert("jprofiler", std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));

View file

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

View file

@ -1206,6 +1206,78 @@ 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
@ -1224,6 +1296,12 @@ 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})
@ -1243,7 +1321,7 @@ endif()
####### Targets ########
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_RESOURCES})
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
@ -1363,7 +1441,7 @@ endif()
if(Launcher_BUILD_UPDATER)
# Updater
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES})
add_library(prism_updater_logic STATIC ${PRISMUPDATER_SOURCES} ${TASKS_SOURCES} ${PRISMUPDATER_UI})
target_include_directories(prism_updater_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if(${Launcher_USE_PCH})
@ -1491,10 +1569,8 @@ if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
target_compile_options(Launcher_logic PRIVATE /wd4100) # C4100 - unused parameter
target_compile_options(${Launcher_Name} PRIVATE /wd4100) # C4100 - unused parameter
else()
# 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)
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)
endif()
#### The bundle mess! ####

View file

@ -36,7 +36,6 @@
*/
#include "FileSystem.h"
#include <qcontainerfwd.h>
#include <QPair>
#include "BuildConfig.h"
@ -684,32 +683,6 @@ 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
@ -823,33 +796,68 @@ QString NormalizePath(QString path)
}
}
namespace {
const QString g_badChars = "<>:\"|?*\r\n!";
QString removeChars(QString source, QChar replace, const QString& extraChars = "")
{
auto badChars = g_badChars;
if (!extraChars.isEmpty()) {
badChars += extraChars;
}
static const QString BAD_WIN_CHARS = "<>:\"|?*\r\n";
static const QString BAD_NTFS_CHARS = "<>:\"|?*";
static const QString BAD_HFS_CHARS = ":";
for (auto& c : source) {
if (c.unicode() < 0x20 || !c.isPrint() || badChars.contains(c)) {
c = replace;
}
}
return source;
}
} // namespace
static const QString BAD_FILENAME_CHARS = BAD_WIN_CHARS + "\\/";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
return removeChars(std::move(string), replaceWith, "\\/");
for (int i = 0; i < string.length(); i++)
if (string.at(i) < ' ' || BAD_FILENAME_CHARS.contains(string.at(i)))
string[i] = replaceWith;
return string;
}
QString RemoveInvalidPathChars(QString string, QChar replaceWith)
QString RemoveInvalidPathChars(QString path, QChar replaceWith)
{
return removeChars(std::move(string), replaceWith);
QString invalidChars;
#ifdef Q_OS_WIN
invalidChars = BAD_WIN_CHARS;
#endif
// the null character is ignored in this check as it was not a problem until now
switch (statFS(path).fsType) {
case FilesystemType::FAT: // similar to NTFS
/* fallthrough */
case FilesystemType::NTFS:
/* fallthrough */
case FilesystemType::REFS: // similar to NTFS(should be available only on windows)
invalidChars += BAD_NTFS_CHARS;
break;
// case FilesystemType::EXT:
// case FilesystemType::EXT_2_OLD:
// case FilesystemType::EXT_2_3_4:
// case FilesystemType::XFS:
// case FilesystemType::BTRFS:
// case FilesystemType::NFS:
// case FilesystemType::ZFS:
case FilesystemType::APFS:
/* fallthrough */
case FilesystemType::HFS:
/* fallthrough */
case FilesystemType::HFSPLUS:
/* fallthrough */
case FilesystemType::HFSX:
invalidChars += BAD_HFS_CHARS;
break;
// case FilesystemType::FUSEBLK:
// case FilesystemType::F2FS:
// case FilesystemType::UNKNOWN:
default:
break;
}
if (invalidChars.size() != 0) {
for (int i = 0; i < path.length(); i++) {
if (path.at(i) < ' ' || invalidChars.contains(path.at(i))) {
path[i] = replaceWith;
}
}
}
return path;
}
QString DirNameFromString(QString string, QString inDir)

View file

@ -291,13 +291,6 @@ 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);
/**

View file

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

View file

@ -27,16 +27,3 @@ 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

View file

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

View file

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

View file

@ -40,7 +40,6 @@
#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"
@ -110,7 +109,7 @@ void LaunchController::decideAccount()
}
}
if (!m_accountToUse && accounts->anyAccountIsValid()) {
if (!m_accountToUse) {
// If no default account is set, ask the user which one to use.
ProfileSelectDialog selectDialog(tr("Which account would you like to use?"), ProfileSelectDialog::GlobalDefaultCheckbox,
m_parentWidget);
@ -134,6 +133,14 @@ 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;
@ -156,9 +163,7 @@ LaunchDecision LaunchController::decideLaunchMode()
}
auto state = accountToCheck->accountState();
const bool needsRefresh =
m_wantedLaunchMode == LaunchMode::Normal && (state == AccountState::Offline || accountToCheck->shouldRefresh());
if (state == AccountState::Unchecked || state == AccountState::Errored || needsRefresh) {
if (state == AccountState::Unchecked || state == AccountState::Errored) {
accountToCheck->refresh();
state = AccountState::Working;
}
@ -226,14 +231,13 @@ bool LaunchController::askPlayDemo() const
return box.clickedButton() == demoButton;
}
QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
QString LaunchController::askOfflineName(const QString& playerName, bool* ok) const
{
if (ok != nullptr) {
*ok = false;
}
QString title, message;
title = tr("Player name");
QString message;
switch (m_actualLaunchMode) {
case LaunchMode::Normal:
Q_ASSERT(false);
@ -243,14 +247,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
break;
case LaunchMode::Offline:
if (m_wantedLaunchMode == LaunchMode::Normal) {
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("You are not connected to the Internet, launching in offline mode\n\n");
}
message += tr("Choose your offline mode player name");
break;
@ -260,7 +257,7 @@ QString LaunchController::askOfflineName(const QString& playerName, bool* ok)
QString usedname = lastOfflinePlayerName.isEmpty() ? playerName : lastOfflinePlayerName;
ChooseOfflineNameDialog dialog(message, m_parentWidget);
dialog.setWindowTitle(title);
dialog.setWindowTitle(tr("Player name"));
dialog.setUsername(usedname);
if (dialog.exec() != QDialog::Accepted) {
return {};
@ -342,11 +339,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) {

View file

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

View file

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

View file

@ -14,26 +14,26 @@
else \
type = Qt::DirectConnection;
#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE, RET_DEF) \
#define DEFINE_FUNC_NO_PARAM(NAME, RET_TYPE) \
static RET_TYPE NAME() \
{ \
RET_TYPE ret = RET_DEF; \
RET_TYPE ret; \
GET_TYPE() \
QMetaObject::invokeMethod(s_instance, "_" #NAME, type, Q_RETURN_ARG(RET_TYPE, ret)); \
return ret; \
}
#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, RET_DEF, PARAM_1_TYPE) \
#define DEFINE_FUNC_ONE_PARAM(NAME, RET_TYPE, PARAM_1_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1) \
{ \
RET_TYPE ret = RET_DEF; \
RET_TYPE ret; \
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, RET_DEF, PARAM_1_TYPE, PARAM_2_TYPE) \
#define DEFINE_FUNC_TWO_PARAM(NAME, RET_TYPE, PARAM_1_TYPE, PARAM_2_TYPE) \
static RET_TYPE NAME(PARAM_1_TYPE p1, PARAM_2_TYPE p2) \
{ \
RET_TYPE ret = RET_DEF; \
RET_TYPE ret; \
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, -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)
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)
// NOTE: Every function returns something non-void to simplify the macros.
private slots:

View file

@ -19,52 +19,23 @@
#include "ResourceDownloadTask.h"
#include <utility>
#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<MinecraftInstance*>(instance);
if (!mcInstance) {
return {};
}
auto* profile = mcInstance->getPackProfile();
if (!profile) {
return {};
}
auto loaders = profile->getModLoadersList();
return {
.reason = std::move(reason),
.gameVersion = profile->getComponentVersion("net.minecraft"),
.loader = !loaders.isEmpty() ? ModPlatform::getModLoaderAsString(loaders.first()) : "",
};
}
} // namespace
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
ResourceFolderModel* packs,
bool isIndexed,
QString downloadReason)
bool is_indexed)
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
{
if (isIndexed) {
if (is_indexed) {
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
@ -74,9 +45,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
m_filesNetJob->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()),
Net::Download::Option::NoOptions,
createModrinthMeta(m_pack_model->instance(), std::move(downloadReason)));
auto action = Net::ApiDownload::makeFile(m_pack_version.downloadUrl, m_pack_model->dir().absoluteFilePath(getFilename()));
if (!m_pack_version.hash_type.isEmpty() && !m_pack_version.hash.isEmpty()) {
switch (Hashing::algorithmFromString(m_pack_version.hash_type)) {
case Hashing::Algorithm::Md4:
@ -113,9 +82,8 @@ 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);
@ -127,9 +95,8 @@ 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()));
}
}
}
}
@ -137,7 +104,7 @@ void ResourceDownloadTask::downloadSucceeded()
void ResourceDownloadTask::downloadFailed(QString reason)
{
m_filesNetJob.reset();
emitFailed(std::move(reason));
emitFailed(reason);
}
void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
@ -147,7 +114,7 @@ void ResourceDownloadTask::downloadProgressChanged(qint64 current, qint64 total)
// This indirection is done so that we don't delete a mod before being sure it was
// downloaded successfully!
void ResourceDownloadTask::hasOldResource(const QString& name, const QString& filename)
void ResourceDownloadTask::hasOldResource(QString name, QString filename)
{
to_delete = { name, filename };
}

View file

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

View file

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

View file

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

View file

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

View file

@ -82,12 +82,12 @@ QUrl BaseEntity::url() const
return QUrl(metaOverride).resolved(localFilename());
}
Task::Ptr BaseEntity::loadTask(Net::Mode mode, bool forceReload)
Task::Ptr BaseEntity::loadTask(Net::Mode mode)
{
if (m_task && m_task->isRunning()) {
return m_task;
}
m_task.reset(new BaseEntityLoadTask(this, mode, forceReload));
m_task.reset(new BaseEntityLoadTask(this, mode));
return m_task;
}
@ -107,9 +107,7 @@ BaseEntity::LoadStatus BaseEntity::status() const
return m_load_status;
}
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode, bool forceReload)
: m_entity(parent), m_mode(mode), m_force_reload(forceReload)
{}
BaseEntityLoadTask::BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode) : m_entity(parent), m_mode(mode) {}
void BaseEntityLoadTask::executeTask()
{
@ -127,11 +125,9 @@ void BaseEntityLoadTask::executeTask()
}
// on online the hash needs to match
const auto& expected = m_entity->m_sha256;
const auto& actual = m_entity->m_file_sha256;
hashMatches = expected == actual;
hashMatches = m_entity->m_sha256 == m_entity->m_file_sha256;
if (m_mode == Net::Mode::Online && !m_entity->m_sha256.isEmpty() && !hashMatches) {
throw Exception(QString("Checksum mismatch, expected sha256: %1, got: %2").arg(expected, actual));
throw Exception("mismatched checksum");
}
// load local file
@ -153,18 +149,13 @@ 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 && !m_force_reload)) {
if (wasLoadedOffline || wasLoadedRemote) {
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);
/*

View file

@ -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, bool forceReload = false);
[[nodiscard]] Task::Ptr loadTask(Net::Mode loadType = Net::Mode::Online);
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, bool forceReload);
explicit BaseEntityLoadTask(BaseEntity* parent, Net::Mode mode);
~BaseEntityLoadTask() override = default;
virtual void executeTask() override;
@ -68,7 +68,6 @@ class BaseEntityLoadTask : public Task {
private:
BaseEntity* m_entity;
Net::Mode m_mode;
bool m_force_reload = false;
NetJob::Ptr m_task;
};
} // namespace Meta

View file

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

View file

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

View file

@ -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(bool forceReload = false) override;
Task::Ptr getLoadTask() override;
const BaseVersion::Ptr at(int i) const override;
int count() const override;
void sortVersions() override;

View file

@ -149,7 +149,7 @@ QList<Net::NetRequest::Ptr> 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 << "expected sha1:" << sha1;
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
} else {
out.append(Net::ApiDownload::makeCached(url, entry, options));

View file

@ -131,8 +131,7 @@
for (const auto& gpu : gpus) {
QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]);
bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]);
bool discrete = qvariant_cast<bool>(gpu.value(QStringLiteral("Discrete"), !defaultGpu));
if (discrete) {
if (!defaultGpu) {
QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]);
for (int i = 0; i + 1 < envList.size(); i += 2) {
env.insert(envList[i], envList[i + 1]);
@ -893,14 +892,6 @@ 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;

View file

@ -7,27 +7,30 @@
#include "minecraft/PackProfile.h"
#include "settings/INISettingsObject.h"
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))
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))
{}
std::unique_ptr<MinecraftInstance> VanillaCreationTask::createInstance()
{
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto inst = std::make_unique<MinecraftInstance>(
m_globalSettings, std::make_unique<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")), m_stagingPath);
auto inst = std::make_unique<MinecraftInstance>(m_globalSettings, std::make_unique<INISettingsObject>(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;
}

View file

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

View file

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

View file

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

View file

@ -648,17 +648,9 @@ 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);
@ -668,12 +660,9 @@ void AccountList::tryNext()
<< accountId;
return;
}
break;
}
}
if (!found) {
qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found.";
}
qDebug() << "RefreshSchedule: Account with internal ID" << accountId << "not found.";
}
// if we get here, no account needed refreshing. Schedule refresh in an hour.
m_refreshTimer->start(1000 * 3600);

View file

@ -56,11 +56,10 @@ 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()) && !Net::isServerError(m_request->error())) {
if (Net::isApplicationError(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;

View file

@ -113,12 +113,6 @@ 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;
@ -126,6 +120,12 @@ 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"));

View file

@ -52,11 +52,10 @@ void MinecraftProfileStep::onRequestDone(QByteArray* response)
qWarning() << " Response:";
qWarning() << QString::fromUtf8(*response);
if (Net::isApplicationError(m_request->error()) && !Net::isServerError(m_request->error())) {
if (Net::isApplicationError(m_request->error())) {
emit finished(AccountTaskState::STATE_FAILED_SOFT,
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()));
}

View file

@ -2,7 +2,7 @@
#include <QJsonDocument>
#include <QJsonParseError>
#include <utility>
#include <QNetworkRequest>
#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(std::move(relyingParty)), m_authorizationKind(std::move(authorizationKind))
: AuthStep(data), m_token(token), m_relyingParty(relyingParty), m_authorizationKind(authorizationKind)
{}
QString XboxAuthorizationStep::describe()
@ -22,7 +22,7 @@ QString XboxAuthorizationStep::describe()
void XboxAuthorizationStep::perform()
{
const QString xboxAuthTemplate = R"XXX(
QString xbox_auth_template = R"XXX(
{
"Properties": {
"SandboxId": "RETAIL",
@ -34,13 +34,15 @@ void XboxAuthorizationStep::perform()
"TokenType": "JWT"
}
)XXX";
const auto xboxAuthData = xboxAuthTemplate.arg(m_data->userToken.token, m_relyingParty);
auto xbox_auth_data = xbox_auth_template.arg(m_data->userToken.token, m_relyingParty);
// http://xboxlive.com
const QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
auto headers = QList<Net::HeaderPair>{ { .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());
QUrl url("https://xsts.auth.xboxlive.com/xsts/authorize");
auto headers = QList<Net::HeaderPair>{
{ "Content-Type", "application/json" },
{ "Accept", "application/json" },
{ "x-xbl-contract-version", "1" }
};
auto [request, response] = Net::Upload::makeByteArray(url, xbox_auth_data.toUtf8());
m_request = request;
m_request->addHeaderProxy(std::make_unique<Net::RawHeaderProxy>(headers));
m_request->enableAutoRetry(true);
@ -60,14 +62,15 @@ 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()) && !Net::isServerError(m_request->error())) {
if (processSTSError(*response)) {
return;
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()));
}
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()));
}
@ -96,8 +99,8 @@ bool XboxAuthorizationStep::processSTSError(const QByteArray& response)
{
if (m_request->error() == QNetworkReply::AuthenticationRequiredError) {
QJsonParseError jsonError;
const QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
QJsonDocument doc = QJsonDocument::fromJson(response, &jsonError);
if (jsonError.error) {
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()));

View file

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

View file

@ -25,72 +25,22 @@ EnsureAvailableMemory::EnsureAvailableMemory(LaunchTask* parent, MinecraftInstan
void EnsureAvailableMemory::executeTask()
{
#ifdef Q_OS_MACOS
QString text;
switch (MacOSHardwareInfo::memoryPressureLevel()) {
case MacOSHardwareInfo::MemoryPressureLevel::Normal:
emitSucceeded();
return;
case MacOSHardwareInfo::MemoryPressureLevel::Warning:
text =
tr("The system is under increased memory pressure.\n"
"This may lead to lag or slowdowns.\n"
"If possible, close other applications before continuing.\n\n"
"Launch anyway?");
break;
case MacOSHardwareInfo::MemoryPressureLevel::Critical:
text =
tr("Your system is under critical memory pressure.\n"
"This may lead to severe slowdowns, crashes or system instability.\n"
"It is recommended to close other applications or restart your system.\n\n"
"Launch anyway?");
break;
}
bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable(nullptr, tr("High memory pressure"), text, QMessageBox::Icon::Warning,
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
shouldAbort = dialog->exec() == QMessageBox::No;
dialog->deleteLater();
}
const auto message = tr("The system is under high memory pressure");
if (shouldAbort) {
emit logLine(message, MessageLevel::Fatal);
emitFailed(message);
return;
}
emit logLine(message, MessageLevel::Warning);
emitSucceeded();
#else
const uint64_t available = HardwareInfo::availableRamMiB();
if (available == 0) {
// could not read
emitSucceeded();
return;
}
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);
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<double>(max) * 0.9 > static_cast<double>(available)) {
if (static_cast<double>(required) * 0.9 > static_cast<double>(available)) {
bool shouldAbort = false;
if (m_instance->settings()->get("LowMemWarning").toBool()) {
auto* dialog = CustomMessageBox::selectable(
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()),
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),
QMessageBox::Icon::Warning, QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No,
QMessageBox::StandardButton::No);
@ -109,5 +59,4 @@ void EnsureAvailableMemory::executeTask()
}
emitSucceeded();
#endif
}

View file

@ -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: %1");
emit logLine(QString(reason).arg(m_process.errorString()), MessageLevel::Fatal);
emitFailed(tr(reason).arg(m_process.errorString()));
const char* reason = QT_TR_NOOP("Could not launch Minecraft!");
emit logLine(reason, MessageLevel::Fatal);
emitFailed(tr(reason));
return;
}
case LoggedProcess::Aborted:

View file

@ -68,12 +68,7 @@ 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 << "";

View file

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

View file

@ -40,26 +40,25 @@
#include <QIcon>
#include <QStyle>
#include "Version.h"
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent)
DataPackFolderModel::DataPackFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
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 };
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 };
}
QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index)) {
if (!validateIndex(index))
return {};
}
int row = index.row();
int column = index.column();
@ -68,13 +67,11 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
case Qt::BackgroundRole:
return rowBackground(row);
case Qt::DisplayRole:
if (column == PackFormatColumn) {
const auto& resource = at(row);
return resource.packFormatStr();
}
if (column == SizeColumn) {
const auto& resource = at(row);
return resource.sizeStr();
switch (column) {
case PackFormatColumn: {
const auto& resource = at(row);
return resource.packFormatStr();
}
}
break;
case Qt::DecorationRole: {
@ -95,8 +92,6 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32);
}
break;
default:
break;
}
// map the columns to the base equivilents
@ -114,14 +109,7 @@ QVariant DataPackFolderModel::data(const QModelIndex& index, int role) const
case ProviderColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::ProviderColumn);
break;
case FileNameColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::FileNameColumn);
break;
case SizeColumn:
mappedIndex = index.siblingAtColumn(ResourceFolderModel::SizeColumn);
break;
default:
break;
// FIXME: there is no size column due to an oversight
}
if (mappedIndex.isValid()) {
@ -141,8 +129,6 @@ 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 {};
@ -159,10 +145,6 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
return tr("The data pack format ID, as well as the Minecraft versions it was designed for.");
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 {};
}
@ -178,7 +160,7 @@ QVariant DataPackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
int DataPackFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NumColumns;
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Resource* DataPackFolderModel::createResource(const QFileInfo& file)
@ -188,5 +170,5 @@ Resource* DataPackFolderModel::createResource(const QFileInfo& file)
Task* DataPackFolderModel::createParseTask(Resource& resource)
{
return new LocalDataPackParseTask(m_nextResolutionTicket, static_cast<DataPack*>(&resource));
return new LocalDataPackParseTask(m_next_resolution_ticket, static_cast<DataPack*>(&resource));
}

View file

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

View file

@ -40,7 +40,6 @@
#include <QDir>
#include <QRegularExpression>
#include <QString>
#include <algorithm>
#include "MTPixmapCache.h"
#include "MetadataHandler.h"
@ -50,34 +49,6 @@
#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");
@ -90,18 +61,18 @@ void Mod::setDetails(const ModDetails& details)
int Mod::compare(const Resource& other, SortType type) const
{
auto cast_other = dynamic_cast<const Mod*>(&other);
auto cast_other = dynamic_cast<Mod const*>(&other);
if (!cast_other)
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)
@ -110,38 +81,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::McVersions: {
auto compare_result = compareVersionLists(mcVersions(), cast_other->mcVersions());
case SortType::MC_VERSIONS: {
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
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::ReleaseType: {
case SortType::RELEASE_TYPE: {
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
case SortType::RequiredBy: {
case SortType::REQUIRED_BY: {
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())
@ -226,19 +197,14 @@ auto Mod::side() const -> QString
return ModPlatform::SideUtils::toString(ModPlatform::Side::UniversalSide);
}
auto Mod::mcVersions() const -> QStringList
auto Mod::mcVersions() const -> QString
{
if (metadata())
return metadata()->mcVersions;
return metadata()->mcVersions.join(", ");
return {};
}
auto Mod::mcVersionsString() const -> QString
{
return mcVersions().join(", ");
}
auto Mod::releaseType() const -> QString
{
if (metadata())

View file

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

View file

@ -51,39 +51,38 @@
#include <QUrl>
#include <QUuid>
#include <algorithm>
#include <set>
#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 isIndexed, bool createDir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent)
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
m_columnNames = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
"Minecraft Versions", "Release Type", "Requires", "Required By", "File Name" });
m_columnNamesTranslated =
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 =
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider"), tr("Size"), tr("Side"),
tr("Loaders"), tr("Minecraft Versions"), tr("Release Type"), tr("Requires"), tr("Required By"), tr("File Name") });
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 };
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 };
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();
@ -110,7 +109,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
return at(row).loaders();
}
case McVersionsColumn: {
return at(row).mcVersionsString();
return at(row).mcVersions();
}
case ReleaseTypeColumn: {
return at(row).releaseType();
@ -121,8 +120,6 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
case RequiresColumn: {
return at(row).requiresCount();
}
default:
break;
}
break;
case Qt::DecorationRole: {
@ -158,11 +155,6 @@ 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()) {
@ -190,7 +182,6 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
case SizeColumn:
case RequiredByColumn:
case RequiresColumn:
case FileNameColumn:
return columnNames().at(section);
default:
return QVariant();
@ -222,8 +213,6 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
return tr("For each mod, the number of other mods which depend on it.");
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();
}
@ -234,12 +223,12 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
int ModFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NumColumns;
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ModFolderModel::createParseTask(Resource& resource)
{
return new LocalModParseTask(m_nextResolutionTicket, resource.type(), resource.fileinfo());
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
}
bool ModFolderModel::isValid()
@ -247,37 +236,35 @@ bool ModFolderModel::isValid()
return m_dir.exists() && m_dir.isReadable();
}
void ModFolderModel::onParseSucceeded(int ticket, const QString& resourceId)
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
{
auto iter = m_activeParseTasks.constFind(ticket);
if (iter == m_activeParseTasks.constEnd()) {
auto iter = m_active_parse_tasks.constFind(ticket);
if (iter == m_active_parse_tasks.constEnd())
return;
}
int row = m_resourcesIndex[resourceId];
int row = m_resources_index[mod_id];
const auto& parseTask = *iter;
auto* castTask = static_cast<LocalModParseTask*>(parseTask.get());
auto parse_task = *iter;
auto cast_task = static_cast<LocalModParseTask*>(parse_task.get());
Q_ASSERT(castTask->token() == ticket);
Q_ASSERT(cast_task->token() == ticket);
auto resource = find(resourceId);
auto resource = find(mod_id);
auto result = castTask->result();
auto result = cast_task->result();
if (result && resource) {
auto* mod = static_cast<Mod*>(resource.get());
mod->finishResolvingWithDetails(std::move(result->details));
}
emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn));
}
namespace {
Mod* findById(QSet<Mod*> mods, const QString& resourceId)
Mod* findById(QSet<Mod*> mods, QString modId)
{
auto found = std::ranges::find_if(mods, [resourceId](Mod* m) { return m->mod_id() == resourceId; });
auto found = std::find_if(mods.begin(), mods.end(), [modId](Mod* m) { return m->mod_id() == modId; });
return found != mods.end() ? *found : nullptr;
}
} // namespace
void ModFolderModel::onParseFinished()
{
@ -290,25 +277,25 @@ void ModFolderModel::onParseFinished()
m_requires.clear();
m_requiredBy.clear();
auto findByProjectID = [mods](const QVariant& modId, ModPlatform::ResourceProvider provider) -> Mod* {
auto found = std::ranges::find_if(mods, [modId, provider](Mod* m) {
auto findByProjectID = [mods](QVariant modId, ModPlatform::ResourceProvider provider) -> Mod* {
auto found = std::find_if(mods.begin(), mods.end(), [modId, provider](Mod* m) {
return m->metadata() && m->metadata()->provider == provider && m->metadata()->project_id == modId;
});
return found != mods.end() ? *found : nullptr;
};
for (auto* mod : mods) {
for (auto mod : mods) {
auto id = mod->mod_id();
for (const auto& dep : mod->dependencies()) {
auto* d = findById(mods, dep);
for (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 (const auto& dep : mod->metadata()->dependencies) {
for (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;
@ -317,31 +304,30 @@ 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_resourcesIndex[mod->internalId()];
int row = m_resources_index[mod->internal_id()];
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
}
}
namespace {
QSet<Mod*> collectMods(const QSet<Mod*>& mods, QHash<QString, QSet<Mod*>> relation, std::set<QString>& seen, bool shouldBeEnabled)
QSet<Mod*> collectMods(QSet<Mod*> mods, QHash<QString, QSet<Mod*>> relation, std::set<QString>& seen, bool shouldBeEnabled)
{
QSet<Mod*> affectedList = {};
QSet<Mod*> needToCheck = {};
for (auto* mod : mods) {
for (auto mod : mods) {
auto id = mod->mod_id();
if (!seen.contains(id)) {
if (seen.count(id) == 0) {
seen.insert(id);
for (auto* affected : relation[id]) {
for (auto affected : relation[id]) {
auto affectedId = affected->mod_id();
if (findById(mods, affectedId) == nullptr && !seen.contains(affectedId)) {
if (findById(mods, affectedId) == nullptr && seen.count(affectedId) == 0) {
seen.insert(affectedId);
if (shouldBeEnabled != affected->enabled()) {
affectedList << affected;
}
@ -356,13 +342,11 @@ QSet<Mod*> collectMods(const QSet<Mod*>& mods, QHash<QString, QSet<Mod*>> relati
}
return affectedList;
}
} // namespace
QModelIndexList ModFolderModel::getAffectedMods(const QModelIndexList& indexes, EnableAction action)
{
if (indexes.isEmpty()) {
if (indexes.isEmpty())
return {};
}
QModelIndexList affectedList = {};
auto affectedModsList = selectedMods(indexes);
@ -382,9 +366,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_resourcesIndex[affected->internalId()];
auto row = m_resources_index[affected->internal_id()];
affectedList << index(row, 0);
}
return affectedList;
@ -392,9 +376,8 @@ 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());
@ -413,7 +396,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 {
@ -428,10 +411,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](const QSet<Mod*>& mods) {
auto toList = [this](QSet<Mod*> mods) {
QModelIndexList list;
for (auto* mod : mods) {
auto row = m_resourcesIndex[mod->internalId()];
for (auto mod : mods) {
auto row = m_resources_index[mod->internal_id()];
list << index(row, 0);
}
return list;
@ -464,8 +447,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();
@ -483,23 +466,21 @@ bool ModFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAc
return disableStatus && enableStatus;
}
namespace {
QStringList reqToList(const QSet<Mod*>& l)
QStringList reqToList(QSet<Mod*> l)
{
QStringList req;
for (auto* m : l) {
for (auto m : l) {
req << m->name();
}
return req;
}
} // namespace
QStringList ModFolderModel::requiresList(const QString& id)
QStringList ModFolderModel::requiresList(QString id)
{
return reqToList(m_requires[id]);
}
QStringList ModFolderModel::requiredByList(const QString& id)
QStringList ModFolderModel::requiredByList(QString id)
{
return reqToList(m_requiredBy[id]);
}
@ -508,7 +489,7 @@ bool ModFolderModel::deleteResources(const QModelIndexList& indexes)
{
auto deleteInvalid = [](QSet<Mod*>& 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())) {
@ -519,14 +500,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_resourcesIndex[mod->internalId()];
int row = m_resources_index[mod->internal_id()];
emit dataChanged(index(row, RequiresColumn), index(row, RequiredByColumn));
}
}

View file

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

View file

@ -4,75 +4,71 @@
#include <QFileInfo>
#include <QRegularExpression>
#include <tuple>
#include <utility>
#include "FileSystem.h"
#include "StringUtils.h"
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
Resource::Resource(QObject* parent) : QObject(parent), m_size_info(0) {}
Resource::Resource(QObject* parent) : QObject(parent) {}
Resource::Resource(QFileInfo fileInfo) : m_size_info(0)
Resource::Resource(QFileInfo file_info) : QObject()
{
setFile(fileInfo);
setFile(file_info);
}
void Resource::setFile(QFileInfo fileInfo)
void Resource::setFile(QFileInfo file_info)
{
m_file_info = std::move(fileInfo);
m_file_info = file_info;
parseFile();
}
namespace {
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
static std::tuple<QString, qint64> 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 fileName{ m_file_info.fileName() };
QString file_name{ m_file_info.fileName() };
m_type = ResourceType::UNKNOWN;
m_internal_id = fileName;
m_internal_id = file_name;
std::tie(m_size_str, m_size_info) = calculateFileSize(m_file_info);
if (m_file_info.isDir()) {
m_type = ResourceType::FOLDER;
m_name = fileName;
m_name = file_name;
} else if (m_file_info.isFile()) {
if (fileName.endsWith(".disabled")) {
fileName.chop(9);
if (file_name.endsWith(".disabled")) {
file_name.chop(9);
m_enabled = false;
}
if (fileName.endsWith(".zip") || fileName.endsWith(".jar")) {
if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) {
m_type = ResourceType::ZIPFILE;
fileName.chop(4);
} else if (fileName.endsWith(".nilmod")) {
file_name.chop(4);
} else if (file_name.endsWith(".nilmod")) {
m_type = ResourceType::ZIPFILE;
fileName.chop(7);
} else if (fileName.endsWith(".litemod")) {
file_name.chop(7);
} else if (file_name.endsWith(".litemod")) {
m_type = ResourceType::LITEMOD;
fileName.chop(8);
file_name.chop(8);
} else {
m_type = ResourceType::SINGLEFILE;
}
m_name = fileName;
m_name = file_name;
}
m_changed_date_time = m_file_info.lastModified();
@ -80,45 +76,39 @@ void Resource::parseFile()
auto Resource::name() const -> QString
{
if (metadata()) {
if (metadata())
return metadata()->name;
}
return m_name;
}
namespace {
void removeThePrefix(QString& string)
static 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::ModStruct>&& metadata)
{
if (status() == ResourceStatus::NoMetadata) {
setStatus(ResourceStatus::Installed);
}
if (status() == ResourceStatus::NO_METADATA)
setStatus(ResourceStatus::INSTALLED);
m_metadata = metadata;
}
@ -143,12 +133,12 @@ void Resource::updateIssues(const BaseInstance* inst)
return;
}
const auto* mcInst = dynamic_cast<const MinecraftInstance*>(inst);
auto mcInst = dynamic_cast<const MinecraftInstance*>(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)) {
@ -161,59 +151,46 @@ 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 thisName{ name() };
QString otherName{ other.name() };
case SortType::NAME: {
QString this_name{ name() };
QString other_name{ other.name() };
// TODO do we need this? it could result in 0 being returned
removeThePrefix(thisName);
removeThePrefix(otherName);
removeThePrefix(this_name);
removeThePrefix(other_name);
return QString::compare(thisName, otherName, Qt::CaseInsensitive);
return QString::compare(this_name, other_name, 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::Filename:
return fileinfo().fileName().localeAwareCompare(other.fileinfo().fileName());
case SortType::Size: {
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 compareResult = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
if (compareResult != 0) {
return compareResult;
}
case SortType::PROVIDER: {
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
if (compare_result != 0)
return compare_result;
break;
}
}
@ -223,20 +200,13 @@ int Resource::compare(const Resource& other, SortType type) const
bool Resource::applyFilter(QRegularExpression filter) const
{
if (filter.match(name()).hasMatch()) {
return true;
}
if (filter.match(fileinfo().fileName()).hasMatch()) {
return true;
}
return false;
return filter.match(name()).hasMatch();
}
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);
@ -255,16 +225,14 @@ 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";
@ -272,9 +240,8 @@ bool Resource::enable(EnableAction action)
path = FS::getUniqueResourceName(path);
}
}
if (!file.rename(path)) {
if (!file.rename(path))
return false;
}
setFile(QFileInfo(path));
@ -282,34 +249,33 @@ bool Resource::enable(EnableAction action)
return true;
}
auto Resource::destroy(const QDir& indexDir, bool preserveMetadata, bool attemptTrash) -> bool
auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
{
m_type = ResourceType::UNKNOWN;
if (!preserveMetadata) {
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
destroyMetadata(indexDir);
destroyMetadata(index_dir);
}
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
}
auto Resource::destroyMetadata(const QDir& indexDir) -> void
auto Resource::destroyMetadata(const QDir& index_dir) -> void
{
if (metadata()) {
Metadata::remove(indexDir, metadata()->slug);
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(indexDir, n);
Metadata::remove(index_dir, n);
}
m_metadata = nullptr;
}
bool Resource::isSymLinkUnder(const QString& instPath) const
{
if (isSymLink()) {
if (isSymLink())
return true;
}
auto instDir = QDir(instPath);
@ -327,9 +293,8 @@ 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;
}
@ -359,16 +324,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::NotInstalled:
case ResourceStatus::NOT_INSTALLED:
debug << "NOT_INSTALLED";
break;
case ResourceStatus::NoMetadata:
case ResourceStatus::NO_METADATA:
debug << "NO_METADATA";
break;
case ResourceStatus::Unknown:
case ResourceStatus::UNKNOWN:
default:
debug << "UNKNOWN";
break;

View file

@ -45,7 +45,7 @@
class BaseInstance;
enum class ResourceType : std::uint8_t {
enum class ResourceType {
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,33 +55,32 @@ enum class ResourceType : std::uint8_t {
QDebug operator<<(QDebug debug, ResourceType type);
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
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
};
QDebug operator<<(QDebug debug, ResourceStatus status);
enum class SortType : std::uint8_t {
Name,
Date,
Version,
Enabled,
PackFormat,
Provider,
Size,
Side,
McVersions,
Loaders,
ReleaseType,
Requires,
RequiredBy,
Filename,
enum class SortType {
NAME,
DATE,
VERSION,
ENABLED,
PACK_FORMAT,
PROVIDER,
SIZE,
SIDE,
MC_VERSIONS,
LOADERS,
RELEASE_TYPE,
REQUIRES,
REQUIRED_BY,
};
enum class EnableAction : std::uint8_t { ENABLE, DISABLE, TOGGLE };
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
/** General class for managed resources. It mirrors a file in disk, with some more info
* for display and house-keeping purposes.
@ -93,19 +92,20 @@ class Resource : public QObject {
Q_DISABLE_COPY(Resource)
public:
using Ptr = shared_qobject_ptr<Resource>;
using WeakPtr = QPointer<Resource>;
Resource(QObject* parent = nullptr);
Resource(QFileInfo fileInfo);
Resource(const QString& filePath) : Resource(QFileInfo(filePath)) {}
Resource(QFileInfo file_info);
Resource(QString file_path) : Resource(QFileInfo(file_path)) {}
~Resource() override = default;
void setFile(QFileInfo fileInfo);
void setFile(QFileInfo file_info);
void parseFile();
auto fileinfo() const -> QFileInfo { return m_file_info; }
auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; }
auto internalId() const -> QString { return m_internal_id; }
auto internal_id() const -> QString { return m_internal_id; }
auto type() const -> ResourceType { return m_type; }
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& indexDir, bool preserveMetadata = false, bool attemptTrash = true) -> bool;
auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
// Delete the metadata only.
auto destroyMetadata(const QDir& indexDir) -> void;
auto destroyMetadata(const QDir& index_dir) -> 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<Metadata::ModStruct> m_metadata = nullptr;

View file

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

View file

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

View file

@ -39,26 +39,27 @@
#include <QIcon>
#include <QStyle>
#include "Version.h"
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent)
: ResourceFolderModel(dir, instance, isIndexed, createDir, parent)
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
{
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 };
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 };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index)) {
if (!validateIndex(index))
return {};
}
int row = index.row();
int column = index.column();
@ -91,8 +92,6 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32);
}
break;
default:
break;
}
// map the columns to the base equivilents
@ -113,11 +112,6 @@ 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()) {
@ -139,7 +133,6 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
case ImageColumn:
case ProviderColumn:
case SizeColumn:
case FileNameColumn:
return columnNames().at(section);
default:
return {};
@ -160,8 +153,6 @@ 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 {};
}
@ -177,10 +168,10 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NumColumns;
return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
{
return new LocalDataPackParseTask(m_nextResolutionTicket, dynamic_cast<ResourcePack*>(&resource));
return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast<ResourcePack*>(&resource));
}

View file

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

View file

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

View file

@ -36,30 +36,28 @@
#include "TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool isIndexed, bool createDir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, isIndexed, createDir, parent)
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
{
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 };
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 };
}
Task* TexturePackFolderModel::createParseTask(Resource& resource)
{
return new LocalTexturePackParseTask(m_nextResolutionTicket, static_cast<TexturePack&>(resource));
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
}
QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
{
if (!validateIndex(index)) {
if (!validateIndex(index))
return {};
}
int row = index.row();
int column = index.column();
@ -78,8 +76,6 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
return QSize(32, 32);
}
break;
default:
break;
}
// map the columns to the base equivilents
@ -100,11 +96,6 @@ 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()) {
@ -125,7 +116,6 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
case ImageColumn:
case ProviderColumn:
case SizeColumn:
case FileNameColumn:
return columnNames().at(section);
default:
return {};
@ -142,8 +132,6 @@ 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 {};
}
@ -157,5 +145,5 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : NumColumns;
return parent.isValid() ? 0 : NUM_COLUMNS;
}

View file

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

View file

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

View file

@ -41,7 +41,7 @@
#include <QObject>
#include <QRunnable>
#include <memory>
#include "minecraft/mod/Resource.h"
#include "minecraft/mod/Mod.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& resourceDir,
const QDir& indexDir,
bool isIndexed,
bool cleanOrphan,
std::function<Resource*(const QFileInfo&)> createFunction);
ResourceFolderLoadTask(const QDir& resource_dir,
const QDir& index_dir,
bool is_indexed,
bool clean_orphan,
std::function<Resource*(const QFileInfo&)> create_function);
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<Resource*(const QFileInfo&)> m_create_func;
std::function<Resource*(QFileInfo const&)> m_create_func;
ResultPtr m_result;
std::atomic<bool> m_aborted = false;

View file

@ -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());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,7 +38,6 @@
#include <QtConcurrent>
#include <algorithm>
#include <utility>
#include "FileSystem.h"
#include "Json.h"
@ -60,28 +59,18 @@
#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()
@ -118,10 +107,11 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr)
QByteArray response = std::move(*responsePtr);
jobPtr.reset();
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();
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();
qWarning() << response;
return;
}
@ -138,7 +128,7 @@ void PackInstallTask::onDownloadSucceeded(QByteArray* responsePtr)
// Derived from the installation mode
QString message;
bool resetDirectory = false;
bool resetDirectory;
switch (m_install_mode) {
case InstallMode::Reinstall:
@ -158,9 +148,8 @@ 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) {
@ -184,7 +173,7 @@ void PackInstallTask::onDownloadFailed(QString reason)
{
qDebug() << "PackInstallTask::onDownloadFailed:" << QThread::currentThreadId();
jobPtr.reset();
emitFailed(std::move(reason));
emitFailed(reason);
}
void PackInstallTask::onDownloadAborted()
@ -213,30 +202,26 @@ 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;
}
if (base == "config") {
} else if (base == "config") {
return FS::PathCombine(minecraftPath, "config");
} else {
qWarning() << "Unrecognised base path" << base;
return minecraftPath;
}
qWarning() << "Unrecognised base path" << base;
return minecraftPath;
};
auto convertToSystemPath = [](const QString& path) {
@ -246,22 +231,24 @@ void PackInstallTask::deleteExistingFiles()
};
auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) {
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.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.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;
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;
}
}
return false;
@ -275,9 +262,8 @@ void PackInstallTask::deleteExistingFiles()
auto targetPath = convertToSystemPath(item.target);
auto fullPath = FS::PathCombine(basePath, targetPath);
if (shouldKeep(fullPath)) {
if (shouldKeep(fullPath))
continue;
}
filesToDelete.insert(fullPath);
}
@ -291,9 +277,8 @@ void PackInstallTask::deleteExistingFiles()
while (it.hasNext()) {
auto path = it.next();
if (shouldKeep(path)) {
if (shouldKeep(path))
continue;
}
filesToDelete.insert(path);
}
@ -305,7 +290,7 @@ void PackInstallTask::deleteExistingFiles()
}
}
QString PackInstallTask::getDirForModType(ModType type, const QString& raw)
QString PackInstallTask::getDirForModType(ModType type, QString raw)
{
switch (type) {
// Mod types that can either be ignored at this stage, or ignored
@ -353,7 +338,7 @@ QString PackInstallTask::getDirForModType(ModType type, const QString& raw)
return Q_NULLPTR;
}
QString PackInstallTask::getVersionForLoader(const QString& uid)
QString PackInstallTask::getVersionForLoader(QString uid)
{
if (m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) {
auto vlist = APPLICATION->metadataIndex()->get(uid);
@ -374,19 +359,16 @@ QString PackInstallTask::getVersionForLoader(const 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();
@ -394,8 +376,7 @@ QString PackInstallTask::getVersionForLoader(const QString& uid)
emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type));
return Q_NULLPTR;
}
if (m_version.loader.choose) {
} else 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);
@ -439,8 +420,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library)
if (name == QString("guava")) {
return "com.google.guava:guava:" + version;
}
if (name == QString("commons-lang3")) {
} else if (name == QString("commons-lang3")) {
return "org.apache.commons:commons-lang3:" + version;
}
}
@ -448,7 +428,7 @@ QString PackInstallTask::detectLibrary(const VersionLibrary& library)
return "org.multimc.atlauncher:" + library.md5 + ":1";
}
bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, PackProfile* profile)
bool PackInstallTask::createLibrariesComponent(QString instanceRoot, PackProfile* profile)
{
if (m_version.libraries.isEmpty()) {
return true;
@ -473,18 +453,18 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
}
auto id = QUuid::createUuid().toString(QUuid::WithoutBraces);
auto targetId = "org.multimc.atlauncher." + id;
auto target_id = "org.multimc.atlauncher." + id;
auto patchDir = FS::PathCombine(instanceRoot, "patches");
if (!FS::ensureFolderPathExists(patchDir)) {
return false;
}
auto patchFileName = FS::PathCombine(patchDir, targetId + ".json");
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
auto f = std::make_shared<VersionFile>();
f->name = m_pack_name + " " + m_version_name + " (libraries)";
const static QMap<QString, QString> s_liteLoaderMap = {
const static QMap<QString, QString> liteLoaderMap = {
{ "61179803bcd5fb7790789b790908663d", "1.12-SNAPSHOT" }, { "1420785ecbfed5aff4a586c5c9dd97eb", "1.12.2-SNAPSHOT" },
{ "073f68e2fcb518b91fd0d99462441714", "1.6.2_03" }, { "10a15b52fc59b1bfb9c05b56de1097d6", "1.6.2_02" },
{ "b52f90f08303edd3d4c374e268a5acf1", "1.6.2_04" }, { "ea747e24e03e24b7cad5bc8a246e0319", "1.6.2_01" },
@ -504,8 +484,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
for (const auto& lib : m_version.libraries) {
// If the library is LiteLoader, we need to ignore it and handle it separately.
if (s_liteLoaderMap.contains(lib.md5)) {
auto ver = getComponentVersion("com.mumfrey.liteloader", s_liteLoaderMap.value(lib.md5));
if (liteLoaderMap.contains(lib.md5)) {
auto ver = getComponentVersion("com.mumfrey.liteloader", liteLoaderMap.value(lib.md5));
if (ver) {
componentsToInstall.insert("com.mumfrey.liteloader", ver);
continue;
@ -522,9 +502,8 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
libExempt = Version(libSpecifier.version()) >= Version(existingLib.version());
}
}
if (libExempt) {
if (libExempt)
continue;
}
auto library = std::make_shared<Library>();
library->setRawName(libName);
@ -557,11 +536,11 @@ bool PackInstallTask::createLibrariesComponent(const QString& instanceRoot, Pack
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close();
profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) });
profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) });
return true;
}
bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfile* profile)
bool PackInstallTask::createPackComponent(QString instanceRoot, PackProfile* profile)
{
if (m_version.mainClass.mainClass.isEmpty() && m_version.extraArguments.arguments.isEmpty()) {
return true;
@ -592,13 +571,13 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
}
auto id = QUuid::createUuid().toString(QUuid::WithoutBraces);
auto targetId = "org.multimc.atlauncher." + id;
auto target_id = "org.multimc.atlauncher." + id;
auto patchDir = FS::PathCombine(instanceRoot, "patches");
if (!FS::ensureFolderPathExists(patchDir)) {
return false;
}
auto patchFileName = FS::PathCombine(patchDir, targetId + ".json");
auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
QStringList mainClasses;
QStringList tweakers;
@ -625,9 +604,8 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
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);
}
@ -646,7 +624,7 @@ bool PackInstallTask::createPackComponent(const QString& instanceRoot, PackProfi
file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
file.close();
profile->appendComponent(ComponentPtr{ new Component(profile, targetId, f) });
profile->appendComponent(ComponentPtr{ new Component(profile, target_id, f) });
return true;
}
@ -676,7 +654,7 @@ void PackInstallTask::installConfigs()
connect(jobPtr.get(), &NetJob::failed, [this](QString reason) {
abortable = false;
jobPtr.reset();
emitFailed(std::move(reason));
emitFailed(reason);
});
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
abortable = true;
@ -733,17 +711,15 @@ void PackInstallTask::downloadMods()
jarmods.clear();
jobPtr.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
QList<VersionMod> blockedMods;
QList<VersionMod> blocked_mods;
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) {
@ -751,7 +727,7 @@ void PackInstallTask::downloadMods()
url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
break;
case DownloadType::Browser: {
blockedMods.append(mod);
blocked_mods.append(mod);
continue;
}
case DownloadType::Direct:
@ -787,9 +763,8 @@ 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);
@ -823,51 +798,49 @@ void PackInstallTask::downloadMods()
modsToCopy[entry->getFullPath()] = path;
}
}
if (!blockedMods.isEmpty()) {
if (!blocked_mods.isEmpty()) {
QList<BlockedMod> mods;
for (const auto& mod : blockedMods) {
BlockedMod blockedMod;
blockedMod.name = mod.file;
blockedMod.websiteUrl = mod.url;
blockedMod.hash = mod.md5;
blockedMod.matched = false;
blockedMod.localPath = "";
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 = "";
mods.append(blockedMod);
mods.append(blocked_mod);
}
qWarning() << "Blocked mods found, displaying mod list";
BlockedModsDialog messageDialog(nullptr, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
mods, "md5");
BlockedModsDialog message_dialog(nullptr, tr("Blocked mods found"),
tr("The following files are not available for download in third party launchers.<br/>"
"You will need to manually download them and add them to the instance."),
mods, "md5");
messageDialog.setModal(true);
message_dialog.setModal(true);
if (messageDialog.exec() != 0) {
if (message_dialog.exec()) {
qDebug() << "Post dialog blocked mods list:" << mods;
for (const auto& blocked : mods) {
for (auto blocked : mods) {
if (!blocked.matched) {
qDebug() << blocked.name << "was not matched to a local file, skipping copy";
continue;
}
auto modIter =
std::ranges::find_if(blockedMods, [blocked](const VersionMod& mod) { return mod.url == blocked.websiteUrl; });
if (modIter == blockedMods.end()) {
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())
continue;
}
const auto& mod = *modIter;
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);
@ -945,8 +918,8 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
setStatus(tr("Extracting mods..."));
for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) {
const auto& modPath = iter.key();
const auto& mod = iter.value();
auto& modPath = iter.key();
auto& mod = iter.value();
QString extractToDir;
if (mod.type == ModType::Extract) {
@ -965,10 +938,6 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& 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;
@ -979,18 +948,13 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
}
for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) {
const auto& modPath = iter.key();
const auto& mod = iter.value();
auto& modPath = iter.key();
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;
@ -999,8 +963,8 @@ bool PackInstallTask::extractMods(const QMap<QString, VersionMod>& toExtract,
}
for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
const auto& from = iter.key();
const auto& to = iter.value();
auto& from = iter.key();
auto& to = iter.value();
// If the file already exists, assume the mod is the correct copy - and remove
// the copy from the Configs.zip
@ -1030,7 +994,7 @@ void PackInstallTask::install()
MinecraftInstance instance(m_globalSettings, std::make_unique<INISettingsObject>(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
@ -1045,23 +1009,20 @@ 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()) {
@ -1094,4 +1055,9 @@ void PackInstallTask::install()
emitSucceeded();
}
static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString& version)
{
return APPLICATION->metadataIndex()->getLoadedVersion(uid, version);
}
} // namespace ATLauncher

View file

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

View file

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

View file

@ -18,6 +18,8 @@
#include "net/NetJob.h"
#include "tasks/Task.h"
static FlameAPI api;
bool FlameCheckUpdate::abort()
{
bool result = false;
@ -37,7 +39,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);
@ -46,10 +48,9 @@ void FlameCheckUpdate::executeTask()
for (auto* resource : m_resources) {
auto project = std::make_shared<ModPlatform::IndexedPack>();
project->addonId = resource->metadata()->project_id.toString();
auto versionsUrlOptional = FlameAPI().getVersionsURL({ .pack = project, .mcVersions = m_gameVersions });
if (!versionsUrlOptional.has_value()) {
auto versionsUrlOptional = api.getVersionsURL({ project, m_gameVersions });
if (!versionsUrlOptional.has_value())
continue;
}
auto [task, response] = Net::ApiDownload::makeByteArray(versionsUrlOptional.value());
@ -62,11 +63,11 @@ void FlameCheckUpdate::executeTask()
void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray* response)
{
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();
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();
qWarning() << *response;
return;
}
@ -87,104 +88,100 @@ void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, QByteArray*
qCritical() << e.what();
qDebug() << doc;
}
auto latestVer = FlameAPI().getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty());
auto latest_ver = api.getLatestVersion(pack->versions, m_loadersList, resource->metadata()->loaders, !m_loadersList.isEmpty());
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
if (!latestVer.has_value() || !latestVer->addonId.isValid()) {
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
QString reason;
if (dynamic_cast<Mod*>(resource) != nullptr) {
if (dynamic_cast<Mod*>(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 (latestVer->downloadUrl.isEmpty() && latestVer->fileId != resource->metadata()->file_id) {
m_blocked[resource] = latestVer->fileId.toString();
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
m_blocked[resource] = latest_ver->fileId.toString();
return;
}
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");
}
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");
}
auto downloadTask = makeShared<ResourceDownloadTask>(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());
auto download_task = makeShared<ResourceDownloadTask>(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());
}
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latestVer.value()));
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
}
void FlameCheckUpdate::collectBlockedMods()
{
QStringList addonIds;
QHash<QString, Resource*> quickSearch;
for (const auto& resource : m_blocked.keys()) {
for (auto const& resource : m_blocked.keys()) {
auto addonId = resource->metadata()->project_id.toString();
addonIds.append(addonId);
quickSearch[addonId] = resource;
}
Task::Ptr projTask;
QByteArray* response = nullptr;
QByteArray* response;
if (addonIds.isEmpty()) {
emitSucceeded();
return;
}
if (addonIds.size() == 1) {
std::tie(projTask, response) = FlameAPI().getProject(*addonIds.begin());
} else if (addonIds.size() == 1) {
std::tie(projTask, response) = api.getProject(*addonIds.begin());
} else {
std::tie(projTask, response) = FlameAPI().getProjects(addonIds);
std::tie(projTask, response) = api.getProjects(addonIds);
}
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
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();
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();
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 entryObj = Json::requireObject(entry);
auto entry_obj = Json::requireObject(entry);
auto id = QString::number(Json::requireInteger(entryObj, "id"));
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
auto* resource = quickSearch.find(id).value();
auto resource = quickSearch.find(id).value();
ModPlatform::IndexedPack pack;
try {
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
FlameMod::loadIndexedPack(pack, entryObj);
auto recoverUrl = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
FlameMod::loadIndexedPack(pack, entry_obj);
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
recoverUrl);
recover_url);
} catch (Json::JsonException& e) {
qDebug() << e.cause();
qDebug() << entries;

View file

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

View file

@ -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,16 +86,28 @@ void PackInstallTask::copySettings()
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.loaderVersion, true);
break;
}
default:
case ModPlatform::Cauldron:
break;
case ModPlatform::LiteLoader:
break;
case ModPlatform::DataPack:
break;
case ModPlatform::Babric:
break;
case ModPlatform::BTA:
break;
case ModPlatform::LegacyFabric:
break;
case ModPlatform::Ornithe:
break;
case ModPlatform::Rift:
break;
}
}
components->saveNow();
instance.setName(name());
if (m_instIcon == "default") {
if (m_instIcon == "default")
m_instIcon = "ftb_logo";
}
instance.setIconKey(m_instIcon);
}
emitSucceeded();

View file

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

View file

@ -16,10 +16,7 @@
#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"
@ -32,119 +29,114 @@
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* instanceList = APPLICATION->instances();
auto instance_list = APPLICATION->instances();
// FIXME: How to handle situations when there's more than one install already for a given modpack?
BaseInstance* inst = nullptr;
if (auto originalId = originalInstanceID(); !originalId.isEmpty()) {
inst = instanceList->getInstanceById(originalId);
BaseInstance* inst;
if (auto original_id = originalInstanceID(); !original_id.isEmpty()) {
inst = instance_list->getInstanceById(original_id);
Q_ASSERT(inst);
} else {
inst = instanceList->getInstanceByManagedName(originalName());
inst = instance_list->getInstanceByManagedName(originalName());
if (!inst) {
inst = instanceList->getInstanceById(originalName());
inst = instance_list->getInstanceById(originalName());
if (!inst) {
if (!inst)
return false;
}
}
}
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(indexPath, m_files, true, false)) {
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (!parseManifest(index_path, m_files, true, false))
return false;
}
auto versionName = inst->getManagedPackVersionName();
auto version_name = inst->getManagedPackVersionName();
m_root_path = QFileInfo(inst->gameRoot()).fileName();
auto versionStr = !versionName.isEmpty() ? tr(" (version %1)").arg(versionName) : "";
auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : "";
if (shouldConfirmUpdate()) {
auto shouldUpdate = askIfShouldUpdate(m_parent, versionStr);
if (shouldUpdate == ShouldUpdate::SkipUpdating) {
auto should_update = askIfShouldUpdate(m_parent, version_str);
if (should_update == ShouldUpdate::SkipUpdating)
return false;
}
if (shouldUpdate == ShouldUpdate::Cancel) {
if (should_update == ShouldUpdate::Cancel) {
m_abort = true;
return false;
}
}
// Remove repeated files, we don't need to download them!
QDir oldInstDir(inst->instanceRoot());
QDir old_inst_dir(inst->instanceRoot());
QString oldIndexFolder(FS::PathCombine(oldInstDir.absolutePath(), "mrpack"));
QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack"));
QString oldIndexPath(FS::PathCombine(oldIndexFolder, "modrinth.index.json"));
QFileInfo oldIndexFile(oldIndexPath);
if (oldIndexFile.exists()) {
std::vector<File> oldFiles;
parseManifest(oldIndexPath, oldFiles, false, false);
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<File> old_files;
parseManifest(old_index_path, old_files, false, false);
// Let's remove all duplicated, identical resources!
auto filesIterator = m_files.begin();
auto files_iterator = m_files.begin();
begin:
while (filesIterator != m_files.end()) {
const auto& file = *filesIterator;
while (files_iterator != m_files.end()) {
auto const& file = *files_iterator;
auto oldFilesIterator = oldFiles.begin();
while (oldFilesIterator != oldFiles.end()) {
const auto& oldFile = *oldFilesIterator;
auto old_files_iterator = old_files.begin();
while (old_files_iterator != old_files.end()) {
auto const& old_file = *old_files_iterator;
if (oldFile.hash == file.hash) {
if (old_file.hash == file.hash) {
qDebug() << "Removed file at" << file.path << "from list of downloads";
filesIterator = m_files.erase(filesIterator);
oldFilesIterator = oldFiles.erase(oldFilesIterator);
files_iterator = m_files.erase(files_iterator);
old_files_iterator = old_files.erase(old_files_iterator);
goto begin; // Sorry :c
}
oldFilesIterator++;
old_files_iterator++;
}
filesIterator++;
files_iterator++;
}
QDir oldMinecraftDir(inst->gameRoot());
QDir old_minecraft_dir(inst->gameRoot());
// Some files were removed from the old version, and some will be downloaded in an updated version,
// so we're fine removing them!
if (!oldFiles.empty()) {
for (const auto& file : oldFiles) {
scheduleToDelete(m_parent, oldMinecraftDir, file.path, true);
if (!old_files.empty()) {
for (auto const& file : old_files) {
scheduleToDelete(m_parent, old_minecraft_dir, 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 oldOverrides = Override::readOverrides("overrides", oldIndexFolder);
for (const auto& entry : oldOverrides) {
scheduleToDelete(m_parent, oldMinecraftDir, entry);
auto old_overrides = Override::readOverrides("overrides", old_index_folder);
for (const auto& entry : old_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);
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);
}
} 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;
@ -166,40 +158,39 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
{
QEventLoop loop;
QString parentFolder(FS::PathCombine(m_stagingPath, "mrpack"));
QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack"));
QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(indexPath, m_files, true, true)) {
QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json");
if (m_files.empty() && !parseManifest(index_path, m_files, true, true))
return nullptr;
}
// Keep index file in case we need it some other time (like when changing versions)
QString newIndexPlace(FS::PathCombine(parentFolder, "modrinth.index.json"));
FS::ensureFilePathExists(newIndexPlace);
FS::move(indexPath, newIndexPlace);
QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json"));
FS::ensureFilePathExists(new_index_place);
FS::move(index_path, new_index_place);
auto mcPath = FS::PathCombine(m_stagingPath, m_root_path);
auto overridePath = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(overridePath)) {
auto override_path = FS::PathCombine(m_stagingPath, "overrides");
if (QFile::exists(override_path)) {
// Create a list of overrides in "overrides.txt" inside mrpack/
Override::createOverrides("overrides", parentFolder, overridePath);
Override::createOverrides("overrides", parent_folder, override_path);
// Apply the overrides
if (!FS::move(overridePath, mcPath)) {
if (!FS::move(override_path, mcPath)) {
setError(tr("Could not rename the overrides folder:\n") + "overrides");
return nullptr;
}
}
// Do client overrides
auto clientOverridePath = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(clientOverridePath)) {
auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides");
if (QFile::exists(client_override_path)) {
// Create a list of overrides in "client-overrides.txt" inside mrpack/
Override::createOverrides("client-overrides", parentFolder, clientOverridePath);
Override::createOverrides("client-overrides", parent_folder, client_override_path);
// Apply the overrides
if (!FS::overrideFolder(mcPath, clientOverridePath)) {
if (!FS::overrideFolder(mcPath, client_override_path)) {
setError(tr("Could not rename the client overrides folder:\n") + "client overrides");
return nullptr;
}
@ -209,27 +200,18 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
auto instanceSettings = std::make_unique<INISettingsObject>(configPath);
auto instance = std::make_unique<MinecraftInstance>(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);
QString loader;
if (!m_fabric_version.isEmpty()) {
if (!m_fabric_version.isEmpty())
components->setComponentVersion("net.fabricmc.fabric-loader", m_fabric_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Fabric);
}
if (!m_quilt_version.isEmpty()) {
if (!m_quilt_version.isEmpty())
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Quilt);
}
if (!m_forge_version.isEmpty()) {
if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", m_forge_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::Forge);
}
if (!m_neoForge_version.isEmpty()) {
if (!m_neoForge_version.isEmpty())
components->setComponentVersion("net.neoforged", m_neoForge_version);
loader = ModPlatform::getModLoaderAsString(ModPlatform::ModLoaderType::NeoForge);
}
if (m_instIcon != "default") {
instance->setIconKey(m_instIcon);
@ -238,35 +220,34 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
}
// Don't add managed info to packs without an ID (most likely imported from ZIP)
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<NetJob>(tr("Mod Download Modrinth"), APPLICATION->network());
auto rootModpackPath = FS::PathCombine(m_stagingPath, m_root_path);
auto rootModpackUrl = QUrl::fromLocalFile(rootModpackPath);
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
// TODO make this work with other sorts of resource
QHash<QString, Resource*> resources;
for (auto& file : m_files) {
auto fileName = file.path;
fileName = FS::RemoveInvalidPathChars(fileName);
auto filePath = FS::PathCombine(rootModpackPath, fileName);
if (!rootModpackUrl.isParentOf(QUrl::fromLocalFile(filePath))) {
auto file_path = FS::PathCombine(root_modpack_path, fileName);
if (!root_modpack_url.isParentOf(QUrl::fromLocalFile(file_path))) {
// This means we somehow got out of the root folder, so abort here to prevent exploits
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(filePath);
auto mod = new Mod(file_path);
ModDetails d;
d.mod_id = filePath;
d.mod_id = file_path;
mod->setDetails(d);
resources[file.hash.toHex()] = mod;
}
@ -274,39 +255,29 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
return nullptr;
}
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);
qDebug() << "Will try to download" << file.downloads.front() << "to" << file_path;
auto dl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
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, filePath, param, downloadMods, meta] {
QUrl fallbackUrl = file.downloads.dequeue();
auto ndl = Net::ApiDownload::makeFile(fallbackUrl, filePath, Net::Download::Option::NoOptions, meta);
connect(dl.get(), &Task::failed, [&file, file_path, param, downloadMods] {
auto ndl = Net::ApiDownload::makeFile(file.downloads.dequeue(), file_path);
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 endedWell = false;
bool ended_well = false;
connect(downloadMods.get(), &NetJob::succeeded, this, [&endedWell]() { endedWell = true; });
connect(downloadMods.get(), &NetJob::failed, [this, &endedWell](const QString& reason) {
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;
setError(reason);
});
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
@ -322,8 +293,8 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
loop.exec();
if (!endedWell) {
for (auto* resource : resources) {
if (!ended_well) {
for (auto resource : resources) {
delete resource;
}
return nullptr;
@ -332,7 +303,7 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
QEventLoop ensureMetaLoop;
QDir folder = FS::PathCombine(instance->modsRoot(), ".index");
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&endedWell]() { endedWell = true; });
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
@ -344,38 +315,40 @@ std::unique_ptr<MinecraftInstance> ModrinthCreationTask::createInstance()
m_task = ensureMetadataTask;
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 && endedWell) {
if (m_instance && ended_well) {
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 (endedWell) {
if (ended_well) {
return instance;
}
return nullptr;
}
bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<File>& files, bool setInternalData, bool showOptionalDialog)
bool ModrinthCreationTask::parseManifest(const QString& index_path,
std::vector<File>& files,
bool set_internal_data,
bool show_optional_dialog)
{
try {
auto doc = Json::requireDocument(indexPath);
auto doc = Json::requireDocument(index_path);
auto obj = Json::requireObject(doc, "modrinth.index.json");
int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json");
if (formatVersion == 1) {
@ -384,10 +357,9 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
throw JSONValidationError("Unknown game: " + game);
}
if (setInternalData) {
if (m_managed_version_id.isEmpty()) {
if (set_internal_data) {
if (m_managed_version_id.isEmpty())
m_managed_version_id = obj["versionId"].toString();
}
m_managed_name = obj["name"].toString();
}
@ -403,8 +375,7 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
QString support = env["client"].toString("unsupported");
if (support == "unsupported") {
continue;
}
if (support == "optional") {
} else if (support == "optional") {
file.required = false;
}
}
@ -416,21 +387,20 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
// Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode
// (as Modrinth seems to incorrectly handle spaces)
auto downloadArr = modInfo["downloads"].toArray();
for (auto download : downloadArr) {
auto download_arr = modInfo["downloads"].toArray();
for (auto download : download_arr) {
qWarning() << download.toString();
bool isLast = download.toString() == downloadArr.last().toString();
bool is_last = download.toString() == download_arr.last().toString();
auto downloadUrl = QUrl(download.toString());
auto download_url = QUrl(download.toString());
if (!downloadUrl.isValid()) {
if (!download_url.isValid()) {
qDebug()
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(downloadUrl.toString(), file.path);
if (isLast && file.downloads.isEmpty()) {
<< QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path);
if (is_last && file.downloads.isEmpty())
throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path));
}
} else {
file.downloads.push_back(downloadUrl);
file.downloads.push_back(download_url);
}
}
@ -438,11 +408,10 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
if (!optionalFiles.empty()) {
if (showOptionalDialog) {
if (show_optional_dialog) {
QStringList oFiles;
for (const auto& file : optionalFiles) {
for (auto file : optionalFiles)
oFiles.push_back(file.path);
}
OptionalModDialog optionalModDialog(m_parent, oFiles);
if (optionalModDialog.exec() == QDialog::Rejected) {
emitAborted();
@ -465,7 +434,7 @@ bool ModrinthCreationTask::parseManifest(const QString& indexPath, std::vector<F
}
}
}
if (setInternalData) {
if (set_internal_data) {
auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json");
for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) {
QString name = it.key();

View file

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

View file

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

View file

@ -20,17 +20,13 @@
#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<Download::Ptr, QByteArray*> makeByteArray(QUrl url, Download::Options options = Download::Option::NoOptions);
Download::Ptr makeFile(QUrl url,
QString path,
Download::Options options = Download::Option::NoOptions,
ModrinthDownloadMeta meta = ModrinthDownloadMeta());
Download::Ptr makeFile(QUrl url, QString path, Download::Options options = Download::Option::NoOptions);
}; // namespace ApiDownload
} // namespace Net

View file

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

View file

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

View file

@ -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::deleteContents(map.base_path);
ret &= FS::deletePath(map.base_path);
}
return ret;
}

View file

@ -69,15 +69,11 @@ void NetJob::executeNextSubTask()
// We're finished, check for failures and retry if we can (up to 3 times)
if (isRunning() && m_queue.isEmpty() && m_doing.isEmpty() && !m_failed.isEmpty() && m_try < 3) {
m_try += 1;
m_failed.removeIf([this](QHash<Task*, Task::Ptr>::iterator task) {
// there is no point in retying on 404 Not Found
if (static_cast<Net::NetRequest*>(task->get())->replyStatusCode() == 404) {
return false;
}
m_done.remove(task->get());
m_queue.enqueue(*task);
return true;
});
while (!m_failed.isEmpty()) {
auto task = m_failed.take(*m_failed.keyBegin());
m_done.remove(task.get());
m_queue.enqueue(task);
}
}
ConcurrentTask::executeNextSubTask();
}
@ -104,18 +100,13 @@ 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) {

View file

@ -40,18 +40,4 @@ 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<QNetworkReply::NetworkError> errors = { QNetworkReply::InternalServerError,
QNetworkReply::OperationNotImplementedError,
QNetworkReply::ServiceUnavailableError, // 503 | seen in logs in 2026
//QNetworkReply::GatewayTimeoutError, // 504 | seen in logs in 2024
// Qt doesn't have it mapped. Unknown covers it
QNetworkReply::UnknownServerError };
return errors.contains(x);
}
} // namespace Net

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

View file

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

View file

@ -35,8 +35,6 @@
*/
#include "settings/INIFile.h"
#include <AssertHelpers.h>
#include <FileSystem.h>
#include <QDebug>
@ -64,10 +62,11 @@ 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 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!)";
qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
return false;
}
@ -179,9 +178,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 while loading INI file" << fileName;
qCritical() << "An access error occurred (e.g. trying to write to a read-only file).";
if (status == QSettings::Status::FormatError)
qCritical() << "A format error occurred while loading INI file" << fileName << "(is the file malformed or corrupted?)";
qCritical() << "A format error occurred (e.g. loading a malformed INI file).";
return false;
}
if (!_settings_obj.value("ConfigVersion").isValid()) {

View file

@ -48,13 +48,6 @@ 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) {

View file

@ -94,7 +94,7 @@ class Task : public QObject, public QRunnable {
public:
explicit Task(bool show_debug_log = true);
~Task() override;
virtual ~Task() = default;
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() && isRunning())
if (canAbort())
emitAborted();
return canAbort();
}

View file

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

View file

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

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