Merge branch 'develop' into curseforge-url-handle
This commit is contained in:
commit
9957aeb003
4
.git-blame-ignore-revs
Normal file
4
.git-blame-ignore-revs
Normal file
@ -0,0 +1,4 @@
|
||||
# .git-blame-ignore-revs
|
||||
|
||||
# tabs -> spaces
|
||||
bbb3b3e6f6e3c0f95873f22e6d0a4aaf350f49d9
|
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: ''
|
||||
qt_version: '6.5.0'
|
||||
qt_version: '6.5.1'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
@ -80,13 +80,13 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: windows
|
||||
qt_arch: 'win64_msvc2019_arm64'
|
||||
qt_version: '6.5.0'
|
||||
qt_version: '6.5.1'
|
||||
qt_modules: 'qt5compat qtimageformats'
|
||||
qt_tools: ''
|
||||
|
||||
- os: macos-12
|
||||
name: macOS
|
||||
macosx_deployment_target: 10.15
|
||||
macosx_deployment_target: 11.0
|
||||
qt_ver: 6
|
||||
qt_host: mac
|
||||
qt_arch: ''
|
||||
@ -191,7 +191,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc appstream
|
||||
|
||||
- name: Install Dependencies (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
@ -250,6 +250,7 @@ jobs:
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
|
||||
${{ github.workspace }}/.github/scripts/prepare_JREs.sh
|
||||
sudo apt install libopengl0
|
||||
|
||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
@ -375,6 +376,8 @@ jobs:
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }}
|
||||
touch ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
@ -387,6 +390,10 @@ jobs:
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libcrypto-1_1.dll -Destination libcrypto-1_1.dll
|
||||
Copy-Item D:/a/PrismLauncher/Qt/Tools/OpenSSL/Win_x86/bin/libssl-1_1.dll -Destination libssl-1_1.dll
|
||||
}
|
||||
cd ${{ github.workspace }}
|
||||
|
||||
Get-ChildItem ${{ env.INSTALL_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
|
||||
- name: Fetch codesign certificate (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@ -411,12 +418,15 @@ jobs:
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=$(cygpath -u $l); l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done >> ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows MSVC, portable)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
run: |
|
||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
|
||||
Get-ChildItem ${{ env.INSTALL_PORTABLE_DIR }} -Recurse | ForEach FullName | Resolve-Path -Relative | %{ $_.TrimStart('.\') } | %{ $_.TrimStart('${{ env.INSTALL_PORTABLE_DIR }}') } | %{ $_.TrimStart('\') } | Out-File -FilePath ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
- name: Package (Windows, installer)
|
||||
if: runner.os == 'Windows'
|
||||
@ -437,6 +447,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
||||
for l in $(find ${{ env.INSTALL_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_DIR }}/manifest.txt
|
||||
|
||||
cd ${{ env.INSTALL_DIR }}
|
||||
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
||||
@ -446,6 +457,8 @@ jobs:
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
||||
for l in $(find ${{ env.INSTALL_PORTABLE_DIR }} -type f); do l=${l#$(pwd)/}; l=${l#${{ env.INSTALL_PORTABLE_DIR }}/}; l=${l#./}; echo $l; done > ${{ env.INSTALL_PORTABLE_DIR }}/manifest.txt
|
||||
|
||||
|
||||
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||
@ -455,7 +468,8 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr
|
||||
|
||||
mv ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.metainfo.xml ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/metainfo/org.prismlauncher.PrismLauncher.appdata.xml
|
||||
export "NO_APPSTREAM=1" # we have to skip appstream checking because appstream on ubuntu 20.04 is outdated
|
||||
export OUTPUT="PrismLauncher-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage"
|
||||
|
||||
chmod +x linuxdeploy-*.AppImage
|
||||
@ -470,7 +484,8 @@ jobs:
|
||||
cp -r /home/runner/work/PrismLauncher/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines
|
||||
|
||||
cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
cp /usr/lib/x86_64-linux-gnu/libOpenGL.so.0* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/
|
||||
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib"
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server"
|
||||
@ -587,7 +602,7 @@ jobs:
|
||||
submodules: 'true'
|
||||
- name: Install nix
|
||||
if: inputs.build_type == 'Debug'
|
||||
uses: cachix/install-nix-action@v20
|
||||
uses: cachix/install-nix-action@v22
|
||||
with:
|
||||
install_url: https://nixos.org/nix/install
|
||||
extra_nix_config: |
|
||||
|
2
.github/workflows/trigger_release.yml
vendored
2
.github/workflows/trigger_release.yml
vendored
@ -47,7 +47,7 @@ jobs:
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ env.VERSION }}.tar.gz
|
||||
|
||||
tar -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||
|
||||
for d in PrismLauncher-Windows-MSVC*; do
|
||||
cd "${d}" || continue
|
||||
|
28
.github/workflows/update-flake.yml
vendored
Normal file
28
.github/workflows/update-flake.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: Update Flake Lockfile
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run weekly on sunday
|
||||
- cron: "0 0 * * 0"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-flake:
|
||||
if: github.repository == 'PrismLauncher/PrismLauncher'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v22
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v19
|
||||
with:
|
||||
commit-msg: "chore(nix): update lockfile"
|
||||
pr-title: "chore(nix): update lockfile"
|
||||
pr-labels: |
|
||||
Linux
|
||||
simple change
|
@ -138,7 +138,7 @@ set(Launcher_NEWS_OPEN_URL "https://prismlauncher.org/news" CACHE STRING "URL th
|
||||
set(Launcher_HELP_URL "https://prismlauncher.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 7)
|
||||
set(Launcher_VERSION_MAJOR 8)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
@ -164,13 +164,13 @@ set(Launcher_BUG_TRACKER_URL "https://github.com/PrismLauncher/PrismLauncher/iss
|
||||
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
|
||||
|
||||
# Matrix Space
|
||||
set(Launcher_MATRIX_URL "https://matrix.to/#/#prismlauncher:matrix.org" CACHE STRING "URL to the Matrix Space")
|
||||
set(Launcher_MATRIX_URL "https://prismlauncher.org/matrix" CACHE STRING "URL to the Matrix Space")
|
||||
|
||||
# Discord URL
|
||||
set(Launcher_DISCORD_URL "https://discord.gg/prismlauncher" CACHE STRING "URL for the Discord guild.")
|
||||
set(Launcher_DISCORD_URL "https://prismlauncher.org/discord" CACHE STRING "URL for the Discord guild.")
|
||||
|
||||
# Subreddit URL
|
||||
set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PrismLauncher/" CACHE STRING "URL for the subreddit.")
|
||||
set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL for the subreddit.")
|
||||
|
||||
# Builds
|
||||
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
|
||||
|
@ -38,15 +38,15 @@ Feel free to create a GitHub issue if you find a bug or want to suggest a new fe
|
||||
|
||||
- **Our Discord server:**
|
||||
|
||||
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://discord.gg/prismlauncher)
|
||||
[![Prism Launcher Discord server](https://discordapp.com/api/guilds/1031648380885147709/widget.png?style=banner3)](https://prismlauncher.org/discord)
|
||||
|
||||
- **Our Matrix space:**
|
||||
|
||||
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://matrix.to/#/#prismlauncher:matrix.org)
|
||||
[![PrismLauncher Space](https://img.shields.io/matrix/prismlauncher:matrix.org?style=for-the-badge&label=Matrix%20Space&logo=matrix&color=purple)](https://prismlauncher.org/matrix)
|
||||
|
||||
- **Our Subreddit:**
|
||||
|
||||
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://www.reddit.com/r/PrismLauncher/)
|
||||
[![r/PrismLauncher](https://img.shields.io/reddit/subreddit-subscribers/prismlauncher?style=for-the-badge&logo=reddit)](https://prismlauncher.org/reddit)
|
||||
|
||||
## Translations
|
||||
|
||||
|
@ -82,6 +82,7 @@ Config::Config()
|
||||
{
|
||||
GIT_REFSPEC = "refs/heads/stable";
|
||||
GIT_TAG = versionString();
|
||||
GIT_COMMIT = "";
|
||||
}
|
||||
|
||||
if (GIT_REFSPEC.startsWith("refs/heads/"))
|
||||
|
@ -1,8 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -36,6 +37,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
/**
|
||||
* \brief The Config class holds all the build-time information passed from the build system.
|
||||
@ -160,6 +162,7 @@ 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 FLAME_BASE_URL = "https://api.curseforge.com/v1";
|
||||
|
||||
|
15
default.nix
15
default.nix
@ -1 +1,14 @@
|
||||
(import nix/flake-compat.nix).defaultNix
|
||||
(
|
||||
import
|
||||
(
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
in
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||
}
|
||||
)
|
||||
{src = ./.;}
|
||||
)
|
||||
.defaultNix
|
||||
|
80
flake.lock
generated
80
flake.lock
generated
@ -16,29 +16,34 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"lastModified": 1688254665,
|
||||
"narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "267149c58a14d15f7f81b4d737308421de9d7152",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1676283394,
|
||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -86,11 +91,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678693419,
|
||||
"narHash": "sha256-bbSv5yqZAW6dz+3f3f3pOUZbxpPN+3OgCljgn7P+nnQ=",
|
||||
"lastModified": 1688221086,
|
||||
"narHash": "sha256-cdW6qUL71cNWhHCpMPOJjlw0wzSRP0pVlRn2vqX/VVg=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8e3fad82be64c06fbfb9fd43993aec9ef4623936",
|
||||
"rev": "cd99c2b3c9f160cd004318e0697f90bbd5960825",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -100,40 +105,44 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1673800717,
|
||||
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
|
||||
"dir": "lib",
|
||||
"lastModified": 1688049487,
|
||||
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
|
||||
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.11",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
"flake-compat": [
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": "flake-utils",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
"nixpkgs-stable": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678376203,
|
||||
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
|
||||
"lastModified": 1688386108,
|
||||
"narHash": "sha256-Vffto9QaVonzYAcPlAzd0soqWYpPpKk60dfNLSIXcFA=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
|
||||
"rev": "42587d3414d1747999a5f71e92a83cf6547b62da",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -145,11 +154,26 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-utils": "flake-utils",
|
||||
"flake-parts": "flake-parts",
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
78
flake.nix
78
flake.nix
@ -3,11 +3,12 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
pre-commit-hooks = {
|
||||
url = "github:cachix/pre-commit-hooks.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
inputs.nixpkgs-stable.follows = "nixpkgs";
|
||||
inputs.flake-compat.follows = "flake-compat";
|
||||
};
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
@ -19,73 +20,8 @@
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
pre-commit-hooks,
|
||||
libnbtplusplus,
|
||||
...
|
||||
}: let
|
||||
# User-friendly version number.
|
||||
version = builtins.substring 0 8 self.lastModifiedDate;
|
||||
|
||||
# Supported systems (qtbase is currently broken for "aarch64-darwin")
|
||||
supportedSystems = with flake-utils.lib.system; [
|
||||
x86_64-linux
|
||||
x86_64-darwin
|
||||
aarch64-linux
|
||||
];
|
||||
|
||||
packagesFn = pkgs: {
|
||||
prismlauncher-qt5 = pkgs.libsForQt5.callPackage ./nix {
|
||||
inherit version self libnbtplusplus;
|
||||
};
|
||||
prismlauncher = pkgs.qt6Packages.callPackage ./nix {
|
||||
inherit version self libnbtplusplus;
|
||||
};
|
||||
};
|
||||
in
|
||||
flake-utils.lib.eachSystem supportedSystems (system: let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in {
|
||||
checks = {
|
||||
pre-commit-check = pre-commit-hooks.lib.${system}.run {
|
||||
src = ./.;
|
||||
hooks = {
|
||||
markdownlint.enable = true;
|
||||
|
||||
alejandra.enable = true;
|
||||
deadnix.enable = true;
|
||||
|
||||
clang-format = {
|
||||
enable =
|
||||
false; # As most of the codebase is **not** formatted, we don't want clang-format yet
|
||||
types_or = ["c" "c++"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
packages = let
|
||||
packages = packagesFn pkgs;
|
||||
in
|
||||
packages // {default = packages.prismlauncher;};
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
inherit (self.checks.${system}.pre-commit-check) shellHook;
|
||||
packages = with pkgs; [
|
||||
nodePackages.markdownlint-cli
|
||||
alejandra
|
||||
deadnix
|
||||
clang-tools
|
||||
];
|
||||
|
||||
inputsFrom = [self.packages.${system}.default];
|
||||
buildInputs = with pkgs; [ccache ninja];
|
||||
};
|
||||
})
|
||||
// {
|
||||
overlays.default = final: _: (packagesFn final);
|
||||
};
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake
|
||||
{inherit inputs;}
|
||||
{imports = [./nix];};
|
||||
}
|
||||
|
@ -377,33 +377,33 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
// init the logger
|
||||
{
|
||||
static const QString logBase = BuildConfig.LAUNCHER_NAME + "-%0.log";
|
||||
auto moveFile = [](const QString &oldName, const QString &newName)
|
||||
{
|
||||
static const QString baseLogFile = BuildConfig.LAUNCHER_NAME + "-%0.log";
|
||||
static const QString logBase = FS::PathCombine("logs", baseLogFile);
|
||||
auto moveFile = [](const QString& oldName, const QString& newName) {
|
||||
QFile::remove(newName);
|
||||
QFile::copy(oldName, newName);
|
||||
QFile::remove(oldName);
|
||||
};
|
||||
if (FS::ensureFolderPathExists("logs")) { // if this did not fail
|
||||
for (auto i = 0; i <= 4; i++)
|
||||
if (auto oldName = baseLogFile.arg(i);
|
||||
QFile::exists(oldName)) // do not pointlessly delete new files if the old ones are not there
|
||||
moveFile(oldName, logBase.arg(i));
|
||||
}
|
||||
|
||||
moveFile(logBase.arg(3), logBase.arg(4));
|
||||
moveFile(logBase.arg(2), logBase.arg(3));
|
||||
moveFile(logBase.arg(1), logBase.arg(2));
|
||||
moveFile(logBase.arg(0), logBase.arg(1));
|
||||
for (auto i = 4; i > 0; i--)
|
||||
moveFile(logBase.arg(i - 1), logBase.arg(i));
|
||||
|
||||
logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
|
||||
if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
|
||||
{
|
||||
showFatalErrorMessage(
|
||||
"The launcher data folder is not writable!",
|
||||
QString(
|
||||
"The launcher couldn't create a log file - the data folder is not writable.\n"
|
||||
"\n"
|
||||
"Make sure you have write permissions to the data folder.\n"
|
||||
"(%1)\n"
|
||||
"\n"
|
||||
"The launcher cannot continue until you fix this problem."
|
||||
).arg(dataPath)
|
||||
);
|
||||
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
|
||||
showFatalErrorMessage("The launcher data folder is not writable!",
|
||||
QString("The launcher couldn't create a log file - the data folder is not writable.\n"
|
||||
"\n"
|
||||
"Make sure you have write permissions to the data folder.\n"
|
||||
"(%1)\n"
|
||||
"\n"
|
||||
"The launcher cannot continue until you fix this problem.")
|
||||
.arg(dataPath));
|
||||
return;
|
||||
}
|
||||
qInstallMessageHandler(appDebugOutput);
|
||||
@ -595,7 +595,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
|
||||
// Java Settings
|
||||
m_settings->registerSetting("JavaPath", "");
|
||||
m_settings->registerSetting("JavaTimestamp", 0);
|
||||
m_settings->registerSetting("JavaSignature", "");
|
||||
m_settings->registerSetting("JavaArchitecture", "");
|
||||
m_settings->registerSetting("JavaRealArchitecture", "");
|
||||
m_settings->registerSetting("JavaVersion", "");
|
||||
@ -688,8 +688,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
||||
m_settings->reset("PastebinCustomAPIBase");
|
||||
}
|
||||
}
|
||||
// meta URL
|
||||
m_settings->registerSetting("MetaURLOverride", "");
|
||||
{
|
||||
// Meta URL
|
||||
m_settings->registerSetting("MetaURLOverride", "");
|
||||
|
||||
QUrl metaUrl(m_settings->get("MetaURLOverride").toString());
|
||||
|
||||
// get rid of invalid meta urls
|
||||
if (!metaUrl.isValid() || metaUrl.scheme() != "http" || metaUrl.scheme() != "https")
|
||||
m_settings->reset("MetaURLOverride");
|
||||
}
|
||||
|
||||
m_settings->registerSetting("CloseAfterLaunch", false);
|
||||
m_settings->registerSetting("QuitAfterGameStop", false);
|
||||
@ -1692,6 +1700,7 @@ bool Application::handleDataMigration(const QString& currentData,
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>(
|
||||
BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("logs/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
|
||||
matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
|
||||
|
@ -362,6 +362,8 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/mod/tasks/LocalWorldSaveParseTask.cpp
|
||||
minecraft/mod/tasks/LocalResourceParse.h
|
||||
minecraft/mod/tasks/LocalResourceParse.cpp
|
||||
minecraft/mod/tasks/GetModDependenciesTask.h
|
||||
minecraft/mod/tasks/GetModDependenciesTask.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
@ -375,8 +377,6 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/services/SkinDelete.cpp
|
||||
minecraft/services/SkinDelete.h
|
||||
|
||||
mojang/PackageManifest.h
|
||||
mojang/PackageManifest.cpp
|
||||
minecraft/Agent.h)
|
||||
|
||||
# the screenshots feature
|
||||
@ -514,6 +514,8 @@ set(FLAME_SOURCES
|
||||
modplatform/flame/FlameCheckUpdate.h
|
||||
modplatform/flame/FlameInstanceCreationTask.h
|
||||
modplatform/flame/FlameInstanceCreationTask.cpp
|
||||
modplatform/flame/FlamePackExportTask.h
|
||||
modplatform/flame/FlamePackExportTask.cpp
|
||||
)
|
||||
|
||||
set(MODRINTH_SOURCES
|
||||
@ -525,6 +527,8 @@ set(MODRINTH_SOURCES
|
||||
modplatform/modrinth/ModrinthCheckUpdate.h
|
||||
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
|
||||
modplatform/modrinth/ModrinthInstanceCreationTask.h
|
||||
modplatform/modrinth/ModrinthPackExportTask.cpp
|
||||
modplatform/modrinth/ModrinthPackExportTask.h
|
||||
)
|
||||
|
||||
set(PACKWIZ_SOURCES
|
||||
@ -680,6 +684,7 @@ SET(LAUNCHER_SOURCES
|
||||
VersionProxyModel.h
|
||||
VersionProxyModel.cpp
|
||||
Markdown.h
|
||||
Markdown.cpp
|
||||
|
||||
# Super secret!
|
||||
KonamiCode.h
|
||||
@ -720,6 +725,10 @@ SET(LAUNCHER_SOURCES
|
||||
# FIXME: maybe find a better home for this.
|
||||
SkinUtils.cpp
|
||||
SkinUtils.h
|
||||
FileIgnoreProxy.cpp
|
||||
FileIgnoreProxy.h
|
||||
FastFileIconProvider.cpp
|
||||
FastFileIconProvider.h
|
||||
|
||||
# GUI - setup wizard
|
||||
ui/setupwizard/SetupWizard.h
|
||||
@ -819,8 +828,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/pages/global/APIPage.h
|
||||
|
||||
# GUI - platform pages
|
||||
ui/pages/modplatform/VanillaPage.cpp
|
||||
ui/pages/modplatform/VanillaPage.h
|
||||
ui/pages/modplatform/CustomPage.cpp
|
||||
ui/pages/modplatform/CustomPage.h
|
||||
|
||||
ui/pages/modplatform/ResourcePage.cpp
|
||||
ui/pages/modplatform/ResourcePage.h
|
||||
@ -900,6 +909,8 @@ SET(LAUNCHER_SOURCES
|
||||
ui/dialogs/EditAccountDialog.h
|
||||
ui/dialogs/ExportInstanceDialog.cpp
|
||||
ui/dialogs/ExportInstanceDialog.h
|
||||
ui/dialogs/ExportPackDialog.cpp
|
||||
ui/dialogs/ExportPackDialog.h
|
||||
ui/dialogs/IconPickerDialog.cpp
|
||||
ui/dialogs/IconPickerDialog.h
|
||||
ui/dialogs/ImportResourceDialog.cpp
|
||||
@ -1024,7 +1035,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/pages/instance/ScreenshotsPage.ui
|
||||
ui/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
|
||||
ui/pages/modplatform/atlauncher/AtlPage.ui
|
||||
ui/pages/modplatform/VanillaPage.ui
|
||||
ui/pages/modplatform/CustomPage.ui
|
||||
ui/pages/modplatform/ResourcePage.ui
|
||||
ui/pages/modplatform/flame/FlamePage.ui
|
||||
ui/pages/modplatform/legacy_ftb/Page.ui
|
||||
@ -1046,6 +1057,7 @@ qt_wrap_ui(LAUNCHER_UI
|
||||
ui/dialogs/ProfileSelectDialog.ui
|
||||
ui/dialogs/SkinUploadDialog.ui
|
||||
ui/dialogs/ExportInstanceDialog.ui
|
||||
ui/dialogs/ExportPackDialog.ui
|
||||
ui/dialogs/IconPickerDialog.ui
|
||||
ui/dialogs/ImportResourceDialog.ui
|
||||
ui/dialogs/MSALoginDialog.ui
|
||||
|
47
launcher/FastFileIconProvider.cpp
Normal file
47
launcher/FastFileIconProvider.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "FastFileIconProvider.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
|
||||
QIcon FastFileIconProvider::icon(const QFileInfo& info) const
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
|
||||
bool link = info.isSymbolicLink() || info.isAlias() || info.isShortcut();
|
||||
#else
|
||||
// in versions prior to 6.4 we don't have access to isAlias
|
||||
bool link = info.isSymLink();
|
||||
#endif
|
||||
QStyle::StandardPixmap icon;
|
||||
|
||||
if (info.isDir()) {
|
||||
if (link)
|
||||
icon = QStyle::SP_DirLinkIcon;
|
||||
else
|
||||
icon = QStyle::SP_DirIcon;
|
||||
} else {
|
||||
if (link)
|
||||
icon = QStyle::SP_FileLinkIcon;
|
||||
else
|
||||
icon = QStyle::SP_FileIcon;
|
||||
}
|
||||
|
||||
return QApplication::style()->standardIcon(icon);
|
||||
}
|
26
launcher/FastFileIconProvider.h
Normal file
26
launcher/FastFileIconProvider.h
Normal file
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFileIconProvider>
|
||||
|
||||
class FastFileIconProvider : public QFileIconProvider {
|
||||
public:
|
||||
QIcon icon(const QFileInfo& info) const override;
|
||||
};
|
279
launcher/FileIgnoreProxy.cpp
Normal file
279
launcher/FileIgnoreProxy.cpp
Normal file
@ -0,0 +1,279 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "FileIgnoreProxy.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFileSystemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStack>
|
||||
#include <algorithm>
|
||||
#include "FileSystem.h"
|
||||
#include "SeparatorPrefixTree.h"
|
||||
#include "StringUtils.h"
|
||||
|
||||
FileIgnoreProxy::FileIgnoreProxy(QString root, QObject* parent) : QSortFilterProxyModel(parent), root(root) {}
|
||||
// NOTE: Sadly, we have to do sorting ourselves.
|
||||
bool FileIgnoreProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const
|
||||
{
|
||||
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||
if (!fsm) {
|
||||
return QSortFilterProxyModel::lessThan(left, right);
|
||||
}
|
||||
bool asc = sortOrder() == Qt::AscendingOrder ? true : false;
|
||||
|
||||
QFileInfo leftFileInfo = fsm->fileInfo(left);
|
||||
QFileInfo rightFileInfo = fsm->fileInfo(right);
|
||||
|
||||
if (!leftFileInfo.isDir() && rightFileInfo.isDir()) {
|
||||
return !asc;
|
||||
}
|
||||
if (leftFileInfo.isDir() && !rightFileInfo.isDir()) {
|
||||
return asc;
|
||||
}
|
||||
|
||||
// sort and proxy model breaks the original model...
|
||||
if (sortColumn() == 0) {
|
||||
return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
if (sortColumn() == 1) {
|
||||
auto leftSize = leftFileInfo.size();
|
||||
auto rightSize = rightFileInfo.size();
|
||||
if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir())) {
|
||||
return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(), Qt::CaseInsensitive) < 0 ? asc : !asc;
|
||||
}
|
||||
return leftSize < rightSize;
|
||||
}
|
||||
return QSortFilterProxyModel::lessThan(left, right);
|
||||
}
|
||||
|
||||
Qt::ItemFlags FileIgnoreProxy::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
auto sourceIndex = mapToSource(index);
|
||||
Qt::ItemFlags flags = sourceIndex.flags();
|
||||
if (index.column() == 0) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
if (sourceIndex.model()->hasChildren(sourceIndex)) {
|
||||
flags |= Qt::ItemIsAutoTristate;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
QVariant FileIgnoreProxy::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
QModelIndex sourceIndex = mapToSource(index);
|
||||
|
||||
if (index.column() == 0 && role == Qt::CheckStateRole) {
|
||||
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||
auto blockedPath = relPath(fsm->filePath(sourceIndex));
|
||||
auto cover = blocked.cover(blockedPath);
|
||||
if (!cover.isNull()) {
|
||||
return QVariant(Qt::Unchecked);
|
||||
} else if (blocked.exists(blockedPath)) {
|
||||
return QVariant(Qt::PartiallyChecked);
|
||||
} else {
|
||||
return QVariant(Qt::Checked);
|
||||
}
|
||||
}
|
||||
|
||||
return sourceIndex.data(role);
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (index.column() == 0 && role == Qt::CheckStateRole) {
|
||||
Qt::CheckState state = static_cast<Qt::CheckState>(value.toInt());
|
||||
return setFilterState(index, state);
|
||||
}
|
||||
|
||||
QModelIndex sourceIndex = mapToSource(index);
|
||||
return QSortFilterProxyModel::sourceModel()->setData(sourceIndex, value, role);
|
||||
}
|
||||
|
||||
QString FileIgnoreProxy::relPath(const QString& path) const
|
||||
{
|
||||
return QDir(root).relativeFilePath(path);
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::setFilterState(QModelIndex index, Qt::CheckState state)
|
||||
{
|
||||
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||
|
||||
if (!fsm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QModelIndex sourceIndex = mapToSource(index);
|
||||
auto blockedPath = relPath(fsm->filePath(sourceIndex));
|
||||
bool changed = false;
|
||||
if (state == Qt::Unchecked) {
|
||||
// blocking a path
|
||||
auto& node = blocked.insert(blockedPath);
|
||||
// get rid of all blocked nodes below
|
||||
node.clear();
|
||||
changed = true;
|
||||
} else if (state == Qt::Checked || state == Qt::PartiallyChecked) {
|
||||
if (!blocked.remove(blockedPath)) {
|
||||
auto cover = blocked.cover(blockedPath);
|
||||
qDebug() << "Blocked by cover" << cover;
|
||||
// uncover
|
||||
blocked.remove(cover);
|
||||
// block all contents, except for any cover
|
||||
QModelIndex rootIndex = fsm->index(FS::PathCombine(root, cover));
|
||||
QModelIndex doing = rootIndex;
|
||||
int row = 0;
|
||||
QStack<QModelIndex> todo;
|
||||
while (1) {
|
||||
auto node = fsm->index(row, 0, doing);
|
||||
if (!node.isValid()) {
|
||||
if (!todo.size()) {
|
||||
break;
|
||||
} else {
|
||||
doing = todo.pop();
|
||||
row = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto relpath = relPath(fsm->filePath(node));
|
||||
if (blockedPath.startsWith(relpath)) // cover found?
|
||||
{
|
||||
// continue processing cover later
|
||||
todo.push(node);
|
||||
} else {
|
||||
// or just block this one.
|
||||
blocked.insert(relpath);
|
||||
}
|
||||
row++;
|
||||
}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
// update the thing
|
||||
emit dataChanged(index, index, { Qt::CheckStateRole });
|
||||
// update everything above index
|
||||
QModelIndex up = index.parent();
|
||||
while (1) {
|
||||
if (!up.isValid())
|
||||
break;
|
||||
emit dataChanged(up, up, { Qt::CheckStateRole });
|
||||
up = up.parent();
|
||||
}
|
||||
// and everything below the index
|
||||
QModelIndex doing = index;
|
||||
int row = 0;
|
||||
QStack<QModelIndex> todo;
|
||||
while (1) {
|
||||
auto node = this->index(row, 0, doing);
|
||||
if (!node.isValid()) {
|
||||
if (!todo.size()) {
|
||||
break;
|
||||
} else {
|
||||
doing = todo.pop();
|
||||
row = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
emit dataChanged(node, node, { Qt::CheckStateRole });
|
||||
todo.push(node);
|
||||
row++;
|
||||
}
|
||||
// siblings and unrelated nodes are ignored
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::shouldExpand(QModelIndex index)
|
||||
{
|
||||
QModelIndex sourceIndex = mapToSource(index);
|
||||
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||
if (!fsm) {
|
||||
return false;
|
||||
}
|
||||
auto blockedPath = relPath(fsm->filePath(sourceIndex));
|
||||
auto found = blocked.find(blockedPath);
|
||||
if (found) {
|
||||
return !found->leaf();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileIgnoreProxy::setBlockedPaths(QStringList paths)
|
||||
{
|
||||
beginResetModel();
|
||||
blocked.clear();
|
||||
blocked.insert(paths);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent)
|
||||
|
||||
// adjust the columns you want to filter out here
|
||||
// return false for those that will be hidden
|
||||
if (source_column == 2 || source_column == 3)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QFileSystemModel* fsm = qobject_cast<QFileSystemModel*>(sourceModel());
|
||||
|
||||
auto fileInfo = fsm->fileInfo(index);
|
||||
return !ignoreFile(fileInfo);
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
|
||||
{
|
||||
auto fileName = fileInfo.fileName();
|
||||
auto path = relPath(fileInfo.absoluteFilePath());
|
||||
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
|
||||
m_ignoreFilePaths.covers(path);
|
||||
}
|
||||
|
||||
bool FileIgnoreProxy::filterFile(const QString& fileName) const
|
||||
{
|
||||
return blocked.covers(fileName) || ignoreFile(QFileInfo(QDir(root), fileName));
|
||||
}
|
85
launcher/FileIgnoreProxy.h
Normal file
85
launcher/FileIgnoreProxy.h
Normal file
@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* This file incorporates work covered by the following copyright and
|
||||
* permission notice:
|
||||
*
|
||||
* Copyright 2013-2021 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include "SeparatorPrefixTree.h"
|
||||
|
||||
class FileIgnoreProxy : public QSortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FileIgnoreProxy(QString root, QObject* parent);
|
||||
// NOTE: Sadly, we have to do sorting ourselves.
|
||||
bool lessThan(const QModelIndex& left, const QModelIndex& right) const;
|
||||
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
|
||||
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
|
||||
|
||||
QString relPath(const QString& path) const;
|
||||
|
||||
bool setFilterState(QModelIndex index, Qt::CheckState state);
|
||||
|
||||
bool shouldExpand(QModelIndex index);
|
||||
|
||||
void setBlockedPaths(QStringList paths);
|
||||
|
||||
inline const SeparatorPrefixTree<'/'>& blockedPaths() const { return blocked; }
|
||||
inline SeparatorPrefixTree<'/'>& blockedPaths() { return blocked; }
|
||||
|
||||
// list of file names that need to be removed completely from model
|
||||
inline QStringList& ignoreFilesWithName() { return m_ignoreFiles; }
|
||||
// list of relative paths that need to be removed completely from model
|
||||
inline SeparatorPrefixTree<'/'>& ignoreFilesWithPath() { return m_ignoreFilePaths; }
|
||||
|
||||
bool filterFile(const QString& fileName) const;
|
||||
|
||||
protected:
|
||||
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
|
||||
|
||||
bool ignoreFile(QFileInfo file) const;
|
||||
|
||||
private:
|
||||
const QString root;
|
||||
SeparatorPrefixTree<'/'> blocked;
|
||||
QStringList m_ignoreFiles;
|
||||
SeparatorPrefixTree<'/'> m_ignoreFilePaths;
|
||||
};
|
@ -36,6 +36,7 @@
|
||||
*/
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QPair>
|
||||
|
||||
#include "BuildConfig.h"
|
||||
|
||||
@ -102,7 +103,7 @@ namespace fs = ghc::filesystem;
|
||||
#include <linux/fs.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include <sys/attr.h>
|
||||
#include <sys/clonefile.h>
|
||||
#elif defined(Q_OS_WIN)
|
||||
@ -246,6 +247,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
m_copied = 0; // reset counter
|
||||
m_failedPaths.clear();
|
||||
|
||||
// NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
@ -277,6 +279,9 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
||||
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
m_failedPaths.append(dst_path);
|
||||
emit copyFailed(relative_dst_path);
|
||||
return;
|
||||
}
|
||||
m_copied++;
|
||||
emit fileCopied(relative_dst_path);
|
||||
@ -372,7 +377,7 @@ void create_link::make_link_list(const QString& offset)
|
||||
auto src_path = source_it.next();
|
||||
auto relative_path = src_dir.relativeFilePath(src_path);
|
||||
|
||||
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth){
|
||||
if (m_max_depth >= 0 && pathDepth(relative_path) > m_max_depth) {
|
||||
relative_path = pathTruncate(relative_path, m_max_depth);
|
||||
src_path = src_dir.filePath(relative_path);
|
||||
if (linkedPaths.contains(src_path)) {
|
||||
@ -663,7 +668,7 @@ QString pathTruncate(const QString& path, int depth)
|
||||
|
||||
QString trunc = QFileInfo(path).path();
|
||||
|
||||
if (pathDepth(trunc) > depth ) {
|
||||
if (pathDepth(trunc) > depth) {
|
||||
return pathTruncate(trunc, depth);
|
||||
}
|
||||
|
||||
@ -769,10 +774,47 @@ QString getDesktopDir()
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortcut(QString destination, QString target, QStringList args, QString name, QString icon)
|
||||
{
|
||||
if (destination.isEmpty()) {
|
||||
destination = PathCombine(getDesktopDir(), RemoveInvalidFilenameChars(name));
|
||||
}
|
||||
#if defined(Q_OS_MACOS)
|
||||
destination += ".command";
|
||||
// Create the Application
|
||||
QDir applicationDirectory = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/" + BuildConfig.LAUNCHER_NAME + " Instances/";
|
||||
|
||||
QFile f(destination);
|
||||
if (!applicationDirectory.mkpath(".")) {
|
||||
qWarning() << "Couldn't create application directory";
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir application = applicationDirectory.path() + "/" + name + ".app/";
|
||||
|
||||
if (application.exists()) {
|
||||
qWarning() << "Application already exists!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!application.mkpath(".")) {
|
||||
qWarning() << "Couldn't create application";
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir content = application.path() + "/Contents/";
|
||||
QDir resources = content.path() + "/Resources/";
|
||||
QDir binaryDir = content.path() + "/MacOS/";
|
||||
QFile info = content.path() + "/Info.plist";
|
||||
|
||||
if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
|
||||
qWarning() << "Couldn't create directories within application";
|
||||
return false;
|
||||
}
|
||||
info.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
|
||||
QFile(icon).rename(resources.path() + "/Icon.icns");
|
||||
|
||||
// Create the Command file
|
||||
QString exec = binaryDir.path() + "/Run.command";
|
||||
|
||||
QFile f(exec);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
@ -789,8 +831,32 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther);
|
||||
|
||||
// Generate the Info.plist
|
||||
QTextStream infoStream(&info);
|
||||
infoStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \n"
|
||||
"<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
|
||||
"\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
|
||||
"<plist version=\"1.0\">\n"
|
||||
"<dict>\n"
|
||||
" <key>CFBundleExecutable</key>\n"
|
||||
" <string>Run.command</string>\n" // The path to the executable
|
||||
" <key>CFBundleIconFile</key>\n"
|
||||
" <string>Icon.icns</string>\n"
|
||||
" <key>CFBundleName</key>\n"
|
||||
" <string>" << name << "</string>\n" // Name of the application
|
||||
" <key>CFBundlePackageType</key>\n"
|
||||
" <string>APPL</string>\n"
|
||||
" <key>CFBundleShortVersionString</key>\n"
|
||||
" <string>1.0</string>\n"
|
||||
" <key>CFBundleVersion</key>\n"
|
||||
" <string>1.0</string>\n"
|
||||
"</dict>\n"
|
||||
"</plist>";
|
||||
|
||||
return true;
|
||||
#elif defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
if (!destination.endsWith(".desktop")) // in case of isFlatpak destination is already populated
|
||||
destination += ".desktop";
|
||||
QFile f(destination);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
@ -974,7 +1040,7 @@ FilesystemType getFilesystemType(const QString& name)
|
||||
{
|
||||
for (auto iter = s_filesystem_type_names.constBegin(); iter != s_filesystem_type_names.constEnd(); ++iter) {
|
||||
auto fs_names = iter.value();
|
||||
if(fs_names.contains(name.toUpper()))
|
||||
if (fs_names.contains(name.toUpper()))
|
||||
return iter.key();
|
||||
}
|
||||
return FilesystemType::UNKNOWN;
|
||||
@ -1072,6 +1138,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
||||
}
|
||||
|
||||
m_cloned = 0; // reset counter
|
||||
m_failedClones.clear();
|
||||
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
@ -1092,6 +1159,9 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
||||
qDebug() << "Failed to clone files: error" << err.value() << "message" << QString::fromStdString(err.message());
|
||||
qDebug() << "Source file:" << src_path;
|
||||
qDebug() << "Destination file:" << dst_path;
|
||||
m_failedClones.append(qMakePair(src_path, dst_path));
|
||||
emit cloneFailed(src_path, dst_path);
|
||||
return;
|
||||
}
|
||||
m_cloned++;
|
||||
emit fileCloned(src_path, dst_path);
|
||||
@ -1151,7 +1221,7 @@ bool clone_file(const QString& src, const QString& dst, std::error_code& ec)
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
|
||||
if (!macos_bsd_clonefile(src_path, dst_path, ec)) {
|
||||
qDebug() << "failed macos_bsd_clonefile:";
|
||||
@ -1380,7 +1450,7 @@ bool linux_ficlone(const std::string& src_path, const std::string& dst_path, std
|
||||
return true;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MACOS) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#elif defined(Q_OS_MACOS)
|
||||
|
||||
bool macos_bsd_clonefile(const std::string& src_path, const std::string& dst_path, std::error_code& ec)
|
||||
{
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include <system_error>
|
||||
|
||||
#include <QDir>
|
||||
#include <QPair>
|
||||
#include <QFlags>
|
||||
#include <QLocalServer>
|
||||
#include <QObject>
|
||||
@ -112,9 +113,12 @@ class copy : public QObject {
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCopied() { return m_copied; }
|
||||
int totalFailed() { return m_failedPaths.length(); }
|
||||
QStringList failed() { return m_failedPaths; }
|
||||
|
||||
signals:
|
||||
void fileCopied(const QString& relativeName);
|
||||
void copyFailed(const QString& relativeName);
|
||||
// TODO: maybe add a "shouldCopy" signal in the future?
|
||||
|
||||
private:
|
||||
@ -127,6 +131,7 @@ class copy : public QObject {
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_copied;
|
||||
QStringList m_failedPaths;
|
||||
};
|
||||
|
||||
struct LinkPair {
|
||||
@ -471,6 +476,9 @@ class clone : public QObject {
|
||||
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
|
||||
|
||||
int totalCloned() { return m_cloned; }
|
||||
int totalFailed() { return m_failedClones.length(); }
|
||||
|
||||
QList<QPair<QString, QString>> failed() { return m_failedClones; }
|
||||
|
||||
signals:
|
||||
void fileCloned(const QString& src, const QString& dst);
|
||||
@ -485,6 +493,7 @@ class clone : public QObject {
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
int m_cloned;
|
||||
QList<QPair<QString, QString>> m_failedClones;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,16 @@ void InstanceCopyTask::executeTask()
|
||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||
|
||||
auto copySaves = [&]() {
|
||||
FS::copy savesCopy(FS::PathCombine(m_origInstance->instanceRoot(), "saves"), FS::PathCombine(m_stagingPath, "saves"));
|
||||
QFileInfo mcDir(FS::PathCombine(m_stagingPath, "minecraft"));
|
||||
QFileInfo dotMCDir(FS::PathCombine(m_stagingPath, ".minecraft"));
|
||||
|
||||
QString staging_mc_dir;
|
||||
if (mcDir.exists() && !dotMCDir.exists())
|
||||
staging_mc_dir = mcDir.filePath();
|
||||
else
|
||||
staging_mc_dir = dotMCDir.filePath();
|
||||
|
||||
FS::copy savesCopy(FS::PathCombine(m_origInstance->gameRoot(), "saves"), FS::PathCombine(staging_mc_dir, "saves"));
|
||||
savesCopy.followSymlinks(true);
|
||||
|
||||
return savesCopy();
|
||||
@ -123,6 +132,7 @@ void InstanceCopyTask::copyFinished()
|
||||
emitFailed(tr("Instance folder copy failed."));
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: shouldn't this be able to report errors?
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
|
||||
@ -134,6 +144,24 @@ void InstanceCopyTask::copyFinished()
|
||||
}
|
||||
if (m_useLinks)
|
||||
inst->addLinkedInstanceId(m_origInstance->id());
|
||||
if (m_useLinks) {
|
||||
auto allowed_symlinks_file = QFileInfo(FS::PathCombine(inst->gameRoot(), "allowed_symlinks.txt"));
|
||||
|
||||
QByteArray allowed_symlinks;
|
||||
if (allowed_symlinks_file.exists()) {
|
||||
allowed_symlinks.append(FS::read(allowed_symlinks_file.filePath()));
|
||||
if (allowed_symlinks.right(1) != "\n")
|
||||
allowed_symlinks.append("\n"); // we want to be on a new line
|
||||
}
|
||||
allowed_symlinks.append(m_origInstance->gameRoot().toUtf8());
|
||||
allowed_symlinks.append("\n");
|
||||
if (allowed_symlinks_file.isSymLink())
|
||||
FS::deletePath(allowed_symlinks_file
|
||||
.filePath()); // we dont want to modify the original. also make sure the resulting file is not itself a link.
|
||||
|
||||
FS::write(allowed_symlinks_file.filePath(), allowed_symlinks);
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ class InstanceCreationTask : public InstanceTask {
|
||||
QString getError() const { return m_error_message; }
|
||||
|
||||
protected:
|
||||
void setError(QString message) { m_error_message = message; };
|
||||
void setError(const QString& message) { m_error_message = message; };
|
||||
|
||||
protected:
|
||||
bool m_abort = false;
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "MMCZip.h"
|
||||
#include "NullInstance.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "icons/IconList.h"
|
||||
#include "icons/IconUtils.h"
|
||||
|
||||
@ -265,7 +266,7 @@ void InstanceImportTask::extractFinished()
|
||||
|
||||
void InstanceImportTask::processFlame()
|
||||
{
|
||||
FlameCreationTask* inst_creation_task = nullptr;
|
||||
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
||||
if (!m_extra_info.isEmpty()) {
|
||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||
@ -280,10 +281,10 @@ void InstanceImportTask::processFlame()
|
||||
if (original_instance_id_it != m_extra_info.constEnd())
|
||||
original_instance_id = original_instance_id_it.value();
|
||||
|
||||
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
inst_creation_task = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, pack_id, pack_version_id, original_instance_id);
|
||||
} else {
|
||||
// FIXME: Find a way to get IDs in directly imported ZIPs
|
||||
inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent, {}, {});
|
||||
inst_creation_task = makeShared<FlameCreationTask>(m_stagingPath, m_globalSettings, m_parent, QString(), QString());
|
||||
}
|
||||
|
||||
inst_creation_task->setName(*this);
|
||||
@ -291,20 +292,19 @@ void InstanceImportTask::processFlame()
|
||||
inst_creation_task->setGroup(m_instGroup);
|
||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
||||
|
||||
connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] {
|
||||
connect(inst_creation_task.get(), &Task::succeeded, this, [this, inst_creation_task] {
|
||||
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
||||
emitSucceeded();
|
||||
});
|
||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
||||
connect(inst_creation_task, &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
||||
connect(inst_creation_task, &Task::details, this, &InstanceImportTask::setDetails);
|
||||
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
||||
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
||||
connect(inst_creation_task.get(), &Task::status, this, &InstanceImportTask::setStatus);
|
||||
connect(inst_creation_task.get(), &Task::details, this, &InstanceImportTask::setDetails);
|
||||
|
||||
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
||||
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
||||
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||
connect(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
|
||||
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||
|
||||
inst_creation_task->start();
|
||||
}
|
||||
|
@ -45,7 +45,10 @@ QString InstanceName::name() const
|
||||
{
|
||||
if (!m_modified_name.isEmpty())
|
||||
return modifiedName();
|
||||
return QString("%1 %2").arg(m_original_name, m_original_version);
|
||||
if (!m_original_version.isEmpty())
|
||||
return QString("%1 %2").arg(m_original_name, m_original_version);
|
||||
|
||||
return m_original_name;
|
||||
}
|
||||
|
||||
QString InstanceName::originalName() const
|
||||
|
@ -122,8 +122,7 @@ void JavaCommon::TestCheck::run()
|
||||
return;
|
||||
}
|
||||
checker.reset(new JavaChecker());
|
||||
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
|
||||
SLOT(checkFinished(JavaCheckResult)));
|
||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
||||
checker->m_path = m_path;
|
||||
checker->performCheck();
|
||||
}
|
||||
@ -137,8 +136,7 @@ void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
|
||||
return;
|
||||
}
|
||||
checker.reset(new JavaChecker());
|
||||
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
|
||||
SLOT(checkFinishedWithArgs(JavaCheckResult)));
|
||||
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
||||
checker->m_path = m_path;
|
||||
checker->m_args = m_args;
|
||||
checker->m_minMem = m_minMem;
|
||||
|
@ -187,8 +187,8 @@ void LaunchController::login() {
|
||||
switch(m_accountToUse->accountState()) {
|
||||
case AccountState::Offline: {
|
||||
m_session->wants_online = false;
|
||||
// NOTE: fallthrough is intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case AccountState::Online: {
|
||||
if(!m_session->wants_online) {
|
||||
// we ask the user for a player name
|
||||
@ -267,8 +267,8 @@ void LaunchController::login() {
|
||||
// This means some sort of soft error that we can fix with a refresh ... so let's refresh.
|
||||
case AccountState::Unchecked: {
|
||||
m_accountToUse->refresh();
|
||||
// NOTE: fallthrough intentional
|
||||
}
|
||||
/* fallthrough */
|
||||
case AccountState::Working: {
|
||||
// refresh is in progress, we need to wait for it to finish to proceed.
|
||||
ProgressDialog progDialog(m_parentWidget);
|
||||
|
@ -44,12 +44,8 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
|
||||
// QProcess has a strange interface... let's map a lot of those into a few.
|
||||
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
||||
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
|
||||
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(this, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
|
||||
#else
|
||||
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
|
||||
#endif
|
||||
connect(this, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &LoggedProcess::on_exit);
|
||||
connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error);
|
||||
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QPixmapCache>
|
||||
#include <QThread>
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
|
||||
#define GET_TYPE() \
|
||||
Qt::ConnectionType type; \
|
||||
@ -60,6 +62,8 @@ class PixmapCache final : public QObject {
|
||||
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:
|
||||
@ -90,6 +94,43 @@ class PixmapCache final : public QObject {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that a cache miss occurred because of a eviction if too many of these occur too fast the cache size is increased
|
||||
* @return if the cache size was increased
|
||||
*/
|
||||
bool _markCacheMissByEviciton()
|
||||
{
|
||||
auto now = QTime::currentTime();
|
||||
if (!m_last_cache_miss_by_eviciton.isNull()) {
|
||||
auto diff = m_last_cache_miss_by_eviciton.msecsTo(now);
|
||||
if (diff < 1000) { // less than a second ago
|
||||
++m_consecutive_fast_evicitons;
|
||||
} else {
|
||||
m_consecutive_fast_evicitons = 0;
|
||||
}
|
||||
}
|
||||
m_last_cache_miss_by_eviciton = now;
|
||||
if (m_consecutive_fast_evicitons >= m_consecutive_fast_evicitons_threshold) {
|
||||
// double the cache size
|
||||
auto newSize = _cacheLimit() * 2;
|
||||
qDebug() << m_consecutive_fast_evicitons << "pixmap cache misses by eviction happened too fast, doubling cache size to"
|
||||
<< newSize;
|
||||
_setCacheLimit(newSize);
|
||||
m_consecutive_fast_evicitons = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _setFastEvictionThreshold(int threshold)
|
||||
{
|
||||
m_consecutive_fast_evicitons_threshold = threshold;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static PixmapCache* s_instance;
|
||||
QTime m_last_cache_miss_by_eviciton;
|
||||
int m_consecutive_fast_evicitons = 0;
|
||||
int m_consecutive_fast_evicitons_threshold = 15;
|
||||
};
|
||||
|
31
launcher/Markdown.cpp
Normal file
31
launcher/Markdown.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2023 Joshua Goins <josh@redstrate.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Markdown.h"
|
||||
|
||||
QString markdownToHTML(const QString& markdown)
|
||||
{
|
||||
const QByteArray markdownData = markdown.toUtf8();
|
||||
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
|
||||
|
||||
QString htmlStr(buffer);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return htmlStr;
|
||||
}
|
@ -21,14 +21,4 @@
|
||||
#include <QString>
|
||||
#include <cmark.h>
|
||||
|
||||
static QString markdownToHTML(const QString& markdown)
|
||||
{
|
||||
const QByteArray markdownData = markdown.toUtf8();
|
||||
char* buffer = cmark_markdown_to_html(markdownData.constData(), markdownData.length(), CMARK_OPT_NOBREAKS | CMARK_OPT_UNSAFE);
|
||||
|
||||
QString htmlStr(buffer);
|
||||
|
||||
free(buffer);
|
||||
|
||||
return htmlStr;
|
||||
}
|
||||
QString markdownToHTML(const QString& markdown);
|
@ -1,21 +1,21 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
@ -24,14 +24,15 @@
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/ResourceFolderModel.h"
|
||||
|
||||
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
|
||||
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||
ModPlatform::IndexedVersion version,
|
||||
const std::shared_ptr<ResourceFolderModel> packs,
|
||||
bool is_indexed)
|
||||
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
|
||||
bool is_indexed,
|
||||
QString custom_target_folder)
|
||||
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
|
||||
{
|
||||
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
|
||||
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), m_pack, m_pack_version));
|
||||
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version));
|
||||
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
|
||||
|
||||
addTask(m_update_task);
|
||||
@ -40,13 +41,13 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
|
||||
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
|
||||
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
|
||||
|
||||
QDir dir { m_pack_model->dir() };
|
||||
QDir dir{ m_pack_model->dir() };
|
||||
{
|
||||
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
|
||||
// or adquiring a reference to the base instance.
|
||||
if (!m_pack_version.custom_target_folder.isEmpty()) {
|
||||
if (!m_custom_target_folder.isEmpty()) {
|
||||
dir.cdUp();
|
||||
dir.cd(m_pack_version.custom_target_folder);
|
||||
dir.cd(m_custom_target_folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,44 +1,53 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class ResourceFolderModel;
|
||||
|
||||
class ResourceDownloadTask : public SequentialTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
|
||||
public:
|
||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||
ModPlatform::IndexedVersion version,
|
||||
const std::shared_ptr<ResourceFolderModel> packs,
|
||||
bool is_indexed = true,
|
||||
QString custom_target_folder = {});
|
||||
const QString& getFilename() const { return m_pack_version.fileName; }
|
||||
const QString& getCustomPath() const { return m_pack_version.custom_target_folder; }
|
||||
const QString& getCustomPath() const { return m_custom_target_folder; }
|
||||
const QVariant& getVersionID() const { return m_pack_version.fileId; }
|
||||
const ModPlatform::IndexedVersion& getVersion() const { return m_pack_version; }
|
||||
const ModPlatform::ResourceProvider& getProvider() const { return m_pack->provider; }
|
||||
const QString& getName() const { return m_pack->name; }
|
||||
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }
|
||||
|
||||
private:
|
||||
ModPlatform::IndexedPack m_pack;
|
||||
private:
|
||||
ModPlatform::IndexedPack::Ptr m_pack;
|
||||
ModPlatform::IndexedVersion m_pack_version;
|
||||
const std::shared_ptr<ResourceFolderModel> m_pack_model;
|
||||
QString m_custom_target_folder;
|
||||
|
||||
NetJob::Ptr m_filesNetJob;
|
||||
LocalModUpdateTask::Ptr m_update_task;
|
||||
@ -47,11 +56,8 @@ private:
|
||||
void downloadFailed(QString reason);
|
||||
void downloadSucceeded();
|
||||
|
||||
std::tuple<QString, QString> to_delete {"", ""};
|
||||
std::tuple<QString, QString> to_delete{ "", "" };
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void hasOldResource(QString name, QString filename);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -54,9 +55,14 @@ public:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
const auto &filters = m_parent->filters();
|
||||
const QString &search = m_parent->search();
|
||||
const QModelIndex idx = sourceModel()->index(source_row, 0, source_parent);
|
||||
|
||||
if (!search.isEmpty() && !sourceModel()->data(idx, BaseVersionList::VersionRole).toString().contains(search, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
for (auto it = filters.begin(); it != filters.end(); ++it)
|
||||
{
|
||||
auto idx = sourceModel()->index(source_row, 0, source_parent);
|
||||
auto data = sourceModel()->data(idx, it.key());
|
||||
auto match = data.toString();
|
||||
if(!it.value()->accepts(match))
|
||||
@ -187,35 +193,21 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
switch(column)
|
||||
if(column == Name && hasRecommended)
|
||||
{
|
||||
case Name:
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
if(hasRecommended)
|
||||
return tr("Recommended");
|
||||
} else if(hasLatest) {
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
return tr("Recommended");
|
||||
}
|
||||
else if(hasLatest)
|
||||
{
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if(value.toBool())
|
||||
{
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
else if(index.row() == 0)
|
||||
{
|
||||
return tr("Latest");
|
||||
}
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole);
|
||||
}
|
||||
} else {
|
||||
return sourceModel()->data(parentIndex, BaseVersionList::VersionIdRole);
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
@ -239,10 +231,6 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
|
||||
return APPLICATION->getThemedIcon("bug");
|
||||
}
|
||||
}
|
||||
else if(index.row() == 0)
|
||||
{
|
||||
return APPLICATION->getThemedIcon("bug");
|
||||
}
|
||||
QPixmap pixmap;
|
||||
QPixmapCache::find("placeholder", &pixmap);
|
||||
if(!pixmap)
|
||||
@ -431,6 +419,7 @@ QModelIndex VersionProxyModel::getVersion(const QString& version) const
|
||||
void VersionProxyModel::clearFilters()
|
||||
{
|
||||
m_filters.clear();
|
||||
m_search.clear();
|
||||
filterModel->filterChanged();
|
||||
}
|
||||
|
||||
@ -440,11 +429,21 @@ void VersionProxyModel::setFilter(const BaseVersionList::ModelRoles column, Filt
|
||||
filterModel->filterChanged();
|
||||
}
|
||||
|
||||
void VersionProxyModel::setSearch(const QString &search) {
|
||||
m_search = search;
|
||||
filterModel->filterChanged();
|
||||
}
|
||||
|
||||
const VersionProxyModel::FilterMap &VersionProxyModel::filters() const
|
||||
{
|
||||
return m_filters;
|
||||
}
|
||||
|
||||
const QString &VersionProxyModel::search() const
|
||||
{
|
||||
return m_search;
|
||||
}
|
||||
|
||||
void VersionProxyModel::sourceAboutToBeReset()
|
||||
{
|
||||
beginResetModel();
|
||||
|
@ -38,7 +38,9 @@ public:
|
||||
virtual void setSourceModel(QAbstractItemModel *sourceModel) override;
|
||||
|
||||
const FilterMap &filters() const;
|
||||
const QString &search() const;
|
||||
void setFilter(const BaseVersionList::ModelRoles column, Filter * filter);
|
||||
void setSearch(const QString &search);
|
||||
void clearFilters();
|
||||
QModelIndex getRecommended() const;
|
||||
QModelIndex getVersion(const QString & version) const;
|
||||
@ -59,6 +61,7 @@ private slots:
|
||||
private:
|
||||
QList<Column> m_columns;
|
||||
FilterMap m_filters;
|
||||
QString m_search;
|
||||
BaseVersionList::RoleList roles;
|
||||
VersionFilterModel * filterModel;
|
||||
bool hasRecommended = false;
|
||||
|
@ -66,9 +66,8 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
|
||||
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
is_watching = false;
|
||||
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
|
||||
|
||||
directoryChanged(path);
|
||||
|
||||
|
@ -85,17 +85,13 @@ void JavaChecker::performCheck()
|
||||
process->setProgram(m_path);
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
process->setProcessEnvironment(CleanEnviroment());
|
||||
qDebug() << "Running java checker: " + m_path + args.join(" ");;
|
||||
qDebug() << "Running java checker:" << m_path << args.join(" ");
|
||||
|
||||
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
||||
#else
|
||||
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
||||
#endif
|
||||
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
|
||||
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
|
||||
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
|
||||
connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished);
|
||||
connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
|
||||
connect(process.get(), &QProcess::readyReadStandardOutput, this, &JavaChecker::stdoutReady);
|
||||
connect(process.get(), &QProcess::readyReadStandardError, this, &JavaChecker::stderrReady);
|
||||
connect(&killTimer, &QTimer::timeout, this, &JavaChecker::timeout);
|
||||
killTimer.setSingleShot(true);
|
||||
killTimer.start(15000);
|
||||
process->start();
|
||||
@ -132,7 +128,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||
result.outLog = m_stdout;
|
||||
qDebug() << "STDOUT" << m_stdout;
|
||||
qWarning() << "STDERR" << m_stderr;
|
||||
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
|
||||
qDebug() << "Java checker finished with status" << status << "exit code" << exitcode;
|
||||
|
||||
if (status == QProcess::CrashExit || exitcode == 1)
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ void JavaCheckerJob::executeTask()
|
||||
for (auto iter : javacheckers)
|
||||
{
|
||||
javaresults.append(JavaCheckResult());
|
||||
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
|
||||
iter->performCheck();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -98,6 +99,8 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const
|
||||
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
|
||||
switch (role)
|
||||
{
|
||||
case SortRole:
|
||||
return -index.row();
|
||||
case VersionPointerRole:
|
||||
return QVariant::fromValue(m_vlist[index.row()]);
|
||||
case VersionIdRole:
|
||||
|
@ -81,15 +81,20 @@ void CheckJava::executeTask()
|
||||
}
|
||||
|
||||
QFileInfo javaInfo(realJavaPath);
|
||||
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
||||
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
|
||||
qint64 javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
||||
auto storedSignature = settings->get("JavaSignature").toString();
|
||||
auto storedArchitecture = settings->get("JavaArchitecture").toString();
|
||||
auto storedRealArchitecture = settings->get("JavaRealArchitecture").toString();
|
||||
auto storedVersion = settings->get("JavaVersion").toString();
|
||||
auto storedVendor = settings->get("JavaVendor").toString();
|
||||
m_javaUnixTime = javaUnixTime;
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(QByteArray::number(javaUnixTime));
|
||||
hash.addData(m_javaPath.toUtf8());
|
||||
m_javaSignature = hash.result().toHex();
|
||||
|
||||
// if timestamps are not the same, or something is missing, check!
|
||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0
|
||||
if (m_javaSignature != storedSignature || storedVersion.size() == 0
|
||||
|| storedArchitecture.size() == 0 || storedRealArchitecture.size() == 0
|
||||
|| storedVendor.size() == 0)
|
||||
{
|
||||
@ -140,7 +145,7 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
|
||||
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
||||
instance->settings()->set("JavaRealArchitecture", result.realPlatform);
|
||||
instance->settings()->set("JavaVendor", result.javaVendor);
|
||||
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
|
||||
instance->settings()->set("JavaSignature", m_javaSignature);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
@ -40,6 +40,6 @@ private:
|
||||
|
||||
private:
|
||||
QString m_javaPath;
|
||||
qlonglong m_javaUnixTime;
|
||||
QString m_javaSignature;
|
||||
JavaCheckerPtr m_JavaChecker;
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ void Update::executeTask()
|
||||
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
|
||||
if(m_updateTask)
|
||||
{
|
||||
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
|
||||
connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished);
|
||||
connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
|
||||
connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress);
|
||||
connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
|
||||
|
@ -45,10 +45,10 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case 0: return list->humanReadable();
|
||||
default: break;
|
||||
if (index.column() == 0) {
|
||||
return list->humanReadable();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
case UidRole: return list->uid();
|
||||
case NameRole: return list->name();
|
||||
|
@ -56,10 +56,10 @@ static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &ob
|
||||
version->setType(ensureString(obj, "type", QString()));
|
||||
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
||||
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
|
||||
RequireSet requires, conflicts;
|
||||
parseRequires(obj, &requires, "requires");
|
||||
RequireSet reqs, conflicts;
|
||||
parseRequires(obj, &reqs, "requires");
|
||||
parseRequires(obj, &conflicts, "conflicts");
|
||||
version->setRequires(requires, conflicts);
|
||||
version->setRequires(reqs, conflicts);
|
||||
return version;
|
||||
}
|
||||
|
||||
@ -176,7 +176,6 @@ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName
|
||||
{
|
||||
if(obj.contains(keyName))
|
||||
{
|
||||
QSet<QString> requires;
|
||||
auto reqArray = requireArray(obj, keyName);
|
||||
auto iter = reqArray.begin();
|
||||
while(iter != reqArray.end())
|
||||
|
@ -116,9 +116,9 @@ void Meta::Version::setTime(const qint64 time)
|
||||
emit timeChanged();
|
||||
}
|
||||
|
||||
void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
|
||||
void Meta::Version::setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts)
|
||||
{
|
||||
m_requires = requires;
|
||||
m_requires = reqs;
|
||||
m_conflicts = conflicts;
|
||||
emit requiresChanged();
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ public:
|
||||
{
|
||||
return m_time;
|
||||
}
|
||||
const Meta::RequireSet &requires() const
|
||||
const Meta::RequireSet &requiredSet() const
|
||||
{
|
||||
return m_requires;
|
||||
}
|
||||
@ -91,7 +91,7 @@ public:
|
||||
public: // for usage by format parsers only
|
||||
void setType(const QString &type);
|
||||
void setTime(const qint64 time);
|
||||
void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
|
||||
void setRequires(const Meta::RequireSet &reqs, const Meta::RequireSet &conflicts);
|
||||
void setVolatile(bool volatile_);
|
||||
void setRecommended(bool recommended);
|
||||
void setProvidesRecommendations();
|
||||
|
@ -77,7 +77,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
case ParentVersionRole:
|
||||
{
|
||||
// FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'.
|
||||
auto & reqs = version->requires();
|
||||
auto & reqs = version->requiredSet();
|
||||
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
|
||||
{
|
||||
return req.uid == "net.minecraft";
|
||||
@ -92,7 +92,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
|
||||
case UidRole: return version->uid();
|
||||
case TimeRole: return version->time();
|
||||
case RequiresRole: return QVariant::fromValue(version->requires());
|
||||
case RequiresRole: return QVariant::fromValue(version->requiredSet());
|
||||
case SortRole: return version->rawTime();
|
||||
case VersionPtrRole: return QVariant::fromValue(version);
|
||||
case RecommendedRole: return version->isRecommended();
|
||||
|
@ -451,9 +451,9 @@ void Component::updateCachedData()
|
||||
m_cachedVolatile = file->m_volatile;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedRequires, file->requires))
|
||||
if(!deepCompare(m_cachedRequires, file->m_requires))
|
||||
{
|
||||
m_cachedRequires = file->requires;
|
||||
m_cachedRequires = file->m_requires;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||
|
@ -148,10 +148,11 @@ void MinecraftInstance::loadSpecificSettings()
|
||||
m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation);
|
||||
|
||||
// special!
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaSignature"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation);
|
||||
m_settings->registerPassthrough(global_settings->getSetting("JavaVendor"), javaOrLocation);
|
||||
|
||||
// Window Size
|
||||
auto windowSetting = m_settings->registerSetting("OverrideWindow", false);
|
||||
@ -1109,79 +1110,70 @@ JavaVersion MinecraftInstance::getJavaVersion()
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
if (!m_loader_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), shared_from_this(), is_indexed));
|
||||
m_loader_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
|
||||
}
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
if (!m_core_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), shared_from_this(), is_indexed));
|
||||
m_core_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
|
||||
}
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList() const
|
||||
std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
|
||||
{
|
||||
if (!m_nil_mod_list)
|
||||
{
|
||||
if (!m_nil_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), shared_from_this(), is_indexed, false));
|
||||
m_nil_mod_list->disableInteraction(isRunning());
|
||||
connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||
m_nil_mod_list.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false));
|
||||
}
|
||||
return m_nil_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() const
|
||||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), shared_from_this()));
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
|
||||
}
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() const
|
||||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), shared_from_this()));
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
|
||||
}
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
|
||||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||
{
|
||||
if (!m_shader_pack_list)
|
||||
{
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), shared_from_this()));
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
|
||||
}
|
||||
return m_shader_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<WorldList> MinecraftInstance::worldList() const
|
||||
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||
{
|
||||
if (!m_world_list)
|
||||
{
|
||||
m_world_list.reset(new WorldList(worldDir(), shared_from_this()));
|
||||
m_world_list.reset(new WorldList(worldDir(), this));
|
||||
}
|
||||
return m_world_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const
|
||||
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel()
|
||||
{
|
||||
if (!m_game_options)
|
||||
{
|
||||
|
@ -115,14 +115,14 @@ public:
|
||||
std::shared_ptr<PackProfile> getPackProfile() const;
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModFolderModel> loaderModList() const;
|
||||
std::shared_ptr<ModFolderModel> coreModList() const;
|
||||
std::shared_ptr<ModFolderModel> nilModList() const;
|
||||
std::shared_ptr<ResourcePackFolderModel> resourcePackList() const;
|
||||
std::shared_ptr<TexturePackFolderModel> texturePackList() const;
|
||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
||||
std::shared_ptr<ModFolderModel> loaderModList();
|
||||
std::shared_ptr<ModFolderModel> coreModList();
|
||||
std::shared_ptr<ModFolderModel> nilModList();
|
||||
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
||||
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
||||
std::shared_ptr<WorldList> worldList();
|
||||
std::shared_ptr<GameOptions> gameOptionsModel();
|
||||
|
||||
////// Launch stuff //////
|
||||
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
||||
|
@ -276,7 +276,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
|
||||
if (root.contains("requires"))
|
||||
{
|
||||
Meta::parseRequires(root, &out->requires);
|
||||
Meta::parseRequires(root, &out->m_requires);
|
||||
}
|
||||
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
|
||||
if(!dependsOnMinecraftVersion.isEmpty())
|
||||
@ -284,9 +284,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
||||
Meta::Require mcReq;
|
||||
mcReq.uid = "net.minecraft";
|
||||
mcReq.equalsVersion = dependsOnMinecraftVersion;
|
||||
if (out->requires.count(mcReq) == 0)
|
||||
if (out->m_requires.count(mcReq) == 0)
|
||||
{
|
||||
out->requires.insert(mcReq);
|
||||
out->m_requires.insert(mcReq);
|
||||
}
|
||||
}
|
||||
if (root.contains("conflicts"))
|
||||
@ -392,9 +392,9 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
||||
}
|
||||
root.insert("mods", array);
|
||||
}
|
||||
if(!patch->requires.empty())
|
||||
if(!patch->m_requires.empty())
|
||||
{
|
||||
Meta::serializeRequires(root, &patch->requires, "requires");
|
||||
Meta::serializeRequires(root, &patch->m_requires, "requires");
|
||||
}
|
||||
if(!patch->conflicts.empty())
|
||||
{
|
||||
|
@ -65,7 +65,8 @@
|
||||
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{
|
||||
{"net.minecraftforge", ResourceAPI::Forge},
|
||||
{"net.fabricmc.fabric-loader", ResourceAPI::Fabric},
|
||||
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt}
|
||||
{"org.quiltmc.quilt-loader", ResourceAPI::Quilt},
|
||||
{"com.mumfrey.liteloader", ResourceAPI::LiteLoader}
|
||||
};
|
||||
|
||||
PackProfile::PackProfile(MinecraftInstance * instance)
|
||||
|
@ -138,7 +138,7 @@ public: /* data */
|
||||
* Prism Launcher: set of packages this depends on
|
||||
* NOTE: this is shared with the meta format!!!
|
||||
*/
|
||||
Meta::RequireSet requires;
|
||||
Meta::RequireSet m_requires;
|
||||
|
||||
/**
|
||||
* Prism Launcher: set of packages this conflicts with
|
||||
|
@ -45,7 +45,7 @@
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
|
||||
WorldList::WorldList(const QString &dir, std::shared_ptr<const BaseInstance> instance)
|
||||
WorldList::WorldList(const QString &dir, BaseInstance* instance)
|
||||
: QAbstractListModel(), m_instance(instance), m_dir(dir)
|
||||
{
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
@ -53,8 +53,7 @@ WorldList::WorldList(const QString &dir, std::shared_ptr<const BaseInstance> ins
|
||||
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
is_watching = false;
|
||||
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged);
|
||||
}
|
||||
|
||||
void WorldList::startWatching()
|
||||
|
@ -50,7 +50,7 @@ public:
|
||||
IconFileRole
|
||||
};
|
||||
|
||||
WorldList(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
||||
WorldList(const QString &dir, BaseInstance* instance);
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
|
||||
@ -128,7 +128,7 @@ signals:
|
||||
void changed();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<const BaseInstance> m_instance;
|
||||
BaseInstance* m_instance;
|
||||
QFileSystemWatcher *m_watcher;
|
||||
bool is_watching;
|
||||
QDir m_dir;
|
||||
|
@ -328,18 +328,21 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case MigrationColumn: {
|
||||
if(account->isMSA() || account->isOffline()) {
|
||||
return tr("N/A", "Can Migrate?");
|
||||
return tr("N/A", "Can Migrate");
|
||||
}
|
||||
if (account->canMigrate()) {
|
||||
return tr("Yes", "Can Migrate?");
|
||||
return tr("Yes", "Can Migrate");
|
||||
}
|
||||
else {
|
||||
return tr("No", "Can Migrate?");
|
||||
return tr("No", "Can Migrate");
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,11 +357,12 @@ QVariant AccountList::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(account);
|
||||
|
||||
case Qt::CheckStateRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case ProfileNameColumn:
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
if (index.column() == ProfileNameColumn) {
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
|
@ -55,12 +55,12 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
|
||||
reply_ = APPLICATION->network()->get(request_);
|
||||
status_ = Requesting;
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
#else
|
||||
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
||||
}
|
||||
|
||||
@ -70,14 +70,14 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
|
||||
status_ = Requesting;
|
||||
reply_ = APPLICATION->network()->post(request_, data_);
|
||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
#else
|
||||
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||
#else // &QNetworkReply::error SIGNAL depricated
|
||||
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||
#endif
|
||||
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
|
||||
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
||||
connect(reply_, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(onUploadProgress(qint64,qint64)));
|
||||
connect(reply_, &QNetworkReply::uploadProgress, this, &AuthRequest::onUploadProgress);
|
||||
}
|
||||
|
||||
void AuthRequest::onRequestFinished() {
|
||||
|
@ -133,8 +133,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MojangLogin(&data, password));
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
@ -144,8 +144,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new MSAInteractive(&data));
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
@ -155,8 +155,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
|
||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||
|
||||
m_currentTask.reset(new OfflineLogin(&data));
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
@ -177,8 +177,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
||||
m_currentTask.reset(new MojangRefresh(&data));
|
||||
}
|
||||
|
||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
||||
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||
emit activityChanged(true);
|
||||
return m_currentTask;
|
||||
|
@ -273,6 +273,7 @@ void Yggdrasil::processReply() {
|
||||
AccountTaskState::STATE_FAILED_GONE,
|
||||
tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account.")
|
||||
);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
changeState(
|
||||
|
@ -38,6 +38,10 @@ void XboxUserStep::perform() {
|
||||
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
// set contract-verison header (prevent err 400 bad-request?)
|
||||
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
|
||||
request.setRawHeader("x-xbl-contract-version", "1");
|
||||
|
||||
auto *requestor = new AuthRequest(this);
|
||||
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
||||
requestor->post(request, xbox_auth_data.toUtf8());
|
||||
|
@ -33,7 +33,9 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
|
||||
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
|
||||
{ 8, { Version("1.18"), Version("1.18.1") } }, { 9, { Version("1.18.2"), Version("1.18.2") } },
|
||||
{ 10, { Version("1.19"), Version("1.19.3") } },
|
||||
{ 10, { Version("1.19"), Version("1.19.3") } }, { 11, { Version("23w03a"), Version("23w05a") } },
|
||||
{ 12, { Version("1.19.4"), Version("1.19.4") } }, { 13, { Version("23w12a"), Version("23w14a") } },
|
||||
{ 14, { Version("23w16a"), Version("23w17a") } }, { 15, { Version("1.20"), Version("1.20") } },
|
||||
};
|
||||
|
||||
void DataPack::setPackFormat(int new_format_id)
|
||||
@ -72,6 +74,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -81,6 +84,7 @@ std::pair<int, bool> DataPack::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -41,9 +41,11 @@
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "MTPixmapCache.h"
|
||||
#include "MetadataHandler.h"
|
||||
#include "Version.h"
|
||||
#include "minecraft/mod/ModDetails.h"
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
|
||||
static ModPlatform::ProviderCapabilities ProviderCaps;
|
||||
|
||||
@ -89,6 +91,7 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::VERSION: {
|
||||
auto this_ver = Version(version());
|
||||
@ -97,11 +100,13 @@ std::pair<int, bool> Mod::compare(const Resource& other, SortType type) const
|
||||
return { 1, type == SortType::VERSION };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::VERSION };
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
auto compare_result = QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::PROVIDER };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
@ -121,7 +126,7 @@ bool Mod::applyFilter(QRegularExpression filter) const
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
||||
{
|
||||
if (!preserve_metadata) {
|
||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||
@ -134,7 +139,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool
|
||||
}
|
||||
}
|
||||
|
||||
return Resource::destroy();
|
||||
return Resource::destroy(attempt_trash);
|
||||
}
|
||||
|
||||
auto Mod::details() const -> const ModDetails&
|
||||
@ -164,6 +169,13 @@ auto Mod::homeurl() const -> QString
|
||||
return details().homeurl;
|
||||
}
|
||||
|
||||
auto Mod::metaurl() const -> QString
|
||||
{
|
||||
if (metadata() == nullptr)
|
||||
return homeurl();
|
||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||
}
|
||||
|
||||
auto Mod::description() const -> QString
|
||||
{
|
||||
return details().description;
|
||||
@ -201,7 +213,10 @@ void Mod::finishResolvingWithDetails(ModDetails&& details)
|
||||
m_local_details = std::move(details);
|
||||
if (metadata)
|
||||
setMetadata(std::move(metadata));
|
||||
};
|
||||
if (!iconPath().isEmpty()) {
|
||||
m_pack_image_cache_key.was_read_attempt = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto Mod::provider() const -> std::optional<QString>
|
||||
{
|
||||
@ -210,6 +225,56 @@ auto Mod::provider() const -> std::optional<QString>
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::licenses() const -> const QList<ModLicense>&
|
||||
{
|
||||
return details().licenses;
|
||||
}
|
||||
|
||||
auto Mod::issueTracker() const -> QString
|
||||
{
|
||||
return details().issue_tracker;
|
||||
}
|
||||
|
||||
void Mod::setIcon(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
Q_ASSERT(!new_image.isNull());
|
||||
|
||||
if (m_pack_image_cache_key.key.isValid())
|
||||
PixmapCache::remove(m_pack_image_cache_key.key);
|
||||
|
||||
// scale the image to avoid flooding the pixmapcache
|
||||
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
m_pack_image_cache_key.was_read_attempt = true;
|
||||
}
|
||||
|
||||
QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
|
||||
{
|
||||
QPixmap cached_image;
|
||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size, mode);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
if ((!m_pack_image_cache_key.was_ever_used && m_pack_image_cache_key.was_read_attempt) || iconPath().isEmpty())
|
||||
return {};
|
||||
|
||||
if (m_pack_image_cache_key.was_ever_used) {
|
||||
qDebug() << "Mod" << name() << "Had it's icon evicted form the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
// Image got evicted from the cache or an attempt to load it has not been made. load it and retry.
|
||||
m_pack_image_cache_key.was_read_attempt = true;
|
||||
ModUtils::loadIconFile(*this);
|
||||
return icon(size);
|
||||
}
|
||||
|
||||
bool Mod::valid() const
|
||||
{
|
||||
return !m_local_details.mod_id.isEmpty();
|
||||
|
@ -38,6 +38,10 @@
|
||||
#include <QDateTime>
|
||||
#include <QFileInfo>
|
||||
#include <QList>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
#include <QPixmap>
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include <optional>
|
||||
|
||||
@ -64,6 +68,16 @@ public:
|
||||
auto authors() const -> QStringList;
|
||||
auto status() const -> ModStatus;
|
||||
auto provider() const -> std::optional<QString>;
|
||||
auto licenses() const -> const QList<ModLicense>&;
|
||||
auto issueTracker() const -> QString;
|
||||
auto metaurl() const -> QString;
|
||||
|
||||
/** Get the intneral path to the mod's icon file*/
|
||||
QString iconPath() const { return m_local_details.icon_file; };
|
||||
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
/** Thread-safe. */
|
||||
void setIcon(QImage new_image) const;
|
||||
|
||||
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
||||
@ -79,10 +93,19 @@ public:
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
// Delete all the files of this mod
|
||||
auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool;
|
||||
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||
|
||||
void finishResolvingWithDetails(ModDetails&& details);
|
||||
|
||||
protected:
|
||||
ModDetails m_local_details;
|
||||
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
struct {
|
||||
QPixmapCache::Key key;
|
||||
bool was_ever_used = false;
|
||||
bool was_read_attempt = false;
|
||||
} mutable m_pack_image_cache_key;
|
||||
|
||||
};
|
||||
|
@ -39,6 +39,7 @@
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
@ -49,6 +50,84 @@ enum class ModStatus {
|
||||
Unknown, // Default status
|
||||
};
|
||||
|
||||
struct ModLicense {
|
||||
QString name = {};
|
||||
QString id = {};
|
||||
QString url = {};
|
||||
QString description = {};
|
||||
|
||||
ModLicense() {}
|
||||
|
||||
ModLicense(const QString license) {
|
||||
// FIXME: come up with a better license parseing.
|
||||
// handle SPDX identifiers? https://spdx.org/licenses/
|
||||
auto parts = license.split(' ');
|
||||
QStringList notNameParts = {};
|
||||
for (auto part : parts) {
|
||||
auto url = QUrl(part);
|
||||
if (part.startsWith("(") && part.endsWith(")"))
|
||||
url = QUrl(part.mid(1, part.size() - 2));
|
||||
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
this->url = url.toString();
|
||||
notNameParts.append(part);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto part : notNameParts) {
|
||||
parts.removeOne(part);
|
||||
}
|
||||
|
||||
auto licensePart = parts.join(' ');
|
||||
this->name = licensePart;
|
||||
this->description = licensePart;
|
||||
|
||||
if (parts.size() == 1) {
|
||||
this->id = parts.first();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ModLicense(const QString name, const QString id, const QString url, const QString description) {
|
||||
this->name = name;
|
||||
this->id = id;
|
||||
this->url = url;
|
||||
this->description = description;
|
||||
}
|
||||
|
||||
ModLicense(const ModLicense& other)
|
||||
: name(other.name)
|
||||
, id(other.id)
|
||||
, url(other.url)
|
||||
, description(other.description)
|
||||
{}
|
||||
|
||||
ModLicense& operator=(const ModLicense& other)
|
||||
{
|
||||
this->name = other.name;
|
||||
this->id = other.id;
|
||||
this->url = other.url;
|
||||
this->description = other.description;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ModLicense& operator=(const ModLicense&& other)
|
||||
{
|
||||
this->name = other.name;
|
||||
this->id = other.id;
|
||||
this->url = other.url;
|
||||
this->description = other.description;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool isEmpty() {
|
||||
return this->name.isEmpty() && this->id.isEmpty() && this->url.isEmpty() && this->description.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct ModDetails
|
||||
{
|
||||
/* Mod ID as defined in the ModLoader-specific metadata */
|
||||
@ -72,6 +151,15 @@ struct ModDetails
|
||||
/* List of the author's names */
|
||||
QStringList authors = {};
|
||||
|
||||
/* Issue Tracker URL */
|
||||
QString issue_tracker = {};
|
||||
|
||||
/* License */
|
||||
QList<ModLicense> licenses = {};
|
||||
|
||||
/* Path of mod logo */
|
||||
QString icon_file = {};
|
||||
|
||||
/* Installation status of the mod */
|
||||
ModStatus status = ModStatus::Unknown;
|
||||
|
||||
@ -89,6 +177,9 @@ struct ModDetails
|
||||
, homeurl(other.homeurl)
|
||||
, description(other.description)
|
||||
, authors(other.authors)
|
||||
, issue_tracker(other.issue_tracker)
|
||||
, licenses(other.licenses)
|
||||
, icon_file(other.icon_file)
|
||||
, status(other.status)
|
||||
{}
|
||||
|
||||
@ -101,6 +192,9 @@ struct ModDetails
|
||||
this->homeurl = other.homeurl;
|
||||
this->description = other.description;
|
||||
this->authors = other.authors;
|
||||
this->issue_tracker = other.issue_tracker;
|
||||
this->licenses = other.licenses;
|
||||
this->icon_file = other.icon_file;
|
||||
this->status = other.status;
|
||||
|
||||
return *this;
|
||||
@ -115,6 +209,9 @@ struct ModDetails
|
||||
this->homeurl = other.homeurl;
|
||||
this->description = other.description;
|
||||
this->authors = other.authors;
|
||||
this->issue_tracker = other.issue_tracker;
|
||||
this->licenses = other.licenses;
|
||||
this->icon_file = other.icon_file;
|
||||
this->status = other.status;
|
||||
|
||||
return *this;
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "ModFolderModel.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <qheaderview.h>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QIcon>
|
||||
@ -52,12 +53,14 @@
|
||||
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
ModFolderModel::ModFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance, bool is_indexed, bool create_dir)
|
||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
||||
{
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Version"), tr("Last Modified"), tr("Provider") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME , SortType::VERSION, SortType::DATE, SortType::PROVIDER};
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents};
|
||||
}
|
||||
|
||||
QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
@ -118,7 +121,9 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->icon({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case Qt::CheckStateRole:
|
||||
@ -142,15 +147,12 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
||||
switch (section)
|
||||
{
|
||||
case ActiveColumn:
|
||||
return QString();
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case VersionColumn:
|
||||
return tr("Version");
|
||||
case DateColumn:
|
||||
return tr("Last changed");
|
||||
case ProviderColumn:
|
||||
return tr("Provider");
|
||||
case ImageColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -197,10 +199,10 @@ Task* ModFolderModel::createParseTask(Resource& resource)
|
||||
|
||||
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
||||
{
|
||||
for(auto mod : allMods()){
|
||||
if(mod->fileinfo().fileName() == filename){
|
||||
for(auto mod : allMods()) {
|
||||
if(mod->fileinfo().fileName() == filename) {
|
||||
auto index_dir = indexDir();
|
||||
mod->destroy(index_dir, preserve_metadata);
|
||||
mod->destroy(index_dir, preserve_metadata, false);
|
||||
|
||||
update();
|
||||
|
||||
@ -213,16 +215,11 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat
|
||||
|
||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
{
|
||||
if(!m_can_interact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(indexes.isEmpty())
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i: indexes)
|
||||
{
|
||||
if(i.column() != 0) {
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
auto m = at(i.row());
|
||||
|
@ -64,6 +64,7 @@ public:
|
||||
enum Columns
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
ImageColumn,
|
||||
NameColumn,
|
||||
VersionColumn,
|
||||
DateColumn,
|
||||
@ -75,7 +76,9 @@ public:
|
||||
Enable,
|
||||
Toggle
|
||||
};
|
||||
ModFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance, bool is_indexed = false, bool create_dir = true);
|
||||
ModFolderModel(const QString &dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
|
||||
|
||||
virtual QString id() const override { return "mods"; }
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
|
@ -71,6 +71,7 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
return { 1, type == SortType::ENABLED };
|
||||
if (!enabled() && other.enabled())
|
||||
return { -1, type == SortType::ENABLED };
|
||||
break;
|
||||
case SortType::NAME: {
|
||||
QString this_name{ name() };
|
||||
QString other_name{ other.name() };
|
||||
@ -81,12 +82,14 @@ std::pair<int, bool> Resource::compare(const Resource& other, SortType type) con
|
||||
auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return { compare_result, type == SortType::NAME };
|
||||
break;
|
||||
}
|
||||
case SortType::DATE:
|
||||
if (dateTimeChanged() > other.dateTimeChanged())
|
||||
return { 1, type == SortType::DATE };
|
||||
if (dateTimeChanged() < other.dateTimeChanged())
|
||||
return { -1, type == SortType::DATE };
|
||||
break;
|
||||
}
|
||||
|
||||
return { 0, false };
|
||||
@ -145,14 +148,10 @@ bool Resource::enable(EnableAction action)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resource::destroy()
|
||||
bool Resource::destroy(bool attemptTrash)
|
||||
{
|
||||
m_type = ResourceType::UNKNOWN;
|
||||
|
||||
if (FS::trash(m_file_info.filePath()))
|
||||
return true;
|
||||
|
||||
return FS::deletePath(m_file_info.filePath());
|
||||
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
||||
}
|
||||
|
||||
bool Resource::isSymLinkUnder(const QString& instPath) const
|
||||
|
@ -92,7 +92,7 @@ class Resource : public QObject {
|
||||
}
|
||||
|
||||
// Delete all files of this resource.
|
||||
bool destroy();
|
||||
bool destroy(bool attemptTrash = true);
|
||||
|
||||
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
#include "ResourceFolderModel.h"
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QIcon>
|
||||
#include <QMenu>
|
||||
#include <QMimeData>
|
||||
#include <QStyle>
|
||||
#include <QThreadPool>
|
||||
@ -12,11 +14,14 @@
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "QVariantUtils.h"
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
|
||||
#include "settings/Setting.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
ResourceFolderModel::ResourceFolderModel(QDir dir, std::shared_ptr<const BaseInstance> instance, QObject* parent, bool create_dir)
|
||||
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
||||
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
|
||||
{
|
||||
if (create_dir) {
|
||||
@ -74,10 +79,6 @@ bool ResourceFolderModel::stopWatching(const QStringList paths)
|
||||
|
||||
bool ResourceFolderModel::installResource(QString original_path)
|
||||
{
|
||||
if (!m_can_interact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
|
||||
original_path = FS::NormalizePath(original_path);
|
||||
QFileInfo file_info(original_path);
|
||||
@ -156,7 +157,7 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
{
|
||||
for (auto& resource : m_resources) {
|
||||
if (resource->fileinfo().fileName() == file_name) {
|
||||
auto res = resource->destroy();
|
||||
auto res = resource->destroy(false);
|
||||
|
||||
update();
|
||||
|
||||
@ -168,9 +169,6 @@ bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
|
||||
bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||
{
|
||||
if (!m_can_interact)
|
||||
return false;
|
||||
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@ -189,11 +187,8 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action)
|
||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
||||
{
|
||||
if (!m_can_interact)
|
||||
return false;
|
||||
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
@ -246,15 +241,18 @@ bool ResourceFolderModel::update()
|
||||
connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded,
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||
connect(m_current_update_task.get(), &Task::finished, this, [=] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
update();
|
||||
} else {
|
||||
emit updateFinished();
|
||||
}
|
||||
}, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
m_current_update_task.get(), &Task::finished, this,
|
||||
[=] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
update();
|
||||
} else {
|
||||
emit updateFinished();
|
||||
}
|
||||
},
|
||||
Qt::ConnectionType::QueuedConnection);
|
||||
|
||||
QThreadPool::globalInstance()->start(m_current_update_task.get());
|
||||
|
||||
@ -344,15 +342,9 @@ Qt::DropActions ResourceFolderModel::supportedDropActions() const
|
||||
Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
auto flags = defaultFlags;
|
||||
if (!m_can_interact) {
|
||||
flags &= ~Qt::ItemIsDropEnabled;
|
||||
} else {
|
||||
flags |= Qt::ItemIsDropEnabled;
|
||||
if (index.isValid()) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
}
|
||||
auto flags = defaultFlags | Qt::ItemIsDropEnabled;
|
||||
if (index.isValid())
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
return flags;
|
||||
}
|
||||
|
||||
@ -425,16 +417,17 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
if (column == NAME_COLUMN) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return 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());;
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
;
|
||||
}
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
@ -460,8 +453,20 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu
|
||||
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
|
||||
return false;
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (m_instance != nullptr && m_instance->isRunning()) {
|
||||
auto response =
|
||||
CustomMessageBox::selectable(nullptr, "Confirm toggle",
|
||||
"If you enable/disable this resource while the game is running it may crash your game.\n"
|
||||
"Are you sure you want to do this?",
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
|
||||
if (response != QMessageBox::Yes)
|
||||
return false;
|
||||
}
|
||||
return setResourceEnabled({ index }, EnableAction::TOGGLE);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -471,10 +476,10 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ACTIVE_COLUMN:
|
||||
case NAME_COLUMN:
|
||||
return tr("Name");
|
||||
case DATE_COLUMN:
|
||||
return tr("Last modified");
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@ -500,6 +505,75 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio
|
||||
return {};
|
||||
}
|
||||
|
||||
void ResourceFolderModel::setupHeaderAction(QAction* act, int column)
|
||||
{
|
||||
Q_ASSERT(act);
|
||||
|
||||
act->setText(columnNames().at(column));
|
||||
}
|
||||
|
||||
void ResourceFolderModel::saveHiddenColumn(int column, bool hidden)
|
||||
{
|
||||
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||
auto setting = (m_instance->settings()->contains(setting_name)) ?
|
||||
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
|
||||
|
||||
auto hiddenColumns = setting->get().toStringList();
|
||||
auto name = columnNames(false).at(column);
|
||||
auto index = hiddenColumns.indexOf(name);
|
||||
if (index >= 0 && !hidden) {
|
||||
hiddenColumns.removeAt(index);
|
||||
} else if ( index < 0 && hidden) {
|
||||
hiddenColumns.append(name);
|
||||
}
|
||||
setting->set(hiddenColumns);
|
||||
}
|
||||
|
||||
void ResourceFolderModel::loadHiddenColumns(QTreeView *tree)
|
||||
{
|
||||
auto const setting_name = QString("UI/%1_Page/HiddenColumns").arg(id());
|
||||
auto setting = (m_instance->settings()->contains(setting_name)) ?
|
||||
m_instance->settings()->getSetting(setting_name) : m_instance->settings()->registerSetting(setting_name);
|
||||
|
||||
auto hiddenColumns = setting->get().toStringList();
|
||||
auto col_names = columnNames(false);
|
||||
for (auto col_name : hiddenColumns) {
|
||||
auto index = col_names.indexOf(col_name);
|
||||
if (index >= 0)
|
||||
tree->setColumnHidden(index, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
|
||||
{
|
||||
auto menu = new QMenu(tree);
|
||||
|
||||
menu->addSeparator()->setText(tr("Show / Hide Columns"));
|
||||
|
||||
for (int col = 0; col < columnCount(); ++col) {
|
||||
auto act = new QAction(menu);
|
||||
setupHeaderAction(act, col);
|
||||
|
||||
act->setCheckable(true);
|
||||
act->setChecked(!tree->isColumnHidden(col));
|
||||
|
||||
connect(act, &QAction::toggled, tree, [this, col, tree](bool toggled){
|
||||
tree->setColumnHidden(col, !toggled);
|
||||
for(int c = 0; c < columnCount(); ++c) {
|
||||
if (m_column_resize_modes.at(c) == QHeaderView::ResizeToContents)
|
||||
tree->resizeColumnToContents(c);
|
||||
}
|
||||
saveHiddenColumn(col, !toggled);
|
||||
});
|
||||
|
||||
menu->addAction(act);
|
||||
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent)
|
||||
{
|
||||
return new ProxyModel(parent);
|
||||
@ -511,16 +585,6 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
|
||||
return m_column_sort_keys.at(column);
|
||||
}
|
||||
|
||||
void ResourceFolderModel::enableInteraction(bool enabled)
|
||||
{
|
||||
if (m_can_interact == enabled)
|
||||
return;
|
||||
|
||||
m_can_interact = enabled;
|
||||
if (size())
|
||||
emit dataChanged(index(0), index(size() - 1));
|
||||
}
|
||||
|
||||
/* Standard Proxy Model for createFilterProxyModel */
|
||||
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
|
||||
{
|
||||
@ -556,6 +620,7 @@ void ResourceFolderModel::enableInteraction(bool enabled)
|
||||
return (compare_result.first > 0);
|
||||
}
|
||||
|
||||
QString ResourceFolderModel::instDirPath() const {
|
||||
QString ResourceFolderModel::instDirPath() const
|
||||
{
|
||||
return QFileInfo(m_instance->instanceRoot()).absoluteFilePath();
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QAction>
|
||||
#include <QTreeView>
|
||||
#include <QAbstractListModel>
|
||||
#include <QDir>
|
||||
#include <QFileSystemWatcher>
|
||||
@ -11,8 +14,8 @@
|
||||
|
||||
#include "BaseInstance.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
@ -26,9 +29,11 @@ class QSortFilterProxyModel;
|
||||
class ResourceFolderModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResourceFolderModel(QDir, std::shared_ptr<const BaseInstance>, QObject* parent = nullptr, bool create_dir = true);
|
||||
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
|
||||
~ResourceFolderModel() override;
|
||||
|
||||
virtual QString id() const { return "resource"; }
|
||||
|
||||
/** Starts watching the paths for changes.
|
||||
*
|
||||
* Returns whether starting to watch all the paths was successful.
|
||||
@ -92,6 +97,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
|
||||
/* Basic columns */
|
||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; };
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
||||
@ -110,6 +116,11 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
|
||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setupHeaderAction(QAction* act, int column);
|
||||
void saveHiddenColumn(int column, bool hidden);
|
||||
void loadHiddenColumns(QTreeView* tree);
|
||||
QMenu* createHeaderContextMenu(QTreeView* tree);
|
||||
|
||||
/** This creates a proxy model to filter / sort the model for a UI.
|
||||
*
|
||||
* The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead!
|
||||
@ -117,6 +128,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] SortType columnToSortKey(size_t column) const;
|
||||
[[nodiscard]] QList<QHeaderView::ResizeMode> columnResizeModes() const { return m_column_resize_modes; }
|
||||
|
||||
class ProxyModel : public QSortFilterProxyModel {
|
||||
public:
|
||||
@ -129,10 +141,6 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
|
||||
QString instDirPath() const;
|
||||
|
||||
public slots:
|
||||
void enableInteraction(bool enabled);
|
||||
void disableInteraction(bool disabled) { enableInteraction(!disabled); }
|
||||
|
||||
signals:
|
||||
void updateFinished();
|
||||
|
||||
@ -181,17 +189,22 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* if the resource is complex and has more stuff to parse.
|
||||
*/
|
||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||
virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
|
||||
virtual void onParseFailed(int ticket, QString resource_id)
|
||||
{
|
||||
Q_UNUSED(ticket);
|
||||
Q_UNUSED(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_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE };
|
||||
|
||||
bool m_can_interact = true;
|
||||
QStringList m_column_names = {"Enable", "Name", "Last Modified"};
|
||||
QStringList m_column_names_translated = {tr("Enable"), tr("Name"), tr("Last Modified")};
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch, QHeaderView::ResizeToContents };
|
||||
|
||||
QDir m_dir;
|
||||
std::shared_ptr<const BaseInstance> m_instance;
|
||||
BaseInstance* m_instance;
|
||||
QFileSystemWatcher m_watcher;
|
||||
bool m_is_watching = false;
|
||||
|
||||
|
@ -18,7 +18,8 @@ static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
|
||||
{ 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } },
|
||||
{ 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } },
|
||||
{ 9, { Version("1.19"), Version("1.19.2") } }, { 11, { Version("22w42a"), Version("22w44a") } },
|
||||
{ 12, { Version("1.19.3"), Version("1.19.3") } },
|
||||
{ 12, { Version("1.19.3"), Version("1.19.3") } }, { 13, { Version("1.19.4"), Version("1.19.4") } },
|
||||
{ 14, { Version("1.20"), Version("1.20") } }
|
||||
};
|
||||
|
||||
void ResourcePack::setPackFormat(int new_format_id)
|
||||
@ -39,7 +40,7 @@ void ResourcePack::setDescription(QString new_description)
|
||||
m_description = new_description;
|
||||
}
|
||||
|
||||
void ResourcePack::setImage(QImage new_image)
|
||||
void ResourcePack::setImage(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
@ -48,7 +49,10 @@ void ResourcePack::setImage(QImage new_image)
|
||||
if (m_pack_image_cache_key.key.isValid())
|
||||
PixmapCache::instance().remove(m_pack_image_cache_key.key);
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::instance().insert(QPixmap::fromImage(new_image));
|
||||
// scale the image to avoid flooding the pixmapcache
|
||||
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
|
||||
// This can happen if the pixmap is too big to fit in the cache :c
|
||||
@ -58,21 +62,25 @@ void ResourcePack::setImage(QImage new_image)
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap ResourcePack::image(QSize size)
|
||||
QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
{
|
||||
QPixmap cached_image;
|
||||
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size);
|
||||
return cached_image.scaled(size, mode);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
if (!m_pack_image_cache_key.was_ever_used)
|
||||
if (!m_pack_image_cache_key.was_ever_used) {
|
||||
return {};
|
||||
} else {
|
||||
qDebug() << "Resource Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
|
||||
// Imaged got evicted from the cache. Re-process it and retry.
|
||||
ResourcePackUtils::process(*this);
|
||||
ResourcePackUtils::processPackPNG(*this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
@ -94,6 +102,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
auto res = Resource::compare(other, type);
|
||||
if (res.first != 0)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
@ -103,6 +112,7 @@ std::pair<int, bool> ResourcePack::compare(const Resource& other, SortType type)
|
||||
return { 1, type == SortType::PACK_FORMAT };
|
||||
if (this_ver < other_ver)
|
||||
return { -1, type == SortType::PACK_FORMAT };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { 0, false };
|
||||
|
@ -31,7 +31,7 @@ class ResourcePack : public Resource {
|
||||
[[nodiscard]] QString description() const { return m_description; }
|
||||
|
||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap image(QSize size);
|
||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
|
||||
/** Thread-safe. */
|
||||
void setPackFormat(int new_format_id);
|
||||
@ -40,7 +40,7 @@ class ResourcePack : public Resource {
|
||||
void setDescription(QString new_description);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
void setImage(QImage new_image) const;
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
@ -67,5 +67,5 @@ class ResourcePack : public Resource {
|
||||
struct {
|
||||
QPixmapCache::Key key;
|
||||
bool was_ever_used = false;
|
||||
} m_pack_image_cache_key;
|
||||
} mutable m_pack_image_cache_key;
|
||||
};
|
||||
|
@ -35,6 +35,8 @@
|
||||
*/
|
||||
|
||||
#include "ResourcePackFolderModel.h"
|
||||
#include <qnamespace.h>
|
||||
#include <qsize.h>
|
||||
|
||||
#include <QIcon>
|
||||
#include <QStyle>
|
||||
@ -45,10 +47,14 @@
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance)
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance)
|
||||
: ResourceFolderModel(QDir(dir), instance)
|
||||
{
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||
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::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
|
||||
|
||||
}
|
||||
|
||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
@ -84,9 +90,11 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NAME_COLUMN && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
@ -94,7 +102,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
//: The string being explained by this is in the format: ID (Lower version - Upper version)
|
||||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||
}
|
||||
if (column == NAME_COLUMN) {
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
@ -126,13 +134,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
return QString();
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
case PackFormatColumn:
|
||||
return tr("Pack Format");
|
||||
case DateColumn:
|
||||
return tr("Last changed");
|
||||
case ImageColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@ -151,6 +157,11 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::SizeHintRole:
|
||||
if (section == ImageColumn) {
|
||||
return QSize(64,0);
|
||||
}
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -11,13 +11,16 @@ public:
|
||||
enum Columns
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
ImageColumn,
|
||||
NameColumn,
|
||||
PackFormatColumn,
|
||||
DateColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
||||
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||
|
||||
virtual QString id() const override { return "resourcepacks"; }
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
|
@ -6,7 +6,9 @@ class ShaderPackFolderModel : public ResourceFolderModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance)
|
||||
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
|
||||
: ResourceFolderModel(QDir(dir), instance)
|
||||
{}
|
||||
|
||||
virtual QString id() const override { return "shaderpacks"; }
|
||||
};
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <QMap>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "MTPixmapCache.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
|
||||
|
||||
void TexturePack::setDescription(QString new_description)
|
||||
@ -32,34 +34,41 @@ void TexturePack::setDescription(QString new_description)
|
||||
m_description = new_description;
|
||||
}
|
||||
|
||||
void TexturePack::setImage(QImage new_image)
|
||||
void TexturePack::setImage(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
Q_ASSERT(!new_image.isNull());
|
||||
|
||||
if (m_pack_image_cache_key.key.isValid())
|
||||
QPixmapCache::remove(m_pack_image_cache_key.key);
|
||||
PixmapCache::remove(m_pack_image_cache_key.key);
|
||||
|
||||
m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image));
|
||||
// scale the image to avoid flooding the pixmapcache
|
||||
auto pixmap = QPixmap::fromImage(new_image.scaled({64, 64}, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
|
||||
|
||||
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
|
||||
m_pack_image_cache_key.was_ever_used = true;
|
||||
}
|
||||
|
||||
QPixmap TexturePack::image(QSize size)
|
||||
QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
{
|
||||
QPixmap cached_image;
|
||||
if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
|
||||
if (size.isNull())
|
||||
return cached_image;
|
||||
return cached_image.scaled(size);
|
||||
return cached_image.scaled(size, mode);
|
||||
}
|
||||
|
||||
// No valid image we can get
|
||||
if (!m_pack_image_cache_key.was_ever_used)
|
||||
if (!m_pack_image_cache_key.was_ever_used) {
|
||||
return {};
|
||||
} else {
|
||||
qDebug() << "Texture Pack" << name() << "Had it's image evicted from the cache. reloading...";
|
||||
PixmapCache::markCacheMissByEviciton();
|
||||
}
|
||||
|
||||
// Imaged got evicted from the cache. Re-process it and retry.
|
||||
TexturePackUtils::process(*this);
|
||||
TexturePackUtils::processPackPNG(*this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
|
@ -40,13 +40,13 @@ class TexturePack : public Resource {
|
||||
[[nodiscard]] QString description() const { return m_description; }
|
||||
|
||||
/** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap image(QSize size);
|
||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
|
||||
/** Thread-safe. */
|
||||
void setDescription(QString new_description);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image);
|
||||
void setImage(QImage new_image) const;
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
@ -65,5 +65,5 @@ class TexturePack : public Resource {
|
||||
struct {
|
||||
QPixmapCache::Key key;
|
||||
bool was_ever_used = false;
|
||||
} m_pack_image_cache_key;
|
||||
} mutable m_pack_image_cache_key;
|
||||
};
|
||||
|
@ -33,15 +33,24 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include "TexturePackFolderModel.h"
|
||||
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
|
||||
|
||||
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance)
|
||||
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance)
|
||||
: ResourceFolderModel(QDir(dir), instance)
|
||||
{}
|
||||
{
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
|
||||
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents};
|
||||
|
||||
}
|
||||
|
||||
Task* TexturePackFolderModel::createUpdateTask()
|
||||
{
|
||||
@ -52,3 +61,96 @@ Task* TexturePackFolderModel::createParseTask(Resource& resource)
|
||||
{
|
||||
return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast<TexturePack&>(resource));
|
||||
}
|
||||
|
||||
|
||||
QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!validateIndex(index))
|
||||
return {};
|
||||
|
||||
int row = index.row();
|
||||
int column = index.column();
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (column) {
|
||||
case NameColumn:
|
||||
return m_resources[row]->name();
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
return 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());;
|
||||
}
|
||||
if (at(row)->isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->image({32, 32}, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
case Qt::CheckStateRole:
|
||||
if (column == ActiveColumn) {
|
||||
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
case NameColumn:
|
||||
case DateColumn:
|
||||
case ImageColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("Is the resource enabled?");
|
||||
case NameColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The name of the resource.");
|
||||
case DateColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The date and time this resource was last changed (or added).");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int TexturePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
|
@ -38,12 +38,35 @@
|
||||
|
||||
#include "ResourceFolderModel.h"
|
||||
|
||||
#include "TexturePack.h"
|
||||
|
||||
class TexturePackFolderModel : public ResourceFolderModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
enum Columns
|
||||
{
|
||||
ActiveColumn = 0,
|
||||
ImageColumn,
|
||||
NameColumn,
|
||||
DateColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
||||
|
||||
virtual QString id() const override { return "texturepacks"; }
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex &parent) const override;
|
||||
|
||||
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||
[[nodiscard]] Task* createUpdateTask() override;
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
RESOURCE_HELPERS(TexturePack)
|
||||
};
|
||||
|
252
launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
Normal file
252
launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GetModDependenciesTask.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include "Json.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
#include "ui/pages/modplatform/ModModel.h"
|
||||
#include "ui/pages/modplatform/flame/FlameResourceModels.h"
|
||||
#include "ui/pages/modplatform/modrinth/ModrinthResourceModels.h"
|
||||
|
||||
static Version mcVersion(BaseInstance* inst)
|
||||
{
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
|
||||
}
|
||||
|
||||
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
|
||||
{
|
||||
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
|
||||
}
|
||||
|
||||
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
|
||||
BaseInstance* instance,
|
||||
ModFolderModel* folder,
|
||||
QList<std::shared_ptr<PackDependency>> selected)
|
||||
: SequentialTask(parent, tr("Get dependencies"))
|
||||
, m_selected(selected)
|
||||
, m_flame_provider{ ModPlatform::ResourceProvider::FLAME, std::make_shared<ResourceDownload::FlameModModel>(*instance),
|
||||
std::make_shared<FlameAPI>() }
|
||||
, m_modrinth_provider{ ModPlatform::ResourceProvider::MODRINTH, std::make_shared<ResourceDownload::ModrinthModModel>(*instance),
|
||||
std::make_shared<ModrinthAPI>() }
|
||||
, m_version(mcVersion(instance))
|
||||
, m_loaderType(mcLoaders(instance))
|
||||
{
|
||||
for (auto mod : folder->allMods())
|
||||
if (auto meta = mod->metadata(); meta)
|
||||
m_mods.append(meta);
|
||||
prepare();
|
||||
};
|
||||
|
||||
void GetModDependenciesTask::prepare()
|
||||
{
|
||||
for (auto sel : m_selected) {
|
||||
for (auto dep : getDependenciesForVersion(sel->version, sel->pack->provider)) {
|
||||
addTask(prepareDependencyTask(dep, sel->pack->provider, 20));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
|
||||
const ModPlatform::ResourceProvider providerName)
|
||||
{
|
||||
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
|
||||
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
|
||||
});
|
||||
if (over != overide.cend()) {
|
||||
return { isQuilt ? over->quilt : over->fabric, dep.type };
|
||||
}
|
||||
}
|
||||
return dep;
|
||||
}
|
||||
|
||||
QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion(const ModPlatform::IndexedVersion& version,
|
||||
const ModPlatform::ResourceProvider providerName)
|
||||
{
|
||||
QList<ModPlatform::Dependency> c_dependencies;
|
||||
for (auto ver_dep : version.dependencies) {
|
||||
if (ver_dep.type != ModPlatform::DependencyType::REQUIRED)
|
||||
continue;
|
||||
|
||||
auto isOnlyVersion = providerName == ModPlatform::ResourceProvider::MODRINTH && ver_dep.addonId.toString().isEmpty();
|
||||
if (auto dep = std::find_if(c_dependencies.begin(), c_dependencies.end(),
|
||||
[&ver_dep, isOnlyVersion](const ModPlatform::Dependency& i) {
|
||||
return isOnlyVersion ? i.version == ver_dep.version : i.addonId == ver_dep.addonId;
|
||||
});
|
||||
dep != c_dependencies.end())
|
||||
continue; // check the current dependency list
|
||||
|
||||
if (auto dep = std::find_if(m_selected.begin(), m_selected.end(),
|
||||
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
|
||||
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.version
|
||||
: i->pack->addonId == ver_dep.addonId);
|
||||
});
|
||||
dep != m_selected.end())
|
||||
continue; // check the selected versions
|
||||
|
||||
if (auto dep = std::find_if(m_mods.begin(), m_mods.end(),
|
||||
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<Metadata::ModStruct> i) {
|
||||
return i->provider == providerName &&
|
||||
(isOnlyVersion ? i->file_id == ver_dep.version : i->project_id == ver_dep.addonId);
|
||||
});
|
||||
dep != m_mods.end())
|
||||
continue; // check the existing mods
|
||||
|
||||
if (auto dep = std::find_if(m_pack_dependencies.begin(), m_pack_dependencies.end(),
|
||||
[&ver_dep, providerName, isOnlyVersion](std::shared_ptr<PackDependency> i) {
|
||||
return i->pack->provider == providerName && (isOnlyVersion ? i->version.version == ver_dep.addonId
|
||||
: i->pack->addonId == ver_dep.addonId);
|
||||
});
|
||||
dep != m_pack_dependencies.end()) // check loaded dependencies
|
||||
continue;
|
||||
|
||||
c_dependencies.append(getOverride(ver_dep, providerName));
|
||||
}
|
||||
return c_dependencies;
|
||||
};
|
||||
|
||||
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
|
||||
{
|
||||
auto provider = pDep->pack->provider == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||
auto responseInfo = std::make_shared<QByteArray>();
|
||||
auto info = provider.api->getProject(pDep->pack->addonId.toString(), responseInfo);
|
||||
QObject::connect(info.get(), &NetJob::succeeded, [responseInfo, provider, pDep] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*responseInfo, &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() << *responseInfo;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto obj = provider.name == ModPlatform::ResourceProvider::FLAME ? Json::requireObject(Json::requireObject(doc), "data")
|
||||
: Json::requireObject(doc);
|
||||
provider.mod->loadIndexedPack(*pDep->pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
});
|
||||
return info;
|
||||
}
|
||||
|
||||
Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Dependency& dep,
|
||||
const ModPlatform::ResourceProvider providerName,
|
||||
int level)
|
||||
{
|
||||
auto pDep = std::make_shared<PackDependency>();
|
||||
pDep->dependency = dep;
|
||||
pDep->pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
pDep->pack->addonId = dep.addonId;
|
||||
pDep->pack->provider = providerName;
|
||||
|
||||
m_pack_dependencies.append(pDep);
|
||||
auto provider = providerName == m_flame_provider.name ? m_flame_provider : m_modrinth_provider;
|
||||
|
||||
auto tasks = makeShared<SequentialTask>(
|
||||
this, QString("DependencyInfo: %1").arg(dep.addonId.toString().isEmpty() ? dep.version : dep.addonId.toString()));
|
||||
|
||||
if (!dep.addonId.toString().isEmpty()) {
|
||||
tasks->addTask(getProjectInfoTask(pDep));
|
||||
}
|
||||
|
||||
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
|
||||
ResourceAPI::DependencySearchCallbacks callbacks;
|
||||
|
||||
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
|
||||
try {
|
||||
QJsonArray arr;
|
||||
if (dep.version.length() != 0 && doc.isObject()) {
|
||||
arr.append(doc.object());
|
||||
} else {
|
||||
arr = doc.isObject() ? Json::ensureArray(doc.object(), "data") : doc.array();
|
||||
}
|
||||
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
|
||||
if (!pDep->version.addonId.isValid()) {
|
||||
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
|
||||
auto overide = ModPlatform::getOverrideDeps();
|
||||
auto over = std::find_if(overide.cbegin(), overide.cend(),
|
||||
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
|
||||
if (over != overide.cend()) {
|
||||
removePack(dep.addonId);
|
||||
addTask(prepareDependencyTask({ over->fabric, dep.type }, provider.name, level));
|
||||
return;
|
||||
}
|
||||
}
|
||||
qWarning() << "Error while reading mod version empty ";
|
||||
qDebug() << doc;
|
||||
return;
|
||||
}
|
||||
pDep->version.is_currently_selected = true;
|
||||
pDep->pack->versions = { pDep->version };
|
||||
pDep->pack->versionsLoaded = true;
|
||||
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod version: " << e.cause();
|
||||
return;
|
||||
}
|
||||
if (level == 0) {
|
||||
qWarning() << "Dependency cycle exeeded";
|
||||
return;
|
||||
}
|
||||
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
|
||||
pDep->pack->addonId = pDep->version.addonId;
|
||||
auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
|
||||
if (dep.addonId != pDep->version.addonId) {
|
||||
removePack(pDep->version.addonId);
|
||||
addTask(prepareDependencyTask(dep, provider.name, level));
|
||||
} else
|
||||
addTask(getProjectInfoTask(pDep));
|
||||
}
|
||||
for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
|
||||
addTask(prepareDependencyTask(dep, provider.name, level - 1));
|
||||
}
|
||||
};
|
||||
|
||||
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
|
||||
tasks->addTask(version);
|
||||
return tasks;
|
||||
};
|
||||
|
||||
void GetModDependenciesTask::removePack(const QVariant addonId)
|
||||
{
|
||||
auto pred = [addonId](const std::shared_ptr<PackDependency>& v) { return v->pack->addonId == addonId; };
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0)
|
||||
m_pack_dependencies.removeIf(pred);
|
||||
#else
|
||||
for (auto it = m_pack_dependencies.begin(); it != m_pack_dependencies.end();)
|
||||
if (pred(*it))
|
||||
it = m_pack_dependencies.erase(it);
|
||||
else
|
||||
++it;
|
||||
#endif
|
||||
}
|
84
launcher/minecraft/mod/tasks/GetModDependenciesTask.h
Normal file
84
launcher/minecraft/mod/tasks/GetModDependenciesTask.h
Normal file
@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QEventLoop>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/pages/modplatform/ModModel.h"
|
||||
|
||||
class GetModDependenciesTask : public SequentialTask {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<GetModDependenciesTask>;
|
||||
|
||||
struct PackDependency {
|
||||
ModPlatform::Dependency dependency;
|
||||
ModPlatform::IndexedPack::Ptr pack;
|
||||
ModPlatform::IndexedVersion version;
|
||||
PackDependency() = default;
|
||||
PackDependency(const ModPlatform::IndexedPack::Ptr p, const ModPlatform::IndexedVersion& v)
|
||||
{
|
||||
pack = p;
|
||||
version = v;
|
||||
}
|
||||
};
|
||||
|
||||
struct Provider {
|
||||
ModPlatform::ResourceProvider name;
|
||||
std::shared_ptr<ResourceDownload::ModModel> mod;
|
||||
std::shared_ptr<ResourceAPI> api;
|
||||
};
|
||||
|
||||
explicit GetModDependenciesTask(QObject* parent,
|
||||
BaseInstance* instance,
|
||||
ModFolderModel* folder,
|
||||
QList<std::shared_ptr<PackDependency>> selected);
|
||||
|
||||
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
|
||||
|
||||
protected slots:
|
||||
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
|
||||
QList<ModPlatform::Dependency> getDependenciesForVersion(const ModPlatform::IndexedVersion&,
|
||||
const ModPlatform::ResourceProvider providerName);
|
||||
void prepare();
|
||||
Task::Ptr getProjectInfoTask(std::shared_ptr<PackDependency> pDep);
|
||||
ModPlatform::Dependency getOverride(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider providerName);
|
||||
void removePack(const QVariant addonId);
|
||||
|
||||
private:
|
||||
QList<std::shared_ptr<PackDependency>> m_pack_dependencies;
|
||||
QList<std::shared_ptr<Metadata::ModStruct>> m_mods;
|
||||
QList<std::shared_ptr<PackDependency>> m_selected;
|
||||
Provider m_flame_provider;
|
||||
Provider m_modrinth_provider;
|
||||
|
||||
Version m_version;
|
||||
ResourceAPI::ModLoaderTypes m_loaderType;
|
||||
};
|
@ -52,6 +52,10 @@ ModDetails ReadMCModInfo(QByteArray contents)
|
||||
authors = firstObj.value("authors").toArray();
|
||||
}
|
||||
|
||||
if (firstObj.contains("logoFile")) {
|
||||
details.icon_file = firstObj.value("logoFile").toString();
|
||||
}
|
||||
|
||||
for (auto author : authors) {
|
||||
details.authors.append(author.toString());
|
||||
}
|
||||
@ -166,6 +170,31 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
||||
}
|
||||
details.homeurl = homeurl;
|
||||
|
||||
QString issueTrackerURL = "";
|
||||
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
|
||||
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
|
||||
} else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
|
||||
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
|
||||
}
|
||||
details.issue_tracker = issueTrackerURL;
|
||||
|
||||
QString license = "";
|
||||
if (auto licenseDatum = tomlData["license"].as_string()) {
|
||||
license = QString::fromStdString(licenseDatum->get());
|
||||
} else if (auto licenseDatum =(*modsTable)["license"].as_string()) {
|
||||
license = QString::fromStdString(licenseDatum->get());
|
||||
}
|
||||
if (!license.isEmpty())
|
||||
details.licenses.append(ModLicense(license));
|
||||
|
||||
QString logoFile = "";
|
||||
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
|
||||
logoFile = QString::fromStdString(logoFileDatum->get());
|
||||
} else if (auto logoFileDatum =(*modsTable)["logoFile"].as_string()) {
|
||||
logoFile = QString::fromStdString(logoFileDatum->get());
|
||||
}
|
||||
details.icon_file = logoFile;
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
@ -201,6 +230,57 @@ ModDetails ReadFabricModInfo(QByteArray contents)
|
||||
if (contact.contains("homepage")) {
|
||||
details.homeurl = contact.value("homepage").toString();
|
||||
}
|
||||
if (contact.contains("issues")) {
|
||||
details.issue_tracker = contact.value("issues").toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (object.contains("license")) {
|
||||
auto license = object.value("license");
|
||||
if (license.isArray()) {
|
||||
for (auto l : license.toArray()) {
|
||||
if (l.isString()) {
|
||||
details.licenses.append(ModLicense(l.toString()));
|
||||
} else if (l.isObject()) {
|
||||
auto obj = l.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||
obj.value("url").toString(), obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
} else if (license.isString()) {
|
||||
details.licenses.append(ModLicense(license.toString()));
|
||||
} else if (license.isObject()) {
|
||||
auto obj = license.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
||||
obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (object.contains("icon")) {
|
||||
auto icon = object.value("icon");
|
||||
if (icon.isObject()) {
|
||||
auto obj = icon.toObject();
|
||||
// take the largest icon
|
||||
int largest = 0;
|
||||
for (auto key : obj.keys()) {
|
||||
auto size = key.split('x').first().toInt();
|
||||
if (size > largest) {
|
||||
largest = size;
|
||||
}
|
||||
}
|
||||
if (largest > 0) {
|
||||
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||
details.icon_file = obj.value(key).toString();
|
||||
} else { // parsing the sizes failed
|
||||
// take the first
|
||||
for (auto i : obj) {
|
||||
details.icon_file = i.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (icon.isString()) {
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
return details;
|
||||
@ -238,6 +318,58 @@ ModDetails ReadQuiltModInfo(QByteArray contents)
|
||||
if (modContact.contains("homepage")) {
|
||||
details.homeurl = Json::requireString(modContact.value("homepage"));
|
||||
}
|
||||
if (modContact.contains("issues")) {
|
||||
details.issue_tracker = Json::requireString(modContact.value("issues"));
|
||||
}
|
||||
|
||||
if (modMetadata.contains("license")) {
|
||||
auto license = modMetadata.value("license");
|
||||
if (license.isArray()) {
|
||||
for (auto l : license.toArray()) {
|
||||
if (l.isString()) {
|
||||
details.licenses.append(ModLicense(l.toString()));
|
||||
} else if (l.isObject()) {
|
||||
auto obj = l.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(),
|
||||
obj.value("url").toString(), obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
} else if (license.isString()) {
|
||||
details.licenses.append(ModLicense(license.toString()));
|
||||
} else if (license.isObject()) {
|
||||
auto obj = license.toObject();
|
||||
details.licenses.append(ModLicense(obj.value("name").toString(), obj.value("id").toString(), obj.value("url").toString(),
|
||||
obj.value("description").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modMetadata.contains("icon")) {
|
||||
auto icon = modMetadata.value("icon");
|
||||
if (icon.isObject()) {
|
||||
auto obj = icon.toObject();
|
||||
// take the largest icon
|
||||
int largest = 0;
|
||||
for (auto key : obj.keys()) {
|
||||
auto size = key.split('x').first().toInt();
|
||||
if (size > largest) {
|
||||
largest = size;
|
||||
}
|
||||
}
|
||||
if (largest > 0) {
|
||||
auto key = QString::number(largest) + "x" + QString::number(largest);
|
||||
details.icon_file = obj.value(key).toString();
|
||||
} else { // parsing the sizes failed
|
||||
// take the first
|
||||
for (auto i : obj) {
|
||||
details.icon_file = i.toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (icon.isString()) {
|
||||
details.icon_file = icon.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return details;
|
||||
}
|
||||
@ -515,6 +647,85 @@ bool validate(QFileInfo file)
|
||||
return ModUtils::process(mod, ProcessingLevel::BasicInfoOnly) && mod.valid();
|
||||
}
|
||||
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
mod.setIcon(img);
|
||||
} else {
|
||||
qWarning() << "Failed to parse mod logo:" << mod.iconPath() << "from" << mod.name();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadIconFile(const Mod& mod) {
|
||||
if (mod.iconPath().isEmpty()) {
|
||||
qWarning() << "No Iconfile set, be sure to parse the mod first";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto png_invalid = [&mod]() {
|
||||
qWarning() << "Mod at" << mod.fileinfo().filePath() << "does not have a valid icon";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (mod.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
{
|
||||
QFileInfo icon_info(FS::PathCombine(mod.fileinfo().filePath(), mod.iconPath()));
|
||||
if (icon_info.exists() && icon_info.isFile()) {
|
||||
QFile icon(icon_info.filePath());
|
||||
if (!icon.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
auto data = icon.readAll();
|
||||
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||
|
||||
icon.close();
|
||||
|
||||
if (!icon_result) {
|
||||
return png_invalid(); // icon invalid
|
||||
}
|
||||
}
|
||||
}
|
||||
case ResourceType::ZIPFILE:
|
||||
{
|
||||
QuaZip zip(mod.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false;
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
if (zip.setCurrentFile(mod.iconPath())) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool icon_result = ModUtils::processIconPNG(mod, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!icon_result) {
|
||||
return png_invalid(); // icon png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // could not set icon as current file.
|
||||
}
|
||||
}
|
||||
case ResourceType::LITEMOD:
|
||||
{
|
||||
return false; // can lightmods even have icons?
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for mod, can not load icon.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ModUtils
|
||||
|
||||
LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile)
|
||||
|
@ -25,6 +25,9 @@ bool processLitemod(Mod& mod, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
/** Checks whether a file is valid as a mod or not. */
|
||||
bool validate(QFileInfo file);
|
||||
|
||||
bool processIconPNG(const Mod& mod, QByteArray&& raw_data);
|
||||
bool loadIconFile(const Mod& mod);
|
||||
} // namespace ModUtils
|
||||
|
||||
class LocalModParseTask : public Task {
|
||||
|
@ -1,25 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "LocalModUpdateTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
|
@ -165,15 +165,16 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -193,7 +194,7 @@ bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
@ -205,6 +206,68 @@ bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const ResourcePack& pack)
|
||||
{
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
{
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
return png_invalid(); // can't open pack.png file
|
||||
|
||||
auto data = pack_png_file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
pack_png_file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
}
|
||||
case ResourceType::ZIPFILE:
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
|
@ -35,7 +35,10 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Ful
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||
bool processPackPNG(const ResourcePack& pack);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
|
@ -131,6 +131,7 @@ bool processZIP(TexturePack& pack, ProcessingLevel level)
|
||||
bool packPNG_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!packPNG_result) {
|
||||
return false;
|
||||
}
|
||||
@ -147,7 +148,7 @@ bool processPackTXT(TexturePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
||||
bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
@ -159,6 +160,70 @@ bool processPackPNG(TexturePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const TexturePack& pack)
|
||||
{
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Texture pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
{
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
return png_invalid(); // can't open pack.png file
|
||||
|
||||
auto data = pack_png_file.readAll();
|
||||
|
||||
bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
pack_png_file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
}
|
||||
case ResourceType::ZIPFILE:
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = TexturePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!pack_png_result) {
|
||||
zip.close();
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
}
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
TexturePack rp{ file };
|
||||
|
@ -36,7 +36,10 @@ bool processZIP(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full
|
||||
bool processFolder(TexturePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processPackTXT(TexturePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(TexturePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const TexturePack& pack, QByteArray&& raw_data);
|
||||
|
||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||
bool processPackPNG(const TexturePack& pack);
|
||||
|
||||
/** Checks whether a file is valid as a texture pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
|
@ -103,7 +103,7 @@ void ModFolderLoadTask::executeTask()
|
||||
while (iter.hasNext()) {
|
||||
auto mod = iter.next().value();
|
||||
if (mod->status() == ModStatus::NotInstalled) {
|
||||
mod->destroy(m_index_dir, false);
|
||||
mod->destroy(m_index_dir, false, false);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
@ -54,9 +54,14 @@ void CapeChange::setCape(QString& cape) {
|
||||
setStatus(tr("Equipping cape"));
|
||||
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
||||
#else
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
||||
#endif
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
||||
}
|
||||
|
||||
void CapeChange::clearCape() {
|
||||
@ -68,13 +73,14 @@ void CapeChange::clearCape() {
|
||||
setStatus(tr("Removing cape"));
|
||||
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
||||
#else
|
||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
||||
#endif
|
||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &CapeChange::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &CapeChange::downloadFinished);
|
||||
}
|
||||
|
||||
|
||||
@ -95,6 +101,17 @@ void CapeChange::downloadError(QNetworkReply::NetworkError error)
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void CapeChange::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Cape change SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void CapeChange::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
|
@ -27,6 +27,7 @@ protected:
|
||||
|
||||
public slots:
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
void downloadFinished();
|
||||
};
|
||||
|
||||
|
@ -53,13 +53,14 @@ void SkinDelete::executeTask()
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
|
||||
setStatus(tr("Deleting skin"));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError);
|
||||
#else
|
||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinDelete::downloadError);
|
||||
#endif
|
||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinDelete::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &SkinDelete::downloadFinished);
|
||||
}
|
||||
|
||||
void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
||||
@ -69,6 +70,17 @@ void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void SkinDelete::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Skin Delete SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinDelete::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
|
@ -22,5 +22,6 @@ protected:
|
||||
|
||||
public slots:
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
void downloadFinished();
|
||||
};
|
||||
|
@ -78,13 +78,14 @@ void SkinUpload::executeTask()
|
||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||
|
||||
setStatus(tr("Uploading skin"));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||
connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError);
|
||||
#else
|
||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
||||
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinUpload::downloadError);
|
||||
#endif
|
||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
||||
connect(rep, &QNetworkReply::sslErrors, this, &SkinUpload::sslErrors);
|
||||
connect(rep, &QNetworkReply::finished, this, &SkinUpload::downloadFinished);
|
||||
}
|
||||
|
||||
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
||||
@ -94,6 +95,17 @@ void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
||||
emitFailed(m_reply->errorString());
|
||||
}
|
||||
|
||||
void SkinUpload::sslErrors(const QList<QSslError>& errors)
|
||||
{
|
||||
int i = 1;
|
||||
for (auto error : errors) {
|
||||
qCritical() << "Skin Upload SSL Error #" << i << " : " << error.errorString();
|
||||
auto cert = error.certificate();
|
||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
void SkinUpload::downloadFinished()
|
||||
{
|
||||
// if the download failed
|
||||
|
@ -32,6 +32,7 @@ protected:
|
||||
public slots:
|
||||
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
void sslErrors(const QList<QSslError>& errors);
|
||||
|
||||
void downloadFinished();
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
#include "modplatform/helpers/HashUtils.h"
|
||||
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||
#include "modplatform/modrinth/ModrinthPackIndex.h"
|
||||
|
||||
@ -24,8 +25,8 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource
|
||||
auto hash_task = createNewHash(mod);
|
||||
if (!hash_task)
|
||||
return;
|
||||
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
|
||||
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
|
||||
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
hash_task->start();
|
||||
}
|
||||
|
||||
@ -37,8 +38,8 @@ EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform:
|
||||
auto hash_task = createNewHash(mod);
|
||||
if (!hash_task)
|
||||
continue;
|
||||
connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); });
|
||||
connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
|
||||
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
m_hashing_task->addTask(hash_task);
|
||||
}
|
||||
}
|
||||
@ -212,12 +213,12 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
|
||||
{
|
||||
auto hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
|
||||
|
||||
auto* response = new QByteArray();
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
|
||||
|
||||
// Prevents unfortunate timings when aborting the task
|
||||
if (!ver_task)
|
||||
return Task::Ptr{nullptr};
|
||||
return Task::Ptr{ nullptr };
|
||||
|
||||
connect(ver_task.get(), &Task::succeeded, this, [this, response] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -264,7 +265,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
|
||||
for (auto const& data : m_temp_versions)
|
||||
addonIds.insert(data.addonId.toString(), data.hash);
|
||||
|
||||
auto response = new QByteArray();
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
Task::Ptr proj_task;
|
||||
|
||||
if (addonIds.isEmpty()) {
|
||||
@ -277,7 +278,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
|
||||
|
||||
// Prevents unfortunate timings when aborting the task
|
||||
if (!proj_task)
|
||||
return Task::Ptr{nullptr};
|
||||
return Task::Ptr{ nullptr };
|
||||
|
||||
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
|
||||
QJsonParseError parse_error{};
|
||||
@ -345,7 +346,7 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
|
||||
// Flame
|
||||
Task::Ptr EnsureMetadataTask::flameVersionsTask()
|
||||
{
|
||||
auto* response = new QByteArray();
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
|
||||
QList<uint> fingerprints;
|
||||
for (auto& murmur : m_mods.keys()) {
|
||||
@ -413,7 +414,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
||||
QHash<QString, QString> addonIds;
|
||||
for (auto const& hash : m_mods.keys()) {
|
||||
if (m_temp_versions.contains(hash)) {
|
||||
auto const& data = m_temp_versions.find(hash).value();
|
||||
auto data = m_temp_versions.find(hash).value();
|
||||
|
||||
auto id_str = data.addonId.toString();
|
||||
if (!id_str.isEmpty())
|
||||
@ -421,7 +422,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
||||
}
|
||||
}
|
||||
|
||||
auto response = new QByteArray();
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
Task::Ptr proj_task;
|
||||
|
||||
if (addonIds.isEmpty()) {
|
||||
@ -434,7 +435,7 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
||||
|
||||
// Prevents unfortunate timings when aborting the task
|
||||
if (!proj_task)
|
||||
return Task::Ptr{nullptr};
|
||||
return Task::Ptr{ nullptr };
|
||||
|
||||
connect(proj_task.get(), &Task::succeeded, this, [this, response, addonIds] {
|
||||
QJsonParseError parse_error{};
|
||||
|
@ -70,11 +70,17 @@ auto ProviderCapabilities::hash(ResourceProvider p, QIODevice* device, QString t
|
||||
}
|
||||
|
||||
QCryptographicHash hash(algo);
|
||||
if(!hash.addData(device))
|
||||
if (!hash.addData(device))
|
||||
qCritical() << "Failed to read JAR to create hash!";
|
||||
|
||||
Q_ASSERT(hash.result().length() == hash.hashLength(algo));
|
||||
return { hash.result().toHex() };
|
||||
}
|
||||
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID)
|
||||
{
|
||||
return ((provider == ModPlatform::ResourceProvider::FLAME) ? "https://www.curseforge.com/projects/" : "https://modrinth.com/mod/") +
|
||||
projectID.toString();
|
||||
}
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
@ -1,20 +1,21 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* PolyMC - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -23,6 +24,7 @@
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
class QIODevice;
|
||||
|
||||
@ -32,6 +34,8 @@ enum class ResourceProvider { MODRINTH, FLAME };
|
||||
|
||||
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
|
||||
|
||||
enum class DependencyType { REQUIRED, OPTIONAL, INCOMPATIBLE, EMBEDDED, TOOL, INCLUDE, UNKNOWN };
|
||||
|
||||
class ProviderCapabilities {
|
||||
public:
|
||||
auto name(ResourceProvider) -> const char*;
|
||||
@ -51,6 +55,12 @@ struct DonationData {
|
||||
QString url;
|
||||
};
|
||||
|
||||
struct Dependency {
|
||||
QVariant addonId;
|
||||
DependencyType type;
|
||||
QString version;
|
||||
};
|
||||
|
||||
struct IndexedVersion {
|
||||
QVariant addonId;
|
||||
QVariant fileId;
|
||||
@ -65,10 +75,10 @@ struct IndexedVersion {
|
||||
QString hash;
|
||||
bool is_preferred = true;
|
||||
QString changelog;
|
||||
QList<Dependency> dependencies;
|
||||
|
||||
// For internal use, not provided by APIs
|
||||
bool is_currently_selected = false;
|
||||
QString custom_target_folder;
|
||||
};
|
||||
|
||||
struct ExtraPackData {
|
||||
@ -83,6 +93,8 @@ struct ExtraPackData {
|
||||
};
|
||||
|
||||
struct IndexedPack {
|
||||
using Ptr = std::shared_ptr<IndexedPack>;
|
||||
|
||||
QVariant addonId;
|
||||
ResourceProvider provider;
|
||||
QString name;
|
||||
@ -113,12 +125,30 @@ struct IndexedPack {
|
||||
if (!versionsLoaded)
|
||||
return false;
|
||||
|
||||
return std::any_of(versions.constBegin(), versions.constEnd(),
|
||||
[](auto const& v) { return v.is_currently_selected; });
|
||||
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
|
||||
}
|
||||
};
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID);
|
||||
|
||||
struct OverrideDep {
|
||||
QString quilt;
|
||||
QString fabric;
|
||||
QString slug;
|
||||
ModPlatform::ResourceProvider provider;
|
||||
};
|
||||
|
||||
inline auto getOverrideDeps() -> QList<OverrideDep>
|
||||
{
|
||||
return { { "634179", "306612", "API", ModPlatform::ResourceProvider::FLAME },
|
||||
{ "720410", "308769", "KotlinLibraries", ModPlatform::ResourceProvider::FLAME },
|
||||
|
||||
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
|
||||
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
|
||||
};
|
||||
QString getMetaURL(ResourceProvider provider, QVariant projectID);
|
||||
|
||||
} // namespace ModPlatform
|
||||
|
||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack::Ptr)
|
||||
Q_DECLARE_METATYPE(ModPlatform::ResourceProvider)
|
||||
|
@ -111,6 +111,16 @@ class ResourceAPI {
|
||||
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
|
||||
};
|
||||
|
||||
struct DependencySearchArgs {
|
||||
ModPlatform::Dependency dependency;
|
||||
Version mcVersion;
|
||||
ModLoaderTypes loader;
|
||||
};
|
||||
|
||||
struct DependencySearchCallbacks {
|
||||
std::function<void(QJsonDocument&, const ModPlatform::Dependency&)> on_succeed;
|
||||
};
|
||||
|
||||
public:
|
||||
/** Gets a list of available sorting methods for this API. */
|
||||
[[nodiscard]] virtual auto getSortingMethods() const -> QList<SortingMethod> = 0;
|
||||
@ -121,12 +131,12 @@ class ResourceAPI {
|
||||
qWarning() << "TODO";
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, QByteArray* response) const
|
||||
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const
|
||||
{
|
||||
qWarning() << "TODO";
|
||||
return nullptr;
|
||||
}
|
||||
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, QByteArray* response) const
|
||||
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
|
||||
{
|
||||
qWarning() << "TODO";
|
||||
return nullptr;
|
||||
@ -143,6 +153,12 @@ class ResourceAPI {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual Task::Ptr getDependencyVersion(DependencySearchArgs&&, DependencySearchCallbacks&&) const
|
||||
{
|
||||
qWarning() << "TODO";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static auto getModLoaderString(ModLoaderType type) -> const QString
|
||||
{
|
||||
switch (type) {
|
||||
|
@ -82,9 +82,9 @@ void PackInstallTask::executeTask()
|
||||
{
|
||||
qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
|
||||
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
|
||||
auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
|
||||
.arg(m_pack_safe_name).arg(m_version_name);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
|
||||
auto searchUrl =
|
||||
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
|
||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||
@ -99,11 +99,12 @@ void PackInstallTask::onDownloadSucceeded()
|
||||
qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
|
||||
jobPtr.reset();
|
||||
|
||||
QJsonParseError parse_error {};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
|
||||
if(parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from ATLauncher at " << parse_error.offset << " reason: " << parse_error.errorString();
|
||||
qWarning() << response;
|
||||
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.get();
|
||||
return;
|
||||
}
|
||||
auto obj = doc.object();
|
||||
@ -352,7 +353,7 @@ QString PackInstallTask::getVersionForLoader(QString uid)
|
||||
if(m_version.loader.recommended || m_version.loader.latest) {
|
||||
for (int i = 0; i < vlist->versions().size(); i++) {
|
||||
auto version = vlist->versions().at(i);
|
||||
auto reqs = version->requires();
|
||||
auto reqs = version->requiredSet();
|
||||
|
||||
// filter by minecraft version, if the loader depends on a certain version.
|
||||
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||
|
@ -40,12 +40,13 @@
|
||||
#include "ATLPackManifest.h"
|
||||
|
||||
#include "InstanceTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "meta/Version.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/PackProfile.h"
|
||||
#include "meta/Version.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace ATLauncher {
|
||||
@ -57,8 +58,7 @@ enum class InstallMode {
|
||||
};
|
||||
|
||||
class UserInteractionSupport {
|
||||
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* Requests a user interaction to select which optional mods should be installed.
|
||||
*/
|
||||
@ -74,23 +74,27 @@ public:
|
||||
* Requests a user interaction to display a message.
|
||||
*/
|
||||
virtual void displayMessage(QString message) = 0;
|
||||
|
||||
virtual ~UserInteractionSupport() = default;
|
||||
};
|
||||
|
||||
class PackInstallTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
class PackInstallTask : public InstanceTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install);
|
||||
virtual ~PackInstallTask(){}
|
||||
public:
|
||||
explicit PackInstallTask(UserInteractionSupport* support,
|
||||
QString packName,
|
||||
QString version,
|
||||
InstallMode installMode = InstallMode::Install);
|
||||
virtual ~PackInstallTask() { delete m_support; }
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
protected:
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
private slots:
|
||||
private slots:
|
||||
void onDownloadSucceeded();
|
||||
void onDownloadFailed(QString reason);
|
||||
void onDownloadAborted();
|
||||
@ -98,7 +102,7 @@ private slots:
|
||||
void onModsDownloaded();
|
||||
void onModsExtracted();
|
||||
|
||||
private:
|
||||
private:
|
||||
QString getDirForModType(ModType type, QString raw);
|
||||
QString getVersionForLoader(QString uid);
|
||||
QString detectLibrary(VersionLibrary library);
|
||||
@ -110,20 +114,18 @@ private:
|
||||
void installConfigs();
|
||||
void extractConfigs();
|
||||
void downloadMods();
|
||||
bool extractMods(
|
||||
const QMap<QString, VersionMod> &toExtract,
|
||||
const QMap<QString, VersionMod> &toDecomp,
|
||||
const QMap<QString, QString> &toCopy
|
||||
);
|
||||
bool extractMods(const QMap<QString, VersionMod>& toExtract,
|
||||
const QMap<QString, VersionMod>& toDecomp,
|
||||
const QMap<QString, QString>& toCopy);
|
||||
void install();
|
||||
|
||||
private:
|
||||
UserInteractionSupport *m_support;
|
||||
private:
|
||||
UserInteractionSupport* m_support;
|
||||
|
||||
bool abortable = false;
|
||||
|
||||
NetJob::Ptr jobPtr;
|
||||
QByteArray response;
|
||||
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
|
||||
|
||||
InstallMode m_install_mode;
|
||||
QString m_pack_name;
|
||||
@ -145,7 +147,6 @@ private:
|
||||
|
||||
QFuture<bool> m_modExtractFuture;
|
||||
QFutureWatcher<bool> m_modExtractFutureWatcher;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace ATLauncher
|
||||
|
@ -25,17 +25,40 @@ void Flame::FileResolvingTask::executeTask()
|
||||
setProgress(0, 3);
|
||||
m_dljob.reset(new NetJob("Mod id resolver", m_network));
|
||||
result.reset(new QByteArray());
|
||||
//build json data to send
|
||||
// build json data to send
|
||||
QJsonObject object;
|
||||
|
||||
object["fileIds"] = QJsonArray::fromVariantList(std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
|
||||
l.push_back(s.fileId);
|
||||
return l;
|
||||
}));
|
||||
object["fileIds"] = QJsonArray::fromVariantList(
|
||||
std::accumulate(m_toProcess.files.begin(), m_toProcess.files.end(), QVariantList(), [](QVariantList& l, const File& s) {
|
||||
l.push_back(s.fileId);
|
||||
return l;
|
||||
}));
|
||||
QByteArray data = Json::toText(object);
|
||||
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result.get(), data);
|
||||
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
|
||||
m_dljob->addNetAction(dl);
|
||||
connect(m_dljob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::netJobFinished);
|
||||
|
||||
auto step_progress = std::make_shared<TaskStepProgress>();
|
||||
connect(m_dljob.get(), &NetJob::finished, this, [this, step_progress]() {
|
||||
step_progress->state = TaskStepState::Succeeded;
|
||||
stepProgress(*step_progress);
|
||||
netJobFinished();
|
||||
});
|
||||
connect(m_dljob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
|
||||
step_progress->state = TaskStepState::Failed;
|
||||
stepProgress(*step_progress);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_dljob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
||||
connect(m_dljob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||
qDebug() << "Resolve slug progress" << current << total;
|
||||
step_progress->update(current, total);
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
connect(m_dljob.get(), &NetJob::status, this, [this, step_progress](QString status) {
|
||||
step_progress->status = status;
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
|
||||
m_dljob->start();
|
||||
}
|
||||
|
||||
@ -44,7 +67,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
setProgress(1, 3);
|
||||
// job to check modrinth for blocked projects
|
||||
m_checkJob.reset(new NetJob("Modrinth check", m_network));
|
||||
blockedProjects = QMap<File *,QByteArray *>();
|
||||
blockedProjects = QMap<File*, std::shared_ptr<QByteArray>>();
|
||||
|
||||
QJsonDocument doc;
|
||||
QJsonArray array;
|
||||
@ -65,24 +88,42 @@ void Flame::FileResolvingTask::netJobFinished()
|
||||
auto fileid = Json::requireInteger(Json::requireObject(file)["id"]);
|
||||
auto& out = m_toProcess.files[fileid];
|
||||
try {
|
||||
out.parseFromObject(Json::requireObject(file));
|
||||
out.parseFromObject(Json::requireObject(file));
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << "Blocked mod on curseforge" << out.fileName;
|
||||
auto hash = out.hash;
|
||||
if(!hash.isEmpty()) {
|
||||
if (!hash.isEmpty()) {
|
||||
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
|
||||
auto output = new QByteArray();
|
||||
auto output = std::make_shared<QByteArray>();
|
||||
auto dl = Net::Download::makeByteArray(QUrl(url), output);
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
|
||||
out.resolved = true;
|
||||
});
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
|
||||
|
||||
m_checkJob->addNetAction(dl);
|
||||
blockedProjects.insert(&out, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(m_checkJob.get(), &NetJob::finished, this, &Flame::FileResolvingTask::modrinthCheckFinished);
|
||||
auto step_progress = std::make_shared<TaskStepProgress>();
|
||||
connect(m_checkJob.get(), &NetJob::finished, this, [this, step_progress]() {
|
||||
step_progress->state = TaskStepState::Succeeded;
|
||||
stepProgress(*step_progress);
|
||||
modrinthCheckFinished();
|
||||
});
|
||||
connect(m_checkJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
|
||||
step_progress->state = TaskStepState::Failed;
|
||||
stepProgress(*step_progress);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_checkJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
||||
connect(m_checkJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||
qDebug() << "Resolve slug progress" << current << total;
|
||||
step_progress->update(current, total);
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
connect(m_checkJob.get(), &NetJob::status, this, [this, step_progress](QString status) {
|
||||
step_progress->status = status;
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
|
||||
m_checkJob->start();
|
||||
}
|
||||
@ -95,7 +136,6 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
||||
auto &out = *it;
|
||||
auto bytes = blockedProjects[out];
|
||||
if (!out->resolved) {
|
||||
delete bytes;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -112,11 +152,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
||||
} else {
|
||||
out->resolved = false;
|
||||
}
|
||||
|
||||
delete bytes;
|
||||
}
|
||||
//copy to an output list and filter out projects found on modrinth
|
||||
auto block = new QList<File *>();
|
||||
auto block = std::make_shared<QList<File*>>();
|
||||
auto it = blockedProjects.keys();
|
||||
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
|
||||
return !f->resolved;
|
||||
@ -124,32 +162,48 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
||||
//Display not found mods early
|
||||
if (!block->empty()) {
|
||||
//blocked mods found, we need the slug for displaying.... we need another job :D !
|
||||
auto slugJob = new NetJob("Slug Job", m_network);
|
||||
auto slugs = QVector<QByteArray>(block->size());
|
||||
auto index = 0;
|
||||
for (auto fileInfo: *block) {
|
||||
auto projectId = fileInfo->projectId;
|
||||
slugs[index] = QByteArray();
|
||||
m_slugJob.reset(new NetJob("Slug Job", m_network));
|
||||
int index = 0;
|
||||
for (auto mod : *block) {
|
||||
auto projectId = mod->projectId;
|
||||
auto output = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
||||
auto dl = Net::Download::makeByteArray(url, &slugs[index]);
|
||||
slugJob->addNetAction(dl);
|
||||
index++;
|
||||
}
|
||||
connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
|
||||
slugJob->deleteLater();
|
||||
auto index = 0;
|
||||
for (const auto &slugResult: slugs) {
|
||||
auto json = QJsonDocument::fromJson(slugResult);
|
||||
auto dl = Net::Download::makeByteArray(url, output);
|
||||
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
||||
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
|
||||
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
|
||||
auto json = QJsonDocument::fromJson(*output);
|
||||
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
|
||||
"websiteUrl");
|
||||
auto mod = block->at(index);
|
||||
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
|
||||
mod->websiteUrl = link;
|
||||
index++;
|
||||
}
|
||||
});
|
||||
m_slugJob->addNetAction(dl);
|
||||
index++;
|
||||
}
|
||||
auto step_progress = std::make_shared<TaskStepProgress>();
|
||||
connect(m_slugJob.get(), &NetJob::succeeded, this, [this, step_progress]() {
|
||||
step_progress->state = TaskStepState::Succeeded;
|
||||
stepProgress(*step_progress);
|
||||
emitSucceeded();
|
||||
});
|
||||
slugJob->start();
|
||||
connect(m_slugJob.get(), &NetJob::failed, this, [this, step_progress](QString reason) {
|
||||
step_progress->state = TaskStepState::Failed;
|
||||
stepProgress(*step_progress);
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_slugJob.get(), &NetJob::stepProgress, this, &FileResolvingTask::propogateStepProgress);
|
||||
connect(m_slugJob.get(), &NetJob::progress, this, [this, step_progress](qint64 current, qint64 total) {
|
||||
qDebug() << "Resolve slug progress" << current << total;
|
||||
step_progress->update(current, total);
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
connect(m_slugJob.get(), &NetJob::status, this, [this, step_progress](QString status) {
|
||||
step_progress->status = status;
|
||||
stepProgress(*step_progress);
|
||||
});
|
||||
|
||||
m_slugJob->start();
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user