Merge branch 'develop' into feat/dont-hide-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
commit
3526d00a23
20
.github/workflows/build.yml
vendored
20
.github/workflows/build.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
qt_version: '6.5.0'
|
qt_version: '6.5.1'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
@ -80,13 +80,13 @@ jobs:
|
|||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: windows
|
qt_host: windows
|
||||||
qt_arch: 'win64_msvc2019_arm64'
|
qt_arch: 'win64_msvc2019_arm64'
|
||||||
qt_version: '6.5.0'
|
qt_version: '6.5.1'
|
||||||
qt_modules: 'qt5compat qtimageformats'
|
qt_modules: 'qt5compat qtimageformats'
|
||||||
qt_tools: ''
|
qt_tools: ''
|
||||||
|
|
||||||
- os: macos-12
|
- os: macos-12
|
||||||
name: macOS
|
name: macOS
|
||||||
macosx_deployment_target: 10.15
|
macosx_deployment_target: 11.0
|
||||||
qt_ver: 6
|
qt_ver: 6
|
||||||
qt_host: mac
|
qt_host: mac
|
||||||
qt_arch: ''
|
qt_arch: ''
|
||||||
@ -375,6 +375,8 @@ jobs:
|
|||||||
shell: msys2 {0}
|
shell: msys2 {0}
|
||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }}
|
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)
|
- name: Package (Windows MSVC)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||||
@ -387,6 +389,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/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
|
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)
|
- name: Fetch codesign certificate (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
@ -411,6 +417,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
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
|
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)
|
- name: Package (Windows MSVC, portable)
|
||||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||||
@ -418,6 +425,8 @@ jobs:
|
|||||||
cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead
|
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
|
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)
|
- name: Package (Windows, installer)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
run: |
|
run: |
|
||||||
@ -437,6 +446,7 @@ jobs:
|
|||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }}
|
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 }}
|
cd ${{ env.INSTALL_DIR }}
|
||||||
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
tar --owner root --group root -czf ../PrismLauncher.tar.gz *
|
||||||
@ -446,6 +456,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable
|
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 }}
|
cd ${{ env.INSTALL_PORTABLE_DIR }}
|
||||||
tar -czf ../PrismLauncher-portable.tar.gz *
|
tar -czf ../PrismLauncher-portable.tar.gz *
|
||||||
@ -587,7 +599,7 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
- name: Install nix
|
- name: Install nix
|
||||||
if: inputs.build_type == 'Debug'
|
if: inputs.build_type == 'Debug'
|
||||||
uses: cachix/install-nix-action@v20
|
uses: cachix/install-nix-action@v21
|
||||||
with:
|
with:
|
||||||
install_url: https://nixos.org/nix/install
|
install_url: https://nixos.org/nix/install
|
||||||
extra_nix_config: |
|
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-Legacy*/PrismLauncher.tar.gz PrismLauncher-macOS-Legacy-${{ env.VERSION }}.tar.gz
|
||||||
mv PrismLauncher-macOS*/PrismLauncher.tar.gz PrismLauncher-macOS-${{ 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
|
for d in PrismLauncher-Windows-MSVC*; do
|
||||||
cd "${d}" || continue
|
cd "${d}" || continue
|
||||||
|
@ -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.")
|
set(Launcher_TRANSLATIONS_URL "https://hosted.weblate.org/projects/prismlauncher/launcher/" CACHE STRING "URL for the translations platform.")
|
||||||
|
|
||||||
# Matrix Space
|
# 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
|
# 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
|
# 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
|
# Builds
|
||||||
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
|
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
|
||||||
@ -345,6 +345,8 @@ elseif(UNIX)
|
|||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION "${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps")
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_mrpack_MIMEInfo} DESTINATION ${KDE_INSTALL_MIMEDIR})
|
||||||
|
|
||||||
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/launcher/qtlogging.ini" DESTINATION "${KDE_INSTALL_DATADIR}/${Launcher_Name}")
|
||||||
|
|
||||||
if(Launcher_ManPage)
|
if(Launcher_ManPage)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_ManPage} DESTINATION "${KDE_INSTALL_MANDIR}/man6")
|
||||||
endif()
|
endif()
|
||||||
@ -372,6 +374,8 @@ else()
|
|||||||
message(FATAL_ERROR "Platform not supported")
|
message(FATAL_ERROR "Platform not supported")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
################################ Included Libs ################################
|
################################ Included Libs ################################
|
||||||
|
|
||||||
include(ExternalProject)
|
include(ExternalProject)
|
||||||
|
@ -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:**
|
- **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:**
|
- **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:**
|
- **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
|
## Translations
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// 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 Jamie Mansfield <jmansfield@cadixdev.org>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* 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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -36,6 +37,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief The Config class holds all the build-time information passed from the build system.
|
* \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_STAGING_URL = "https://staging-api.modrinth.com/v2";
|
||||||
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
|
QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2";
|
||||||
|
QStringList MODRINTH_MRPACK_HOSTS{"cdn.modrinth.com", "github.com", "raw.githubusercontent.com", "gitlab.com"};
|
||||||
|
|
||||||
QString FLAME_BASE_URL = "https://api.curseforge.com/v1";
|
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
|
||||||
|
62
flake.lock
62
flake.lock
@ -16,29 +16,31 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat_2": {
|
"flake-parts": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1673956053,
|
"lastModified": 1683560683,
|
||||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||||
"owner": "edolstra",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-parts",
|
||||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "edolstra",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-parts",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1676283394,
|
"lastModified": 1667395993,
|
||||||
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -86,11 +88,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678693419,
|
"lastModified": 1685012353,
|
||||||
"narHash": "sha256-bbSv5yqZAW6dz+3f3f3pOUZbxpPN+3OgCljgn7P+nnQ=",
|
"narHash": "sha256-U3oOge4cHnav8OLGdRVhL45xoRj4Ppd+It6nPC9nNIU=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "8e3fad82be64c06fbfb9fd43993aec9ef4623936",
|
"rev": "aeb75dba965e790de427b73315d5addf91a54955",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -100,40 +102,44 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-stable": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1673800717,
|
"dir": "lib",
|
||||||
"narHash": "sha256-SFHraUqLSu5cC6IxTprex/nTsI81ZQAtDvlBvGDWfnA=",
|
"lastModified": 1682879489,
|
||||||
|
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2f9fd351ec37f5d479556cd48be4ca340da59b8f",
|
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
"dir": "lib",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-22.11",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pre-commit-hooks": {
|
"pre-commit-hooks": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_2",
|
"flake-compat": [
|
||||||
"flake-utils": [
|
"flake-compat"
|
||||||
"flake-utils"
|
|
||||||
],
|
],
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
"gitignore": "gitignore",
|
"gitignore": "gitignore",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"nixpkgs-stable": "nixpkgs-stable"
|
"nixpkgs-stable": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1678376203,
|
"lastModified": 1684842236,
|
||||||
"narHash": "sha256-3tyYGyC8h7fBwncLZy5nCUjTJPrHbmNwp47LlNLOHSM=",
|
"narHash": "sha256-rYWsIXHvNhVQ15RQlBUv67W3YnM+Pd+DuXGMvCBq2IE=",
|
||||||
"owner": "cachix",
|
"owner": "cachix",
|
||||||
"repo": "pre-commit-hooks.nix",
|
"repo": "pre-commit-hooks.nix",
|
||||||
"rev": "1a20b9708962096ec2481eeb2ddca29ed747770a",
|
"rev": "61e567d6497bc9556f391faebe5e410e6623217f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -145,7 +151,7 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"flake-utils": "flake-utils",
|
"flake-parts": "flake-parts",
|
||||||
"libnbtplusplus": "libnbtplusplus",
|
"libnbtplusplus": "libnbtplusplus",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"pre-commit-hooks": "pre-commit-hooks"
|
"pre-commit-hooks": "pre-commit-hooks"
|
||||||
|
78
flake.nix
78
flake.nix
@ -3,11 +3,12 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
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 = {
|
pre-commit-hooks = {
|
||||||
url = "github:cachix/pre-commit-hooks.nix";
|
url = "github:cachix/pre-commit-hooks.nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.flake-utils.follows = "flake-utils";
|
inputs.nixpkgs-stable.follows = "nixpkgs";
|
||||||
|
inputs.flake-compat.follows = "flake-compat";
|
||||||
};
|
};
|
||||||
flake-compat = {
|
flake-compat = {
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
@ -19,73 +20,8 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = inputs:
|
||||||
self,
|
inputs.flake-parts.lib.mkFlake
|
||||||
nixpkgs,
|
{inherit inputs;}
|
||||||
flake-utils,
|
{imports = [./nix];};
|
||||||
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);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
* Copyright (C) 2022 Tayou <tayou@gmx.net>
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -46,6 +47,7 @@
|
|||||||
#include "net/PasteUpload.h"
|
#include "net/PasteUpload.h"
|
||||||
#include "pathmatcher/MultiMatcher.h"
|
#include "pathmatcher/MultiMatcher.h"
|
||||||
#include "pathmatcher/SimplePrefixMatcher.h"
|
#include "pathmatcher/SimplePrefixMatcher.h"
|
||||||
|
#include "settings/INIFile.h"
|
||||||
#include "ui/MainWindow.h"
|
#include "ui/MainWindow.h"
|
||||||
#include "ui/InstanceWindow.h"
|
#include "ui/InstanceWindow.h"
|
||||||
|
|
||||||
@ -286,6 +288,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
|
||||||
dataPath = m_rootPath;
|
dataPath = m_rootPath;
|
||||||
adjustedBy = "Portable data path";
|
adjustedBy = "Portable data path";
|
||||||
|
m_portable = true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -411,6 +414,47 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||||||
"%{if-category}[%{category}]: %{endif}"
|
"%{if-category}[%{category}]: %{endif}"
|
||||||
"%{message}");
|
"%{message}");
|
||||||
|
|
||||||
|
bool foundLoggingRules = false;
|
||||||
|
|
||||||
|
auto logRulesFile = QStringLiteral("qtlogging.ini");
|
||||||
|
auto logRulesPath = FS::PathCombine(dataPath, logRulesFile);
|
||||||
|
|
||||||
|
qDebug() << "Testing" << logRulesPath << "...";
|
||||||
|
foundLoggingRules = QFile::exists(logRulesPath);
|
||||||
|
|
||||||
|
// search the dataPath()
|
||||||
|
// seach app data standard path
|
||||||
|
if(!foundLoggingRules && !isPortable() && dirParam.isEmpty()) {
|
||||||
|
logRulesPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, FS::PathCombine("..", logRulesFile));
|
||||||
|
if(!logRulesPath.isEmpty()) {
|
||||||
|
qDebug() << "Found" << logRulesPath << "...";
|
||||||
|
foundLoggingRules = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// seach root path
|
||||||
|
if(!foundLoggingRules) {
|
||||||
|
logRulesPath = FS::PathCombine(m_rootPath, logRulesFile);
|
||||||
|
qDebug() << "Testing" << logRulesPath << "...";
|
||||||
|
foundLoggingRules = QFile::exists(logRulesPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(foundLoggingRules) {
|
||||||
|
// load and set logging rules
|
||||||
|
qDebug() << "Loading logging rules from:" << logRulesPath;
|
||||||
|
QSettings loggingRules(logRulesPath, QSettings::IniFormat);
|
||||||
|
loggingRules.beginGroup("Rules");
|
||||||
|
QStringList rule_names = loggingRules.childKeys();
|
||||||
|
QStringList rules;
|
||||||
|
qDebug() << "Setting log rules:";
|
||||||
|
for (auto rule_name : rule_names) {
|
||||||
|
auto rule = QString("%1=%2").arg(rule_name).arg(loggingRules.value(rule_name).toString());
|
||||||
|
rules.append(rule);
|
||||||
|
qDebug() << " " << rule;
|
||||||
|
}
|
||||||
|
auto rules_str = rules.join("\n");
|
||||||
|
QLoggingCategory::setFilterRules(rules_str);
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "<> Log initialized.";
|
qDebug() << "<> Log initialized.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +187,10 @@ public:
|
|||||||
return m_rootPath;
|
return m_rootPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPortable() {
|
||||||
|
return m_portable;
|
||||||
|
}
|
||||||
|
|
||||||
const Capabilities capabilities() {
|
const Capabilities capabilities() {
|
||||||
return m_capabilities;
|
return m_capabilities;
|
||||||
}
|
}
|
||||||
@ -275,6 +279,7 @@ private:
|
|||||||
QString m_rootPath;
|
QString m_rootPath;
|
||||||
Status m_status = Application::StartingUp;
|
Status m_status = Application::StartingUp;
|
||||||
Capabilities m_capabilities;
|
Capabilities m_capabilities;
|
||||||
|
bool m_portable = false;
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
|
Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive;
|
||||||
|
@ -124,6 +124,8 @@ set(NET_SOURCES
|
|||||||
net/HttpMetaCache.h
|
net/HttpMetaCache.h
|
||||||
net/MetaCacheSink.cpp
|
net/MetaCacheSink.cpp
|
||||||
net/MetaCacheSink.h
|
net/MetaCacheSink.h
|
||||||
|
net/Logging.h
|
||||||
|
net/Logging.cpp
|
||||||
net/NetAction.h
|
net/NetAction.h
|
||||||
net/NetJob.cpp
|
net/NetJob.cpp
|
||||||
net/NetJob.h
|
net/NetJob.h
|
||||||
@ -523,6 +525,8 @@ set(MODRINTH_SOURCES
|
|||||||
modplatform/modrinth/ModrinthCheckUpdate.h
|
modplatform/modrinth/ModrinthCheckUpdate.h
|
||||||
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
|
modplatform/modrinth/ModrinthInstanceCreationTask.cpp
|
||||||
modplatform/modrinth/ModrinthInstanceCreationTask.h
|
modplatform/modrinth/ModrinthInstanceCreationTask.h
|
||||||
|
modplatform/modrinth/ModrinthPackExportTask.cpp
|
||||||
|
modplatform/modrinth/ModrinthPackExportTask.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PACKWIZ_SOURCES
|
set(PACKWIZ_SOURCES
|
||||||
@ -576,6 +580,55 @@ ecm_qt_declare_logging_category(CORE_SOURCES
|
|||||||
EXPORT "${Launcher_Name}"
|
EXPORT "${Launcher_Name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskLogC
|
||||||
|
CATEGORY_NAME "launcher.task"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "Task actions"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskNetLogC
|
||||||
|
CATEGORY_NAME "launcher.task.net"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "task network action"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskDownloadLogC
|
||||||
|
CATEGORY_NAME "launcher.task.net.download"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "task network download actions"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskUploadLogC
|
||||||
|
CATEGORY_NAME "launcher.task.net.upload"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "task network upload actions"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskMetaCacheLogC
|
||||||
|
CATEGORY_NAME "launcher.task.net.metacache"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "task network meta-cache actions"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_qt_export_logging_category(
|
||||||
|
IDENTIFIER taskHttpMetaCacheLogC
|
||||||
|
CATEGORY_NAME "launcher.task.net.metacache.http"
|
||||||
|
DEFAULT_SEVERITY Debug
|
||||||
|
DESCRIPTION "task network http meta-cache actions"
|
||||||
|
EXPORT "${Launcher_Name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
|
if(KDE_INSTALL_LOGGINGCATEGORIESDIR) # only install if there is a standard path for this
|
||||||
ecm_qt_install_logging_categories(
|
ecm_qt_install_logging_categories(
|
||||||
EXPORT "${Launcher_Name}"
|
EXPORT "${Launcher_Name}"
|
||||||
@ -669,6 +722,10 @@ SET(LAUNCHER_SOURCES
|
|||||||
# FIXME: maybe find a better home for this.
|
# FIXME: maybe find a better home for this.
|
||||||
SkinUtils.cpp
|
SkinUtils.cpp
|
||||||
SkinUtils.h
|
SkinUtils.h
|
||||||
|
FileIgnoreProxy.cpp
|
||||||
|
FileIgnoreProxy.h
|
||||||
|
FastFileIconProvider.cpp
|
||||||
|
FastFileIconProvider.h
|
||||||
|
|
||||||
# GUI - setup wizard
|
# GUI - setup wizard
|
||||||
ui/setupwizard/SetupWizard.h
|
ui/setupwizard/SetupWizard.h
|
||||||
@ -849,6 +906,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/dialogs/EditAccountDialog.h
|
ui/dialogs/EditAccountDialog.h
|
||||||
ui/dialogs/ExportInstanceDialog.cpp
|
ui/dialogs/ExportInstanceDialog.cpp
|
||||||
ui/dialogs/ExportInstanceDialog.h
|
ui/dialogs/ExportInstanceDialog.h
|
||||||
|
ui/dialogs/ExportMrPackDialog.cpp
|
||||||
|
ui/dialogs/ExportMrPackDialog.h
|
||||||
ui/dialogs/IconPickerDialog.cpp
|
ui/dialogs/IconPickerDialog.cpp
|
||||||
ui/dialogs/IconPickerDialog.h
|
ui/dialogs/IconPickerDialog.h
|
||||||
ui/dialogs/ImportResourceDialog.cpp
|
ui/dialogs/ImportResourceDialog.cpp
|
||||||
@ -922,6 +981,8 @@ SET(LAUNCHER_SOURCES
|
|||||||
ui/widgets/VariableSizedImageObject.cpp
|
ui/widgets/VariableSizedImageObject.cpp
|
||||||
ui/widgets/ProjectItem.h
|
ui/widgets/ProjectItem.h
|
||||||
ui/widgets/ProjectItem.cpp
|
ui/widgets/ProjectItem.cpp
|
||||||
|
ui/widgets/SubTaskProgressBar.h
|
||||||
|
ui/widgets/SubTaskProgressBar.cpp
|
||||||
ui/widgets/VersionListView.cpp
|
ui/widgets/VersionListView.cpp
|
||||||
ui/widgets/VersionListView.h
|
ui/widgets/VersionListView.h
|
||||||
ui/widgets/VersionSelectWidget.cpp
|
ui/widgets/VersionSelectWidget.cpp
|
||||||
@ -982,6 +1043,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/widgets/CustomCommands.ui
|
ui/widgets/CustomCommands.ui
|
||||||
ui/widgets/InfoFrame.ui
|
ui/widgets/InfoFrame.ui
|
||||||
ui/widgets/ModFilterWidget.ui
|
ui/widgets/ModFilterWidget.ui
|
||||||
|
ui/widgets/SubTaskProgressBar.ui
|
||||||
ui/widgets/ThemeCustomizationWidget.ui
|
ui/widgets/ThemeCustomizationWidget.ui
|
||||||
ui/dialogs/CopyInstanceDialog.ui
|
ui/dialogs/CopyInstanceDialog.ui
|
||||||
ui/dialogs/ProfileSetupDialog.ui
|
ui/dialogs/ProfileSetupDialog.ui
|
||||||
@ -992,6 +1054,7 @@ qt_wrap_ui(LAUNCHER_UI
|
|||||||
ui/dialogs/ProfileSelectDialog.ui
|
ui/dialogs/ProfileSelectDialog.ui
|
||||||
ui/dialogs/SkinUploadDialog.ui
|
ui/dialogs/SkinUploadDialog.ui
|
||||||
ui/dialogs/ExportInstanceDialog.ui
|
ui/dialogs/ExportInstanceDialog.ui
|
||||||
|
ui/dialogs/ExportMrPackDialog.ui
|
||||||
ui/dialogs/IconPickerDialog.ui
|
ui/dialogs/IconPickerDialog.ui
|
||||||
ui/dialogs/ImportResourceDialog.ui
|
ui/dialogs/ImportResourceDialog.ui
|
||||||
ui/dialogs/MSALoginDialog.ui
|
ui/dialogs/MSALoginDialog.ui
|
||||||
@ -1155,6 +1218,12 @@ if(INSTALL_BUNDLE STREQUAL "full")
|
|||||||
CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
|
CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
|
||||||
COMPONENT Runtime
|
COMPONENT Runtime
|
||||||
)
|
)
|
||||||
|
# add qtlogging.ini as a config file
|
||||||
|
install(
|
||||||
|
FILES "qtlogging.ini"
|
||||||
|
DESTINATION ${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}
|
||||||
|
COMPONENT Runtime
|
||||||
|
)
|
||||||
# Bundle plugins
|
# Bundle plugins
|
||||||
# Image formats
|
# Image formats
|
||||||
install(
|
install(
|
||||||
|
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;
|
||||||
|
};
|
256
launcher/FileIgnoreProxy.cpp
Normal file
256
launcher/FileIgnoreProxy.cpp
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
// 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 "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;
|
||||||
|
}
|
72
launcher/FileIgnoreProxy.h
Normal file
72
launcher/FileIgnoreProxy.h
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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 <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; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QString root;
|
||||||
|
SeparatorPrefixTree<'/'> blocked;
|
||||||
|
};
|
@ -39,7 +39,16 @@ void InstanceCopyTask::executeTask()
|
|||||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||||
|
|
||||||
auto copySaves = [&]() {
|
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);
|
savesCopy.followSymlinks(true);
|
||||||
|
|
||||||
return savesCopy();
|
return savesCopy();
|
||||||
@ -123,6 +132,7 @@ void InstanceCopyTask::copyFinished()
|
|||||||
emitFailed(tr("Instance folder copy failed."));
|
emitFailed(tr("Instance folder copy failed."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: shouldn't this be able to report errors?
|
// FIXME: shouldn't this be able to report errors?
|
||||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||||
|
|
||||||
@ -134,6 +144,24 @@ void InstanceCopyTask::copyFinished()
|
|||||||
}
|
}
|
||||||
if (m_useLinks)
|
if (m_useLinks)
|
||||||
inst->addLinkedInstanceId(m_origInstance->id());
|
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();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class InstanceCreationTask : public InstanceTask {
|
|||||||
QString getError() const { return m_error_message; }
|
QString getError() const { return m_error_message; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setError(QString message) { m_error_message = message; };
|
void setError(const QString& message) { m_error_message = message; };
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool m_abort = false;
|
bool m_abort = false;
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "MMCZip.h"
|
#include "MMCZip.h"
|
||||||
#include "NullInstance.h"
|
#include "NullInstance.h"
|
||||||
|
|
||||||
|
#include "QObjectPtr.h"
|
||||||
#include "icons/IconList.h"
|
#include "icons/IconList.h"
|
||||||
#include "icons/IconUtils.h"
|
#include "icons/IconUtils.h"
|
||||||
|
|
||||||
@ -98,6 +99,7 @@ void InstanceImportTask::executeTask()
|
|||||||
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
||||||
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
|
||||||
|
|
||||||
@ -259,7 +261,7 @@ void InstanceImportTask::extractFinished()
|
|||||||
|
|
||||||
void InstanceImportTask::processFlame()
|
void InstanceImportTask::processFlame()
|
||||||
{
|
{
|
||||||
FlameCreationTask* inst_creation_task = nullptr;
|
shared_qobject_ptr<FlameCreationTask> inst_creation_task = nullptr;
|
||||||
if (!m_extra_info.isEmpty()) {
|
if (!m_extra_info.isEmpty()) {
|
||||||
auto pack_id_it = m_extra_info.constFind("pack_id");
|
auto pack_id_it = m_extra_info.constFind("pack_id");
|
||||||
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
Q_ASSERT(pack_id_it != m_extra_info.constEnd());
|
||||||
@ -274,10 +276,10 @@ void InstanceImportTask::processFlame()
|
|||||||
if (original_instance_id_it != m_extra_info.constEnd())
|
if (original_instance_id_it != m_extra_info.constEnd())
|
||||||
original_instance_id = original_instance_id_it.value();
|
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 {
|
} else {
|
||||||
// FIXME: Find a way to get IDs in directly imported ZIPs
|
// 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);
|
inst_creation_task->setName(*this);
|
||||||
@ -285,18 +287,19 @@ void InstanceImportTask::processFlame()
|
|||||||
inst_creation_task->setGroup(m_instGroup);
|
inst_creation_task->setGroup(m_instGroup);
|
||||||
inst_creation_task->setConfirmUpdate(shouldConfirmUpdate());
|
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());
|
setOverride(inst_creation_task->shouldOverride(), inst_creation_task->originalInstanceID());
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task.get(), &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
connect(inst_creation_task.get(), &Task::progress, this, &InstanceImportTask::setProgress);
|
||||||
connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus);
|
connect(inst_creation_task.get(), &Task::stepProgress, this, &InstanceImportTask::propogateStepProgress);
|
||||||
connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
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(this, &Task::aborted, inst_creation_task.get(), &InstanceCreationTask::abort);
|
||||||
connect(inst_creation_task, &Task::aborted, this, &Task::abort);
|
connect(inst_creation_task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable);
|
connect(inst_creation_task.get(), &Task::abortStatusChanged, this, &Task::setAbortable);
|
||||||
|
|
||||||
inst_creation_task->start();
|
inst_creation_task->start();
|
||||||
}
|
}
|
||||||
@ -382,7 +385,9 @@ void InstanceImportTask::processModrinth()
|
|||||||
});
|
});
|
||||||
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed);
|
||||||
connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress);
|
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::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, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater);
|
||||||
|
|
||||||
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort);
|
||||||
|
@ -797,7 +797,9 @@ class InstanceStaging : public Task {
|
|||||||
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
|
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
|
||||||
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
|
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
|
||||||
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
||||||
|
connect(child, &Task::details, this, &InstanceStaging::setDetails);
|
||||||
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
||||||
|
connect(child, &Task::stepProgress, this, &InstanceStaging::propogateStepProgress);
|
||||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,8 +122,7 @@ void JavaCommon::TestCheck::run()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker());
|
checker.reset(new JavaChecker());
|
||||||
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinished);
|
||||||
SLOT(checkFinished(JavaCheckResult)));
|
|
||||||
checker->m_path = m_path;
|
checker->m_path = m_path;
|
||||||
checker->performCheck();
|
checker->performCheck();
|
||||||
}
|
}
|
||||||
@ -137,8 +136,7 @@ void JavaCommon::TestCheck::checkFinished(JavaCheckResult result)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checker.reset(new JavaChecker());
|
checker.reset(new JavaChecker());
|
||||||
connect(checker.get(), SIGNAL(checkFinished(JavaCheckResult)), this,
|
connect(checker.get(), &JavaChecker::checkFinished, this, &JavaCommon::TestCheck::checkFinishedWithArgs);
|
||||||
SLOT(checkFinishedWithArgs(JavaCheckResult)));
|
|
||||||
checker->m_path = m_path;
|
checker->m_path = m_path;
|
||||||
checker->m_args = m_args;
|
checker->m_args = m_args;
|
||||||
checker->m_minMem = m_minMem;
|
checker->m_minMem = m_minMem;
|
||||||
|
@ -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.
|
// QProcess has a strange interface... let's map a lot of those into a few.
|
||||||
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
||||||
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
|
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
|
||||||
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
|
connect(this, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &LoggedProcess::on_exit);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
connect(this, &QProcess::errorOccurred, this, &LoggedProcess::on_error);
|
||||||
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, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
|
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
#include <MMCTime.h>
|
#include <MMCTime.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
QString Time::prettifyDuration(int64_t duration) {
|
QString Time::prettifyDuration(int64_t duration) {
|
||||||
int seconds = (int) (duration % 60);
|
int seconds = (int) (duration % 60);
|
||||||
@ -36,3 +38,65 @@ QString Time::prettifyDuration(int64_t duration) {
|
|||||||
}
|
}
|
||||||
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
|
return QObject::tr("%1d %2h %3min").arg(days).arg(hours).arg(minutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Time::humanReadableDuration(double duration, int precision) {
|
||||||
|
|
||||||
|
using days = std::chrono::duration<int, std::ratio<86400>>;
|
||||||
|
|
||||||
|
QString outStr;
|
||||||
|
QTextStream os(&outStr);
|
||||||
|
|
||||||
|
bool neg = false;
|
||||||
|
if (duration < 0) {
|
||||||
|
neg = true; // flag
|
||||||
|
duration *= -1; // invert
|
||||||
|
}
|
||||||
|
|
||||||
|
auto std_duration = std::chrono::duration<double>(duration);
|
||||||
|
auto d = std::chrono::duration_cast<days>(std_duration);
|
||||||
|
std_duration -= d;
|
||||||
|
auto h = std::chrono::duration_cast<std::chrono::hours>(std_duration);
|
||||||
|
std_duration -= h;
|
||||||
|
auto m = std::chrono::duration_cast<std::chrono::minutes>(std_duration);
|
||||||
|
std_duration -= m;
|
||||||
|
auto s = std::chrono::duration_cast<std::chrono::seconds>(std_duration);
|
||||||
|
std_duration -= s;
|
||||||
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std_duration);
|
||||||
|
|
||||||
|
auto dc = d.count();
|
||||||
|
auto hc = h.count();
|
||||||
|
auto mc = m.count();
|
||||||
|
auto sc = s.count();
|
||||||
|
auto msc = ms.count();
|
||||||
|
|
||||||
|
if (neg) {
|
||||||
|
os << '-';
|
||||||
|
}
|
||||||
|
if (dc) {
|
||||||
|
os << dc << QObject::tr("days");
|
||||||
|
}
|
||||||
|
if (hc) {
|
||||||
|
if (dc)
|
||||||
|
os << " ";
|
||||||
|
os << qSetFieldWidth(2) << hc << QObject::tr("h"); // hours
|
||||||
|
}
|
||||||
|
if (mc) {
|
||||||
|
if (dc || hc)
|
||||||
|
os << " ";
|
||||||
|
os << qSetFieldWidth(2) << mc << QObject::tr("m"); // minutes
|
||||||
|
}
|
||||||
|
if (dc || hc || mc || sc) {
|
||||||
|
if (dc || hc || mc)
|
||||||
|
os << " ";
|
||||||
|
os << qSetFieldWidth(2) << sc << QObject::tr("s"); // seconds
|
||||||
|
}
|
||||||
|
if ((msc && (precision > 0)) || !(dc || hc || mc || sc)) {
|
||||||
|
if (dc || hc || mc || sc)
|
||||||
|
os << " ";
|
||||||
|
os << qSetFieldWidth(0) << qSetRealNumberPrecision(precision) << msc << QObject::tr("ms"); // miliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
os.flush();
|
||||||
|
|
||||||
|
return outStr;
|
||||||
|
}
|
@ -22,4 +22,13 @@ namespace Time {
|
|||||||
|
|
||||||
QString prettifyDuration(int64_t duration);
|
QString prettifyDuration(int64_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
|
||||||
|
* miliseconds are only included if `precision` is greater than 0.
|
||||||
|
*
|
||||||
|
* @param duration a number of seconds as floating point
|
||||||
|
* @param precision number of decmial points to display on fractons of a second, defualts to 0.
|
||||||
|
* @return QString
|
||||||
|
*/
|
||||||
|
QString humanReadableDuration(double duration, int precision = 0);
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, version 3.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ResourceDownloadTask.h"
|
#include "ResourceDownloadTask.h"
|
||||||
|
|
||||||
@ -24,14 +24,15 @@
|
|||||||
#include "minecraft/mod/ModFolderModel.h"
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
#include "minecraft/mod/ResourceFolderModel.h"
|
#include "minecraft/mod/ResourceFolderModel.h"
|
||||||
|
|
||||||
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
|
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
||||||
ModPlatform::IndexedVersion version,
|
ModPlatform::IndexedVersion version,
|
||||||
const std::shared_ptr<ResourceFolderModel> packs,
|
const std::shared_ptr<ResourceFolderModel> packs,
|
||||||
bool is_indexed)
|
bool is_indexed,
|
||||||
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs)
|
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) {
|
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);
|
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
|
||||||
|
|
||||||
addTask(m_update_task);
|
addTask(m_update_task);
|
||||||
@ -40,19 +41,20 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack pack,
|
|||||||
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
|
m_filesNetJob.reset(new NetJob(tr("Resource download"), APPLICATION->network()));
|
||||||
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
|
m_filesNetJob->setStatus(tr("Downloading resource:\n%1").arg(m_pack_version.downloadUrl));
|
||||||
|
|
||||||
QDir dir { m_pack_model->dir() };
|
QDir dir{ m_pack_model->dir() };
|
||||||
{
|
{
|
||||||
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
|
// FIXME: Make this more generic. May require adding additional info to IndexedVersion,
|
||||||
// or adquiring a reference to the base instance.
|
// 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.cdUp();
|
||||||
dir.cd(m_pack_version.custom_target_folder);
|
dir.cd(m_custom_target_folder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propogateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &ResourceDownloadTask::downloadFailed);
|
||||||
|
|
||||||
addTask(m_filesNetJob);
|
addTask(m_filesNetJob);
|
||||||
|
@ -1,44 +1,51 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* Prism Launcher - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022-2023 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, version 3.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "net/NetJob.h"
|
#include "net/NetJob.h"
|
||||||
#include "tasks/SequentialTask.h"
|
#include "tasks/SequentialTask.h"
|
||||||
|
|
||||||
#include "modplatform/ModIndex.h"
|
|
||||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||||
|
#include "modplatform/ModIndex.h"
|
||||||
|
|
||||||
class ResourceFolderModel;
|
class ResourceFolderModel;
|
||||||
|
|
||||||
class ResourceDownloadTask : public SequentialTask {
|
class ResourceDownloadTask : public SequentialTask {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ResourceDownloadTask(ModPlatform::IndexedPack pack, ModPlatform::IndexedVersion version, const std::shared_ptr<ResourceFolderModel> packs, bool is_indexed = true);
|
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& 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 QVariant& getVersionID() const { return m_pack_version.fileId; }
|
||||||
|
const QString& getName() const { return m_pack->name; }
|
||||||
|
ModPlatform::IndexedPack::Ptr getPack() { return m_pack; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModPlatform::IndexedPack m_pack;
|
ModPlatform::IndexedPack::Ptr m_pack;
|
||||||
ModPlatform::IndexedVersion m_pack_version;
|
ModPlatform::IndexedVersion m_pack_version;
|
||||||
const std::shared_ptr<ResourceFolderModel> m_pack_model;
|
const std::shared_ptr<ResourceFolderModel> m_pack_model;
|
||||||
|
QString m_custom_target_folder;
|
||||||
|
|
||||||
NetJob::Ptr m_filesNetJob;
|
NetJob::Ptr m_filesNetJob;
|
||||||
LocalModUpdateTask::Ptr m_update_task;
|
LocalModUpdateTask::Ptr m_update_task;
|
||||||
@ -47,11 +54,8 @@ private:
|
|||||||
void downloadFailed(QString reason);
|
void downloadFailed(QString reason);
|
||||||
void downloadSucceeded();
|
void downloadSucceeded();
|
||||||
|
|
||||||
std::tuple<QString, QString> to_delete {"", ""};
|
std::tuple<QString, QString> to_delete{ "", "" };
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void hasOldResource(QString name, QString filename);
|
void hasOldResource(QString name, QString filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@
|
|||||||
|
|
||||||
#include "StringUtils.h"
|
#include "StringUtils.h"
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
/// If you're wondering where these came from exactly, then know you're not the only one =D
|
/// If you're wondering where these came from exactly, then know you're not the only one =D
|
||||||
|
|
||||||
@ -113,6 +115,69 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
|
|||||||
return QString::compare(s1, s2, cs);
|
return QString::compare(s1, s2, cs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString StringUtils::truncateUrlHumanFriendly(QUrl& url, int max_len, bool hard_limit)
|
||||||
|
{
|
||||||
|
auto display_options = QUrl::RemoveUserInfo | QUrl::RemoveFragment | QUrl::NormalizePathSegments;
|
||||||
|
auto str_url = url.toDisplayString(display_options);
|
||||||
|
|
||||||
|
if (str_url.length() <= max_len)
|
||||||
|
return str_url;
|
||||||
|
|
||||||
|
auto url_path_parts = url.path().split('/');
|
||||||
|
QString last_path_segment = url_path_parts.takeLast();
|
||||||
|
|
||||||
|
if (url_path_parts.size() >= 1 && url_path_parts.first().isEmpty())
|
||||||
|
url_path_parts.removeFirst(); // drop empty first segment (from leading / )
|
||||||
|
|
||||||
|
if (url_path_parts.size() >= 1)
|
||||||
|
url_path_parts.removeLast(); // drop the next to last path segment
|
||||||
|
|
||||||
|
auto url_template = QStringLiteral("%1://%2/%3%4");
|
||||||
|
|
||||||
|
auto url_compact = url_path_parts.isEmpty()
|
||||||
|
? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query())
|
||||||
|
: url_template.arg(url.scheme(), url.host(),
|
||||||
|
QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query());
|
||||||
|
|
||||||
|
// remove url parts one by one if it's still too long
|
||||||
|
while (url_compact.length() > max_len && url_path_parts.size() >= 1) {
|
||||||
|
url_path_parts.removeLast(); // drop the next to last path segment
|
||||||
|
url_compact = url_path_parts.isEmpty()
|
||||||
|
? url_template.arg(url.scheme(), url.host(), QStringList({ "...", last_path_segment }).join('/'), url.query())
|
||||||
|
: url_template.arg(url.scheme(), url.host(),
|
||||||
|
QStringList({ url_path_parts.join('/'), "...", last_path_segment }).join('/'), url.query());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((url_compact.length() >= max_len) && hard_limit) {
|
||||||
|
// still too long, truncate normaly
|
||||||
|
url_compact = QString(str_url);
|
||||||
|
auto to_remove = url_compact.length() - max_len + 3;
|
||||||
|
url_compact.remove(url_compact.length() - to_remove - 1, to_remove);
|
||||||
|
url_compact.append("...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return url_compact;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QStringList s_units_si{ "KB", "MB", "GB", "TB" };
|
||||||
|
static const QStringList s_units_kibi{ "KiB", "MiB", "GiB", "TiB" };
|
||||||
|
|
||||||
|
QString StringUtils::humanReadableFileSize(double bytes, bool use_si, int decimal_points)
|
||||||
|
{
|
||||||
|
const QStringList units = use_si ? s_units_si : s_units_kibi;
|
||||||
|
const int scale = use_si ? 1000 : 1024;
|
||||||
|
|
||||||
|
int u = -1;
|
||||||
|
double r = pow(10, decimal_points);
|
||||||
|
|
||||||
|
do {
|
||||||
|
bytes /= scale;
|
||||||
|
u++;
|
||||||
|
} while (round(abs(bytes) * r) / r >= scale && u < units.length() - 1);
|
||||||
|
|
||||||
|
return QString::number(bytes, 'f', 2) + " " + units[u];
|
||||||
|
}
|
||||||
|
|
||||||
QString StringUtils::getRandomAlphaNumeric()
|
QString StringUtils::getRandomAlphaNumeric()
|
||||||
{
|
{
|
||||||
return QUuid::createUuid().toString(QUuid::Id128);
|
return QUuid::createUuid().toString(QUuid::Id128);
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
namespace StringUtils {
|
namespace StringUtils {
|
||||||
|
|
||||||
@ -66,5 +67,16 @@ inline QString fromStdString(string s)
|
|||||||
|
|
||||||
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
|
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Truncate a url while keeping its readability py placing the `...` in the middle of the path
|
||||||
|
* @param url Url to truncate
|
||||||
|
* @param max_len max lenght of url in charaters
|
||||||
|
* @param hard_limit if truncating the path can't get the url short enough, truncate it normaly.
|
||||||
|
*/
|
||||||
|
QString truncateUrlHumanFriendly(QUrl &url, int max_len, bool hard_limit = false);
|
||||||
|
|
||||||
|
QString humanReadableFileSize(double bytes, bool use_si = false, int decimal_points = 1);
|
||||||
|
|
||||||
|
|
||||||
QString getRandomAlphaNumeric();
|
QString getRandomAlphaNumeric();
|
||||||
} // namespace StringUtils
|
} // namespace StringUtils
|
||||||
|
@ -66,9 +66,8 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren
|
|||||||
|
|
||||||
m_watcher.reset(new QFileSystemWatcher());
|
m_watcher.reset(new QFileSystemWatcher());
|
||||||
is_watching = false;
|
is_watching = false;
|
||||||
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
|
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
|
||||||
SLOT(directoryChanged(QString)));
|
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
|
||||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
|
||||||
|
|
||||||
directoryChanged(path);
|
directoryChanged(path);
|
||||||
|
|
||||||
|
@ -87,15 +87,11 @@ void JavaChecker::performCheck()
|
|||||||
process->setProcessEnvironment(CleanEnviroment());
|
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)));
|
connect(process.get(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &JavaChecker::finished);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
connect(process.get(), &QProcess::errorOccurred, this, &JavaChecker::error);
|
||||||
connect(process.get(), SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
connect(process.get(), &QProcess::readyReadStandardOutput, this, &JavaChecker::stdoutReady);
|
||||||
#else
|
connect(process.get(), &QProcess::readyReadStandardError, this, &JavaChecker::stderrReady);
|
||||||
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
connect(&killTimer, &QTimer::timeout, this, &JavaChecker::timeout);
|
||||||
#endif
|
|
||||||
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
|
|
||||||
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
|
|
||||||
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
|
|
||||||
killTimer.setSingleShot(true);
|
killTimer.setSingleShot(true);
|
||||||
killTimer.start(15000);
|
killTimer.start(15000);
|
||||||
process->start();
|
process->start();
|
||||||
|
@ -38,7 +38,7 @@ void JavaCheckerJob::executeTask()
|
|||||||
for (auto iter : javacheckers)
|
for (auto iter : javacheckers)
|
||||||
{
|
{
|
||||||
javaresults.append(JavaCheckResult());
|
javaresults.append(JavaCheckResult());
|
||||||
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
connect(iter.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
|
||||||
iter->performCheck();
|
iter->performCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,11 @@ void Update::executeTask()
|
|||||||
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
|
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
|
||||||
if(m_updateTask)
|
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, &Task::setProgress);
|
connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress);
|
||||||
connect(m_updateTask.get(), &Task::status, this, &Task::setStatus);
|
connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propogateStepProgress);
|
||||||
|
connect(m_updateTask.get(), &Task::status, this, &Update::setStatus);
|
||||||
|
connect(m_updateTask.get(), &Task::details, this, &Update::setDetails);
|
||||||
emit progressReportingRequest();
|
emit progressReportingRequest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -56,10 +56,10 @@ static Version::Ptr parseCommonVersion(const QString &uid, const QJsonObject &ob
|
|||||||
version->setType(ensureString(obj, "type", QString()));
|
version->setType(ensureString(obj, "type", QString()));
|
||||||
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
||||||
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
|
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
|
||||||
RequireSet requires, conflicts;
|
RequireSet reqs, conflicts;
|
||||||
parseRequires(obj, &requires, "requires");
|
parseRequires(obj, &reqs, "requires");
|
||||||
parseRequires(obj, &conflicts, "conflicts");
|
parseRequires(obj, &conflicts, "conflicts");
|
||||||
version->setRequires(requires, conflicts);
|
version->setRequires(reqs, conflicts);
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,6 @@ void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName
|
|||||||
{
|
{
|
||||||
if(obj.contains(keyName))
|
if(obj.contains(keyName))
|
||||||
{
|
{
|
||||||
QSet<QString> requires;
|
|
||||||
auto reqArray = requireArray(obj, keyName);
|
auto reqArray = requireArray(obj, keyName);
|
||||||
auto iter = reqArray.begin();
|
auto iter = reqArray.begin();
|
||||||
while(iter != reqArray.end())
|
while(iter != reqArray.end())
|
||||||
|
@ -116,9 +116,9 @@ void Meta::Version::setTime(const qint64 time)
|
|||||||
emit timeChanged();
|
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;
|
m_conflicts = conflicts;
|
||||||
emit requiresChanged();
|
emit requiresChanged();
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ public:
|
|||||||
{
|
{
|
||||||
return m_time;
|
return m_time;
|
||||||
}
|
}
|
||||||
const Meta::RequireSet &requires() const
|
const Meta::RequireSet &requiredSet() const
|
||||||
{
|
{
|
||||||
return m_requires;
|
return m_requires;
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ public:
|
|||||||
public: // for usage by format parsers only
|
public: // for usage by format parsers only
|
||||||
void setType(const QString &type);
|
void setType(const QString &type);
|
||||||
void setTime(const qint64 time);
|
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 setVolatile(bool volatile_);
|
||||||
void setRecommended(bool recommended);
|
void setRecommended(bool recommended);
|
||||||
void setProvidesRecommendations();
|
void setProvidesRecommendations();
|
||||||
|
@ -77,7 +77,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
|||||||
case ParentVersionRole:
|
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'.
|
// 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)
|
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
|
||||||
{
|
{
|
||||||
return req.uid == "net.minecraft";
|
return req.uid == "net.minecraft";
|
||||||
@ -92,7 +92,7 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
case UidRole: return version->uid();
|
case UidRole: return version->uid();
|
||||||
case TimeRole: return version->time();
|
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 SortRole: return version->rawTime();
|
||||||
case VersionPtrRole: return QVariant::fromValue(version);
|
case VersionPtrRole: return QVariant::fromValue(version);
|
||||||
case RecommendedRole: return version->isRecommended();
|
case RecommendedRole: return version->isRecommended();
|
||||||
|
@ -451,9 +451,9 @@ void Component::updateCachedData()
|
|||||||
m_cachedVolatile = file->m_volatile;
|
m_cachedVolatile = file->m_volatile;
|
||||||
changed = true;
|
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;
|
changed = true;
|
||||||
}
|
}
|
||||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||||
|
@ -1109,79 +1109,79 @@ JavaVersion MinecraftInstance::getJavaVersion()
|
|||||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
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();
|
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.reset(new ModFolderModel(modsRoot(), this, is_indexed));
|
||||||
m_loader_mod_list->disableInteraction(isRunning());
|
m_loader_mod_list->disableInteraction(isRunning());
|
||||||
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
|
connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||||
}
|
}
|
||||||
return m_loader_mod_list;
|
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();
|
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.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
|
||||||
m_core_mod_list->disableInteraction(isRunning());
|
m_core_mod_list->disableInteraction(isRunning());
|
||||||
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
|
connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||||
}
|
}
|
||||||
return m_core_mod_list;
|
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();
|
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.reset(new ModFolderModel(nilModsDir(), this, is_indexed, false));
|
||||||
m_nil_mod_list->disableInteraction(isRunning());
|
m_nil_mod_list->disableInteraction(isRunning());
|
||||||
connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
|
connect(this, &BaseInstance::runningStatusChanged, m_nil_mod_list.get(), &ModFolderModel::disableInteraction);
|
||||||
}
|
}
|
||||||
return m_nil_mod_list;
|
return m_nil_mod_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList() const
|
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||||
{
|
{
|
||||||
if (!m_resource_pack_list)
|
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;
|
return m_resource_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList() const
|
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||||
{
|
{
|
||||||
if (!m_texture_pack_list)
|
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;
|
return m_texture_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList() const
|
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||||
{
|
{
|
||||||
if (!m_shader_pack_list)
|
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;
|
return m_shader_pack_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<WorldList> MinecraftInstance::worldList() const
|
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||||
{
|
{
|
||||||
if (!m_world_list)
|
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;
|
return m_world_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const
|
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel()
|
||||||
{
|
{
|
||||||
if (!m_game_options)
|
if (!m_game_options)
|
||||||
{
|
{
|
||||||
|
@ -115,14 +115,14 @@ public:
|
|||||||
std::shared_ptr<PackProfile> getPackProfile() const;
|
std::shared_ptr<PackProfile> getPackProfile() const;
|
||||||
|
|
||||||
////// Mod Lists //////
|
////// Mod Lists //////
|
||||||
std::shared_ptr<ModFolderModel> loaderModList() const;
|
std::shared_ptr<ModFolderModel> loaderModList();
|
||||||
std::shared_ptr<ModFolderModel> coreModList() const;
|
std::shared_ptr<ModFolderModel> coreModList();
|
||||||
std::shared_ptr<ModFolderModel> nilModList() const;
|
std::shared_ptr<ModFolderModel> nilModList();
|
||||||
std::shared_ptr<ResourcePackFolderModel> resourcePackList() const;
|
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
||||||
std::shared_ptr<TexturePackFolderModel> texturePackList() const;
|
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
||||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList() const;
|
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
||||||
std::shared_ptr<WorldList> worldList() const;
|
std::shared_ptr<WorldList> worldList();
|
||||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
std::shared_ptr<GameOptions> gameOptionsModel();
|
||||||
|
|
||||||
////// Launch stuff //////
|
////// Launch stuff //////
|
||||||
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
Task::Ptr createUpdateTask(Net::Mode mode) override;
|
||||||
|
@ -22,6 +22,7 @@ void MinecraftLoadAndCheck::executeTask()
|
|||||||
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
|
||||||
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
connect(m_task.get(), &Task::aborted, this, [this]{ subtaskFailed(tr("Aborted")); });
|
||||||
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
|
||||||
|
connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propogateStepProgress);
|
||||||
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +100,9 @@ void MinecraftUpdate::next()
|
|||||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||||
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
disconnect(task.get(), &Task::aborted, this, &Task::abort);
|
||||||
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||||
|
disconnect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
||||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||||
|
disconnect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||||
}
|
}
|
||||||
if(m_currentTask == m_tasks.size())
|
if(m_currentTask == m_tasks.size())
|
||||||
{
|
{
|
||||||
@ -118,7 +120,9 @@ void MinecraftUpdate::next()
|
|||||||
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||||
connect(task.get(), &Task::aborted, this, &Task::abort);
|
connect(task.get(), &Task::aborted, this, &Task::abort);
|
||||||
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||||
|
connect(task.get(), &Task::stepProgress, this, &MinecraftUpdate::propogateStepProgress);
|
||||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||||
|
connect(task.get(), &Task::details, this, &MinecraftUpdate::setDetails);
|
||||||
// if the task is already running, do not start it again
|
// if the task is already running, do not start it again
|
||||||
if(!task->isRunning())
|
if(!task->isRunning())
|
||||||
{
|
{
|
||||||
|
@ -276,7 +276,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
|||||||
|
|
||||||
if (root.contains("requires"))
|
if (root.contains("requires"))
|
||||||
{
|
{
|
||||||
Meta::parseRequires(root, &out->requires);
|
Meta::parseRequires(root, &out->m_requires);
|
||||||
}
|
}
|
||||||
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
|
QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
|
||||||
if(!dependsOnMinecraftVersion.isEmpty())
|
if(!dependsOnMinecraftVersion.isEmpty())
|
||||||
@ -284,9 +284,9 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
|
|||||||
Meta::Require mcReq;
|
Meta::Require mcReq;
|
||||||
mcReq.uid = "net.minecraft";
|
mcReq.uid = "net.minecraft";
|
||||||
mcReq.equalsVersion = dependsOnMinecraftVersion;
|
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"))
|
if (root.contains("conflicts"))
|
||||||
@ -392,9 +392,9 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
|
|||||||
}
|
}
|
||||||
root.insert("mods", array);
|
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())
|
if(!patch->conflicts.empty())
|
||||||
{
|
{
|
||||||
|
@ -138,7 +138,7 @@ public: /* data */
|
|||||||
* Prism Launcher: set of packages this depends on
|
* Prism Launcher: set of packages this depends on
|
||||||
* NOTE: this is shared with the meta format!!!
|
* NOTE: this is shared with the meta format!!!
|
||||||
*/
|
*/
|
||||||
Meta::RequireSet requires;
|
Meta::RequireSet m_requires;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prism Launcher: set of packages this conflicts with
|
* Prism Launcher: set of packages this conflicts with
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QDebug>
|
#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)
|
: QAbstractListModel(), m_instance(instance), m_dir(dir)
|
||||||
{
|
{
|
||||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
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_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
|
||||||
m_watcher = new QFileSystemWatcher(this);
|
m_watcher = new QFileSystemWatcher(this);
|
||||||
is_watching = false;
|
is_watching = false;
|
||||||
connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
|
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &WorldList::directoryChanged);
|
||||||
SLOT(directoryChanged(QString)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldList::startWatching()
|
void WorldList::startWatching()
|
||||||
|
@ -50,7 +50,7 @@ public:
|
|||||||
IconFileRole
|
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;
|
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ signals:
|
|||||||
void changed();
|
void changed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<const BaseInstance> m_instance;
|
BaseInstance* m_instance;
|
||||||
QFileSystemWatcher *m_watcher;
|
QFileSystemWatcher *m_watcher;
|
||||||
bool is_watching;
|
bool is_watching;
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
|
@ -55,12 +55,12 @@ void AuthRequest::get(const QNetworkRequest &req, int timeout/* = 60*1000*/) {
|
|||||||
reply_ = APPLICATION->network()->get(request_);
|
reply_ = APPLICATION->network()->get(request_);
|
||||||
status_ = Requesting;
|
status_ = Requesting;
|
||||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||||
#else
|
#else // &QNetworkReply::error SIGNAL depricated
|
||||||
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||||
#endif
|
#endif
|
||||||
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
|
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,14 +70,14 @@ void AuthRequest::post(const QNetworkRequest &req, const QByteArray &data, int t
|
|||||||
status_ = Requesting;
|
status_ = Requesting;
|
||||||
reply_ = APPLICATION->network()->post(request_, data_);
|
reply_ = APPLICATION->network()->post(request_, data_);
|
||||||
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
timedReplies_.add(new Katabasis::Reply(reply_, timeout));
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(reply_, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
connect(reply_, &QNetworkReply::errorOccurred, this, &AuthRequest::onRequestError);
|
||||||
#else
|
#else // &QNetworkReply::error SIGNAL depricated
|
||||||
connect(reply_, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRequestError(QNetworkReply::NetworkError)));
|
connect(reply_, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &AuthRequest::onRequestError);
|
||||||
#endif
|
#endif
|
||||||
connect(reply_, SIGNAL(finished()), this, SLOT(onRequestFinished()));
|
connect(reply_, &QNetworkReply::finished, this, &AuthRequest::onRequestFinished);
|
||||||
connect(reply_, &QNetworkReply::sslErrors, this, &AuthRequest::onSslErrors);
|
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() {
|
void AuthRequest::onRequestFinished() {
|
||||||
|
@ -133,8 +133,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password) {
|
|||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new MojangLogin(&data, password));
|
m_currentTask.reset(new MojangLogin(&data, password));
|
||||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||||
emit activityChanged(true);
|
emit activityChanged(true);
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
@ -144,8 +144,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA() {
|
|||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new MSAInteractive(&data));
|
m_currentTask.reset(new MSAInteractive(&data));
|
||||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||||
emit activityChanged(true);
|
emit activityChanged(true);
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
@ -155,8 +155,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::loginOffline() {
|
|||||||
Q_ASSERT(m_currentTask.get() == nullptr);
|
Q_ASSERT(m_currentTask.get() == nullptr);
|
||||||
|
|
||||||
m_currentTask.reset(new OfflineLogin(&data));
|
m_currentTask.reset(new OfflineLogin(&data));
|
||||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||||
emit activityChanged(true);
|
emit activityChanged(true);
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
@ -177,8 +177,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh() {
|
|||||||
m_currentTask.reset(new MojangRefresh(&data));
|
m_currentTask.reset(new MojangRefresh(&data));
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
|
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
|
||||||
connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
|
connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
|
||||||
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
connect(m_currentTask.get(), &Task::aborted, this, [this]{ authFailed(tr("Aborted")); });
|
||||||
emit activityChanged(true);
|
emit activityChanged(true);
|
||||||
return m_currentTask;
|
return m_currentTask;
|
||||||
|
@ -38,6 +38,10 @@ void XboxUserStep::perform() {
|
|||||||
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Accept", "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);
|
auto *requestor = new AuthRequest(this);
|
||||||
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
connect(requestor, &AuthRequest::finished, this, &XboxUserStep::onRequestDone);
|
||||||
requestor->post(request, xbox_auth_data.toUtf8());
|
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") } },
|
{ 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") } },
|
{ 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") } },
|
{ 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)
|
void DataPack::setPackFormat(int new_format_id)
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||||
#include "modplatform/ModIndex.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)
|
: 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_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
|
||||||
|
@ -75,7 +75,7 @@ public:
|
|||||||
Enable,
|
Enable,
|
||||||
Toggle
|
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);
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
#include "tasks/Task.h"
|
#include "tasks/Task.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)
|
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
|
||||||
{
|
{
|
||||||
if (create_dir) {
|
if (create_dir) {
|
||||||
|
@ -26,7 +26,7 @@ class QSortFilterProxyModel;
|
|||||||
class ResourceFolderModel : public QAbstractListModel {
|
class ResourceFolderModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
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;
|
~ResourceFolderModel() override;
|
||||||
|
|
||||||
/** Starts watching the paths for changes.
|
/** Starts watching the paths for changes.
|
||||||
@ -191,7 +191,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||||||
bool m_can_interact = true;
|
bool m_can_interact = true;
|
||||||
|
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
std::shared_ptr<const BaseInstance> m_instance;
|
BaseInstance* m_instance;
|
||||||
QFileSystemWatcher m_watcher;
|
QFileSystemWatcher m_watcher;
|
||||||
bool m_is_watching = false;
|
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") } },
|
{ 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") } },
|
{ 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") } },
|
{ 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)
|
void ResourcePack::setPackFormat(int new_format_id)
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.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)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{
|
{
|
||||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
|
||||||
|
@ -17,7 +17,7 @@ public:
|
|||||||
NUM_COLUMNS
|
NUM_COLUMNS
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ResourcePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
explicit ResourcePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||||
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ class ShaderPackFolderModel : public ResourceFolderModel {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ShaderPackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance)
|
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance)
|
||||||
: ResourceFolderModel(QDir(dir), instance)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||||
#include "minecraft/mod/tasks/LocalTexturePackParseTask.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)
|
: ResourceFolderModel(QDir(dir), instance)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class TexturePackFolderModel : public ResourceFolderModel
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TexturePackFolderModel(const QString &dir, std::shared_ptr<const BaseInstance> instance);
|
explicit TexturePackFolderModel(const QString &dir, BaseInstance* instance);
|
||||||
[[nodiscard]] Task* createUpdateTask() override;
|
[[nodiscard]] Task* createUpdateTask() override;
|
||||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||||
};
|
};
|
||||||
|
@ -54,9 +54,14 @@ void CapeChange::setCape(QString& cape) {
|
|||||||
setStatus(tr("Equipping cape"));
|
setStatus(tr("Equipping cape"));
|
||||||
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
|
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() {
|
void CapeChange::clearCape() {
|
||||||
@ -68,13 +73,14 @@ void CapeChange::clearCape() {
|
|||||||
setStatus(tr("Removing cape"));
|
setStatus(tr("Removing cape"));
|
||||||
|
|
||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &CapeChange::setProgress);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, &QNetworkReply::errorOccurred, this, &CapeChange::downloadError);
|
||||||
#else
|
#else
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &CapeChange::downloadError);
|
||||||
#endif
|
#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());
|
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()
|
void CapeChange::downloadFinished()
|
||||||
{
|
{
|
||||||
// if the download failed
|
// if the download failed
|
||||||
|
@ -27,6 +27,7 @@ protected:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
void downloadError(QNetworkReply::NetworkError);
|
||||||
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
void downloadFinished();
|
void downloadFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,13 +53,14 @@ void SkinDelete::executeTask()
|
|||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||||
|
|
||||||
setStatus(tr("Deleting skin"));
|
setStatus(tr("Deleting skin"));
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &SkinDelete::setProgress);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, &QNetworkReply::errorOccurred, this, &SkinDelete::downloadError);
|
||||||
#else
|
#else
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinDelete::downloadError);
|
||||||
#endif
|
#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)
|
void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
||||||
@ -69,6 +70,17 @@ void SkinDelete::downloadError(QNetworkReply::NetworkError error)
|
|||||||
emitFailed(m_reply->errorString());
|
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()
|
void SkinDelete::downloadFinished()
|
||||||
{
|
{
|
||||||
// if the download failed
|
// if the download failed
|
||||||
|
@ -22,5 +22,6 @@ protected:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
void downloadError(QNetworkReply::NetworkError);
|
||||||
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
void downloadFinished();
|
void downloadFinished();
|
||||||
};
|
};
|
||||||
|
@ -78,13 +78,14 @@ void SkinUpload::executeTask()
|
|||||||
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
m_reply = shared_qobject_ptr<QNetworkReply>(rep);
|
||||||
|
|
||||||
setStatus(tr("Uploading skin"));
|
setStatus(tr("Uploading skin"));
|
||||||
connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
|
connect(rep, &QNetworkReply::uploadProgress, this, &SkinUpload::setProgress);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, &QNetworkReply::errorOccurred, this, &SkinUpload::downloadError);
|
||||||
#else
|
#else
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &SkinUpload::downloadError);
|
||||||
#endif
|
#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)
|
void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
||||||
@ -94,6 +95,17 @@ void SkinUpload::downloadError(QNetworkReply::NetworkError error)
|
|||||||
emitFailed(m_reply->errorString());
|
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()
|
void SkinUpload::downloadFinished()
|
||||||
{
|
{
|
||||||
// if the download failed
|
// if the download failed
|
||||||
|
@ -32,6 +32,7 @@ protected:
|
|||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void downloadError(QNetworkReply::NetworkError);
|
void downloadError(QNetworkReply::NetworkError);
|
||||||
|
void sslErrors(const QList<QSslError>& errors);
|
||||||
|
|
||||||
void downloadFinished();
|
void downloadFinished();
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,7 @@ void AssetUpdateTask::executeTask()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetIndexFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||||
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
||||||
|
|
||||||
qDebug() << m_inst->name() << ": Starting asset index download";
|
qDebug() << m_inst->name() << ": Starting asset index download";
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
@ -83,6 +84,7 @@ void AssetUpdateTask::assetIndexFinished()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &AssetUpdateTask::assetsFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &AssetUpdateTask::progress);
|
||||||
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &AssetUpdateTask::propogateStepProgress);
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ void FMLLibrariesTask::executeTask()
|
|||||||
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
|
connect(dljob.get(), &NetJob::failed, this, &FMLLibrariesTask::fmllibsFailed);
|
||||||
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(dljob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
|
connect(dljob.get(), &NetJob::progress, this, &FMLLibrariesTask::progress);
|
||||||
|
connect(dljob.get(), &NetJob::stepProgress, this, &FMLLibrariesTask::propogateStepProgress);
|
||||||
downloadJob.reset(dljob);
|
downloadJob.reset(dljob);
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ void LibrariesTask::executeTask()
|
|||||||
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
|
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
|
||||||
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
connect(downloadJob.get(), &NetJob::aborted, this, [this]{ emitFailed(tr("Aborted")); });
|
||||||
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
|
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
|
||||||
|
connect(downloadJob.get(), &NetJob::stepProgress, this, &LibrariesTask::propogateStepProgress);
|
||||||
|
|
||||||
downloadJob->start();
|
downloadJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* PolyMC - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, version 3.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@ -23,6 +23,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
|
||||||
@ -68,7 +69,6 @@ struct IndexedVersion {
|
|||||||
|
|
||||||
// For internal use, not provided by APIs
|
// For internal use, not provided by APIs
|
||||||
bool is_currently_selected = false;
|
bool is_currently_selected = false;
|
||||||
QString custom_target_folder;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExtraPackData {
|
struct ExtraPackData {
|
||||||
@ -83,6 +83,8 @@ struct ExtraPackData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct IndexedPack {
|
struct IndexedPack {
|
||||||
|
using Ptr = std::shared_ptr<IndexedPack>;
|
||||||
|
|
||||||
QVariant addonId;
|
QVariant addonId;
|
||||||
ResourceProvider provider;
|
ResourceProvider provider;
|
||||||
QString name;
|
QString name;
|
||||||
@ -113,12 +115,12 @@ struct IndexedPack {
|
|||||||
if (!versionsLoaded)
|
if (!versionsLoaded)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return std::any_of(versions.constBegin(), versions.constEnd(),
|
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
|
||||||
[](auto const& v) { return v.is_currently_selected; });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ModPlatform
|
} // namespace ModPlatform
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
|
||||||
|
Q_DECLARE_METATYPE(ModPlatform::IndexedPack::Ptr)
|
||||||
Q_DECLARE_METATYPE(ModPlatform::ResourceProvider)
|
Q_DECLARE_METATYPE(ModPlatform::ResourceProvider)
|
||||||
|
@ -352,7 +352,7 @@ QString PackInstallTask::getVersionForLoader(QString uid)
|
|||||||
if(m_version.loader.recommended || m_version.loader.latest) {
|
if(m_version.loader.recommended || m_version.loader.latest) {
|
||||||
for (int i = 0; i < vlist->versions().size(); i++) {
|
for (int i = 0; i < vlist->versions().size(); i++) {
|
||||||
auto version = vlist->versions().at(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.
|
// 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
|
// not all mod loaders depend on a given Minecraft version, so we won't do this
|
||||||
@ -683,6 +683,7 @@ void PackInstallTask::installConfigs()
|
|||||||
abortable = true;
|
abortable = true;
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
|
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
||||||
connect(jobPtr.get(), &NetJob::aborted, [&]{
|
connect(jobPtr.get(), &NetJob::aborted, [&]{
|
||||||
abortable = false;
|
abortable = false;
|
||||||
jobPtr.reset();
|
jobPtr.reset();
|
||||||
@ -846,9 +847,11 @@ void PackInstallTask::downloadMods()
|
|||||||
});
|
});
|
||||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||||
{
|
{
|
||||||
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
abortable = true;
|
abortable = true;
|
||||||
setProgress(current, total);
|
setProgress(current, total);
|
||||||
});
|
});
|
||||||
|
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
||||||
connect(jobPtr.get(), &NetJob::aborted, [&]
|
connect(jobPtr.get(), &NetJob::aborted, [&]
|
||||||
{
|
{
|
||||||
abortable = false;
|
abortable = false;
|
||||||
|
@ -35,7 +35,29 @@ void Flame::FileResolvingTask::executeTask()
|
|||||||
QByteArray data = Json::toText(object);
|
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.get(), data);
|
||||||
m_dljob->addNetAction(dl);
|
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();
|
m_dljob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +66,7 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
setProgress(1, 3);
|
setProgress(1, 3);
|
||||||
// job to check modrinth for blocked projects
|
// job to check modrinth for blocked projects
|
||||||
m_checkJob.reset(new NetJob("Modrinth check", m_network));
|
m_checkJob.reset(new NetJob("Modrinth check", m_network));
|
||||||
blockedProjects = QMap<File *,QByteArray *>();
|
blockedProjects = QMap<File*, std::shared_ptr<QByteArray>>();
|
||||||
|
|
||||||
QJsonDocument doc;
|
QJsonDocument doc;
|
||||||
QJsonArray array;
|
QJsonArray array;
|
||||||
@ -71,8 +93,8 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
auto hash = out.hash;
|
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 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);
|
auto dl = Net::Download::makeByteArray(QUrl(url), output.get());
|
||||||
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
|
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() {
|
||||||
out.resolved = true;
|
out.resolved = true;
|
||||||
});
|
});
|
||||||
@ -82,7 +104,27 @@ void Flame::FileResolvingTask::netJobFinished()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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();
|
m_checkJob->start();
|
||||||
}
|
}
|
||||||
@ -95,7 +137,6 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
auto &out = *it;
|
auto &out = *it;
|
||||||
auto bytes = blockedProjects[out];
|
auto bytes = blockedProjects[out];
|
||||||
if (!out->resolved) {
|
if (!out->resolved) {
|
||||||
delete bytes;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,11 +153,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
} else {
|
} else {
|
||||||
out->resolved = false;
|
out->resolved = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete bytes;
|
|
||||||
}
|
}
|
||||||
//copy to an output list and filter out projects found on modrinth
|
//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();
|
auto it = blockedProjects.keys();
|
||||||
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
|
std::copy_if(it.begin(), it.end(), std::back_inserter(*block), [](File *f) {
|
||||||
return !f->resolved;
|
return !f->resolved;
|
||||||
@ -124,32 +163,48 @@ void Flame::FileResolvingTask::modrinthCheckFinished() {
|
|||||||
//Display not found mods early
|
//Display not found mods early
|
||||||
if (!block->empty()) {
|
if (!block->empty()) {
|
||||||
//blocked mods found, we need the slug for displaying.... we need another job :D !
|
//blocked mods found, we need the slug for displaying.... we need another job :D !
|
||||||
auto slugJob = new NetJob("Slug Job", m_network);
|
m_slugJob.reset(new NetJob("Slug Job", m_network));
|
||||||
auto slugs = QVector<QByteArray>(block->size());
|
int index = 0;
|
||||||
auto index = 0;
|
for (auto mod : *block) {
|
||||||
for (auto fileInfo: *block) {
|
auto projectId = mod->projectId;
|
||||||
auto projectId = fileInfo->projectId;
|
auto output = std::make_shared<QByteArray>();
|
||||||
slugs[index] = QByteArray();
|
|
||||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
|
||||||
auto dl = Net::Download::makeByteArray(url, &slugs[index]);
|
auto dl = Net::Download::makeByteArray(url, output.get());
|
||||||
slugJob->addNetAction(dl);
|
qDebug() << "Fetching url slug for file:" << mod->fileName;
|
||||||
index++;
|
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
|
||||||
connect(slugJob, &NetJob::succeeded, this, [slugs, this, slugJob, block]() {
|
auto json = QJsonDocument::fromJson(*output);
|
||||||
slugJob->deleteLater();
|
|
||||||
auto index = 0;
|
|
||||||
for (const auto &slugResult: slugs) {
|
|
||||||
auto json = QJsonDocument::fromJson(slugResult);
|
|
||||||
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
|
auto base = Json::requireString(Json::requireObject(Json::requireObject(Json::requireObject(json),"data"),"links"),
|
||||||
"websiteUrl");
|
"websiteUrl");
|
||||||
auto mod = block->at(index);
|
|
||||||
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
|
auto link = QString("%1/download/%2").arg(base, QString::number(mod->fileId));
|
||||||
mod->websiteUrl = link;
|
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();
|
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 {
|
} else {
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "tasks/Task.h"
|
|
||||||
#include "net/NetJob.h"
|
|
||||||
#include "PackManifest.h"
|
#include "PackManifest.h"
|
||||||
|
#include "net/NetJob.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
namespace Flame
|
namespace Flame {
|
||||||
{
|
class FileResolvingTask : public Task {
|
||||||
class FileResolvingTask : public Task
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest &toProcess);
|
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess);
|
||||||
virtual ~FileResolvingTask() {};
|
virtual ~FileResolvingTask(){};
|
||||||
|
|
||||||
bool canAbort() const override { return true; }
|
bool canAbort() const override { return true; }
|
||||||
bool abort() override;
|
bool abort() override;
|
||||||
|
|
||||||
const Flame::Manifest &getResults() const
|
const Flame::Manifest& getResults() const { return m_toProcess; }
|
||||||
{
|
|
||||||
return m_toProcess;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void executeTask() override;
|
virtual void executeTask() override;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void netJobFinished();
|
void netJobFinished();
|
||||||
|
|
||||||
private: /* data */
|
private: /* data */
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
Flame::Manifest m_toProcess;
|
Flame::Manifest m_toProcess;
|
||||||
std::shared_ptr<QByteArray> result;
|
std::shared_ptr<QByteArray> result;
|
||||||
NetJob::Ptr m_dljob;
|
NetJob::Ptr m_dljob;
|
||||||
NetJob::Ptr m_checkJob;
|
NetJob::Ptr m_checkJob;
|
||||||
|
NetJob::Ptr m_slugJob;
|
||||||
|
|
||||||
void modrinthCheckFinished();
|
void modrinthCheckFinished();
|
||||||
|
|
||||||
QMap<File *, QByteArray *> blockedProjects;
|
QMap<File*, std::shared_ptr<QByteArray>> blockedProjects;
|
||||||
};
|
};
|
||||||
}
|
} // namespace Flame
|
||||||
|
@ -38,14 +38,14 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
|
|||||||
QEventLoop lock;
|
QEventLoop lock;
|
||||||
QString changelog;
|
QString changelog;
|
||||||
|
|
||||||
auto* netJob = new NetJob(QString("Flame::FileChangelog"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
|
||||||
auto* response = new QByteArray();
|
auto response = std::make_shared<QByteArray>();
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(
|
netJob->addNetAction(Net::Download::makeByteArray(
|
||||||
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
|
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
|
||||||
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
|
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
|
||||||
response));
|
response.get()));
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &changelog] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &changelog] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
@ -60,10 +60,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
|
|||||||
changelog = Json::ensureString(doc.object(), "data");
|
changelog = Json::ensureString(doc.object(), "data");
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::finished, [response, &lock] {
|
QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); });
|
||||||
delete response;
|
|
||||||
lock.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
netJob->start();
|
netJob->start();
|
||||||
lock.exec();
|
lock.exec();
|
||||||
@ -76,13 +73,12 @@ auto FlameAPI::getModDescription(int modId) -> QString
|
|||||||
QEventLoop lock;
|
QEventLoop lock;
|
||||||
QString description;
|
QString description;
|
||||||
|
|
||||||
auto* netJob = new NetJob(QString("Flame::ModDescription"), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
|
||||||
auto* response = new QByteArray();
|
auto response = std::make_shared<QByteArray>();
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(
|
netJob->addNetAction(Net::Download::makeByteArray(
|
||||||
QString("https://api.curseforge.com/v1/mods/%1/description")
|
QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response.get()));
|
||||||
.arg(QString::number(modId)), response));
|
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &description] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
@ -97,10 +93,7 @@ auto FlameAPI::getModDescription(int modId) -> QString
|
|||||||
description = Json::ensureString(doc.object(), "data");
|
description = Json::ensureString(doc.object(), "data");
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::finished, [response, &lock] {
|
QObject::connect(netJob.get(), &NetJob::finished, [&lock] { lock.quit(); });
|
||||||
delete response;
|
|
||||||
lock.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
netJob->start();
|
netJob->start();
|
||||||
lock.exec();
|
lock.exec();
|
||||||
@ -118,13 +111,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
|
|||||||
|
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
|
||||||
auto netJob = new NetJob(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
|
auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
|
||||||
auto response = new QByteArray();
|
auto response = std::make_shared<QByteArray>();
|
||||||
ModPlatform::IndexedVersion ver;
|
ModPlatform::IndexedVersion ver;
|
||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response.get()));
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::succeeded, [response, args, &ver] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
@ -158,11 +151,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob, &NetJob::finished, [response, netJob, &loop] {
|
QObject::connect(netJob.get(), &NetJob::finished, [&loop] { loop.quit(); });
|
||||||
netJob->deleteLater();
|
|
||||||
delete response;
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
|
|
||||||
netJob->start();
|
netJob->start();
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "FlameModIndex.h"
|
#include "FlameModIndex.h"
|
||||||
|
|
||||||
#include <MurmurHash2.h>
|
#include <MurmurHash2.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
#include "Json.h"
|
#include "Json.h"
|
||||||
@ -129,8 +130,7 @@ void FlameCheckUpdate::executeTask()
|
|||||||
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
|
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
|
||||||
setProgress(i++, m_mods.size());
|
setProgress(i++, m_mods.size());
|
||||||
|
|
||||||
ModPlatform::IndexedPack pack{ mod->metadata()->project_id.toString() };
|
auto latest_ver = api.getLatestVersion({ { mod->metadata()->project_id.toString() }, m_game_versions, m_loaders });
|
||||||
auto latest_ver = api.getLatestVersion({ pack, m_game_versions, m_loaders });
|
|
||||||
|
|
||||||
// Check if we were aborted while getting the latest version
|
// Check if we were aborted while getting the latest version
|
||||||
if (m_was_aborted) {
|
if (m_was_aborted) {
|
||||||
@ -156,15 +156,15 @@ void FlameCheckUpdate::executeTask()
|
|||||||
|
|
||||||
if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) {
|
if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) {
|
||||||
// Fake pack with the necessary info to pass to the download task :)
|
// Fake pack with the necessary info to pass to the download task :)
|
||||||
ModPlatform::IndexedPack pack;
|
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||||
pack.name = mod->name();
|
pack->name = mod->name();
|
||||||
pack.slug = mod->metadata()->slug;
|
pack->slug = mod->metadata()->slug;
|
||||||
pack.addonId = mod->metadata()->project_id;
|
pack->addonId = mod->metadata()->project_id;
|
||||||
pack.websiteUrl = mod->homeurl();
|
pack->websiteUrl = mod->homeurl();
|
||||||
for (auto& author : mod->authors())
|
for (auto& author : mod->authors())
|
||||||
pack.authors.append({ author });
|
pack->authors.append({ author });
|
||||||
pack.description = mod->description();
|
pack->description = mod->description();
|
||||||
pack.provider = ModPlatform::ResourceProvider::FLAME;
|
pack->provider = ModPlatform::ResourceProvider::FLAME;
|
||||||
|
|
||||||
auto old_version = mod->version();
|
auto old_version = mod->version();
|
||||||
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
|
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
|
||||||
@ -173,7 +173,7 @@ void FlameCheckUpdate::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
|
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
|
||||||
m_updatable.emplace_back(pack.name, mod->metadata()->hash, old_version, latest_ver.version,
|
m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version,
|
||||||
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
|
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
|
||||||
ModPlatform::ResourceProvider::FLAME, download_task);
|
ModPlatform::ResourceProvider::FLAME, download_task);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
#include "FlameInstanceCreationTask.h"
|
#include "FlameInstanceCreationTask.h"
|
||||||
|
|
||||||
|
#include "modplatform/flame/FileResolvingTask.h"
|
||||||
#include "modplatform/flame/FlameAPI.h"
|
#include "modplatform/flame/FlameAPI.h"
|
||||||
#include "modplatform/flame/PackManifest.h"
|
#include "modplatform/flame/PackManifest.h"
|
||||||
|
|
||||||
@ -382,7 +383,8 @@ bool FlameCreationTask::createInstance()
|
|||||||
});
|
});
|
||||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
||||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
||||||
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
||||||
|
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
||||||
m_mod_id_resolver->start();
|
m_mod_id_resolver->start();
|
||||||
|
|
||||||
loop.exec();
|
loop.exec();
|
||||||
@ -452,7 +454,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||||||
|
|
||||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||||
{
|
{
|
||||||
m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||||
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
for (const auto& result : m_mod_id_resolver->getResults().files) {
|
||||||
QString filename = result.fileName;
|
QString filename = result.fileName;
|
||||||
if (!result.required) {
|
if (!result.required) {
|
||||||
@ -496,7 +498,11 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||||||
m_files_job.reset();
|
m_files_job.reset();
|
||||||
setError(reason);
|
setError(reason);
|
||||||
});
|
});
|
||||||
connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress);
|
connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total){
|
||||||
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
|
setProgress(current, total);
|
||||||
|
});
|
||||||
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propogateStepProgress);
|
||||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||||
|
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
|
@ -24,7 +24,7 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
|
netJob->addNetAction(Net::Download::makeByteArray(QUrl(search_url), response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [=]{
|
QObject::connect(netJob.get(), &NetJob::succeeded, [this, response, callbacks]{
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
@ -40,16 +40,20 @@ Task::Ptr NetworkResourceAPI::searchProjects(SearchArgs&& args, SearchCallbacks&
|
|||||||
callbacks.on_succeed(doc);
|
callbacks.on_succeed(doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::failed, [=](QString reason){
|
QObject::connect(netJob.get(), &NetJob::failed, [&netJob, callbacks](QString reason){
|
||||||
int network_error_code = -1;
|
int network_error_code = -1;
|
||||||
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
if (auto* failed_action = netJob->getFailedActions().at(0); failed_action && failed_action->m_reply)
|
||||||
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
network_error_code = failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
callbacks.on_fail(reason, network_error_code);
|
callbacks.on_fail(reason, network_error_code);
|
||||||
});
|
});
|
||||||
QObject::connect(netJob.get(), &NetJob::aborted, [=]{
|
QObject::connect(netJob.get(), &NetJob::aborted, [callbacks]{
|
||||||
callbacks.on_abort();
|
callbacks.on_abort();
|
||||||
});
|
});
|
||||||
|
QObject::connect(netJob.get(), &NetJob::finished, [response] {
|
||||||
|
delete response;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
return netJob;
|
return netJob;
|
||||||
}
|
}
|
||||||
@ -88,7 +92,7 @@ Task::Ptr NetworkResourceAPI::getProjectVersions(VersionSearchArgs&& args, Versi
|
|||||||
|
|
||||||
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
|
||||||
|
|
||||||
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
|
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
|
||||||
QJsonParseError parse_error{};
|
QJsonParseError parse_error{};
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||||
if (parse_error.error != QJsonParseError::NoError) {
|
if (parse_error.error != QJsonParseError::NoError) {
|
||||||
|
@ -81,6 +81,7 @@ void PackInstallTask::downloadPack()
|
|||||||
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
|
||||||
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
|
||||||
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
|
||||||
|
connect(netJobContainer.get(), &NetJob::stepProgress, this, &PackInstallTask::propogateStepProgress);
|
||||||
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
|
connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted);
|
||||||
|
|
||||||
netJobContainer->start();
|
netJobContainer->start();
|
||||||
|
@ -54,7 +54,7 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
if (mod->metadata()->hash_format != best_hash_type) {
|
if (mod->metadata()->hash_format != best_hash_type) {
|
||||||
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
|
auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath());
|
||||||
connect(hash_task.get(), &Task::succeeded, [&] {
|
connect(hash_task.get(), &Task::succeeded, [&] {
|
||||||
QString hash (hash_task->getResult());
|
QString hash(hash_task->getResult());
|
||||||
hashes.append(hash);
|
hashes.append(hash);
|
||||||
mappings.insert(hash, mod);
|
mappings.insert(hash, mod);
|
||||||
});
|
});
|
||||||
@ -67,7 +67,7 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); });
|
connect(&hashing_task, &Task::finished, [&loop] { loop.quit(); });
|
||||||
hashing_task.start();
|
hashing_task.start();
|
||||||
loop.exec();
|
loop.exec();
|
||||||
|
|
||||||
@ -112,7 +112,8 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
// so we may want to filter it
|
// so we may want to filter it
|
||||||
QString loader_filter;
|
QString loader_filter;
|
||||||
if (m_loaders.has_value()) {
|
if (m_loaders.has_value()) {
|
||||||
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt };
|
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric,
|
||||||
|
ResourceAPI::ModLoaderType::Quilt };
|
||||||
for (auto flag : flags) {
|
for (auto flag : flags) {
|
||||||
if (m_loaders.value().testFlag(flag)) {
|
if (m_loaders.value().testFlag(flag)) {
|
||||||
loader_filter = api.getModLoaderString(flag);
|
loader_filter = api.getModLoaderString(flag);
|
||||||
@ -122,7 +123,8 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
|
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
|
||||||
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the loader_filter
|
// - The file needs to be preferred: It is either the primary file, or the one found via (explicit) usage of the
|
||||||
|
// loader_filter
|
||||||
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
|
// - The version reported by the JAR is different from the version reported by the indexed version (it's usually the case)
|
||||||
// Such is the pain of having arbitrary files for a given version .-.
|
// Such is the pain of having arbitrary files for a given version .-.
|
||||||
|
|
||||||
@ -149,19 +151,19 @@ void ModrinthCheckUpdate::executeTask()
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Fake pack with the necessary info to pass to the download task :)
|
// Fake pack with the necessary info to pass to the download task :)
|
||||||
ModPlatform::IndexedPack pack;
|
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||||
pack.name = mod->name();
|
pack->name = mod->name();
|
||||||
pack.slug = mod->metadata()->slug;
|
pack->slug = mod->metadata()->slug;
|
||||||
pack.addonId = mod->metadata()->project_id;
|
pack->addonId = mod->metadata()->project_id;
|
||||||
pack.websiteUrl = mod->homeurl();
|
pack->websiteUrl = mod->homeurl();
|
||||||
for (auto& author : mod->authors())
|
for (auto& author : mod->authors())
|
||||||
pack.authors.append({ author });
|
pack->authors.append({ author });
|
||||||
pack.description = mod->description();
|
pack->description = mod->description();
|
||||||
pack.provider = ModPlatform::ResourceProvider::MODRINTH;
|
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
|
||||||
|
|
||||||
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
|
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
|
||||||
|
|
||||||
m_updatable.emplace_back(pack.name, hash, mod->version(), project_ver.version_number, project_ver.changelog,
|
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog,
|
||||||
ModPlatform::ResourceProvider::MODRINTH, download_task);
|
ModPlatform::ResourceProvider::MODRINTH, download_task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "net/ChecksumValidator.h"
|
#include "net/ChecksumValidator.h"
|
||||||
|
|
||||||
|
#include "net/NetJob.h"
|
||||||
#include "settings/INISettingsObject.h"
|
#include "settings/INISettingsObject.h"
|
||||||
|
|
||||||
#include "ui/dialogs/CustomMessageBox.h"
|
#include "ui/dialogs/CustomMessageBox.h"
|
||||||
@ -223,7 +224,7 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
instance.setName(name());
|
instance.setName(name());
|
||||||
instance.saveNow();
|
instance.saveNow();
|
||||||
|
|
||||||
m_files_job.reset(new NetJob(tr("Mod download"), APPLICATION->network()));
|
m_files_job.reset(new NetJob(tr("Mod Download Modrinth"), APPLICATION->network()));
|
||||||
|
|
||||||
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
|
auto root_modpack_path = FS::PathCombine(m_stagingPath, ".minecraft");
|
||||||
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
||||||
@ -262,7 +263,11 @@ bool ModrinthCreationTask::createInstance()
|
|||||||
setError(reason);
|
setError(reason);
|
||||||
});
|
});
|
||||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||||
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); });
|
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||||
|
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||||
|
setProgress(current, total);
|
||||||
|
});
|
||||||
|
connect(m_files_job.get(), &NetJob::stepProgress, this, &ModrinthCreationTask::propogateStepProgress);
|
||||||
|
|
||||||
setStatus(tr("Downloading mods..."));
|
setStatus(tr("Downloading mods..."));
|
||||||
m_files_job->start();
|
m_files_job->start();
|
||||||
|
319
launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
Normal file
319
launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
// 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 "ModrinthPackExportTask.h"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
#include "Json.h"
|
||||||
|
#include "MMCZip.h"
|
||||||
|
#include "minecraft/PackProfile.h"
|
||||||
|
#include "minecraft/mod/ModFolderModel.h"
|
||||||
|
|
||||||
|
const QStringList ModrinthPackExportTask::PREFIXES({ "mods/", "coremods/", "resourcepacks/", "texturepacks/", "shaderpacks/" });
|
||||||
|
const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "zip" });
|
||||||
|
|
||||||
|
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
|
||||||
|
const QString& version,
|
||||||
|
const QString& summary,
|
||||||
|
InstancePtr instance,
|
||||||
|
const QString& output,
|
||||||
|
MMCZip::FilterFunction filter)
|
||||||
|
: name(name)
|
||||||
|
, version(version)
|
||||||
|
, summary(summary)
|
||||||
|
, instance(instance)
|
||||||
|
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
|
||||||
|
, gameRoot(instance->gameRoot())
|
||||||
|
, output(output)
|
||||||
|
, filter(filter)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::executeTask()
|
||||||
|
{
|
||||||
|
setStatus(tr("Searching for files..."));
|
||||||
|
setProgress(0, 0);
|
||||||
|
collectFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModrinthPackExportTask::abort()
|
||||||
|
{
|
||||||
|
if (task != nullptr) {
|
||||||
|
task->abort();
|
||||||
|
task = nullptr;
|
||||||
|
emitAborted();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildZipFuture.isRunning()) {
|
||||||
|
buildZipFuture.cancel();
|
||||||
|
// NOTE: Here we don't do `emitAborted()` because it will be done when `buildZipFuture` actually cancels, which may not occur immediately.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::collectFiles()
|
||||||
|
{
|
||||||
|
setAbortable(false);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
files.clear();
|
||||||
|
if (!MMCZip::collectFileListRecursively(instance->gameRoot(), nullptr, &files, filter)) {
|
||||||
|
emitFailed(tr("Could not search for files"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingHashes.clear();
|
||||||
|
resolvedFiles.clear();
|
||||||
|
|
||||||
|
if (mcInstance) {
|
||||||
|
mcInstance->loaderModList()->update();
|
||||||
|
connect(mcInstance->loaderModList().get(), &ModFolderModel::updateFinished, this, &ModrinthPackExportTask::collectHashes);
|
||||||
|
} else
|
||||||
|
collectHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::collectHashes()
|
||||||
|
{
|
||||||
|
for (const QFileInfo& file : files) {
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||||
|
// require sensible file types
|
||||||
|
if (!std::any_of(PREFIXES.begin(), PREFIXES.end(), [&relative](const QString& prefix) { return relative.startsWith(prefix); }))
|
||||||
|
continue;
|
||||||
|
if (!std::any_of(FILE_EXTENSIONS.begin(), FILE_EXTENSIONS.end(), [&relative](const QString& extension) {
|
||||||
|
return relative.endsWith('.' + extension) || relative.endsWith('.' + extension + ".disabled");
|
||||||
|
}))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
QCryptographicHash sha512(QCryptographicHash::Algorithm::Sha512);
|
||||||
|
|
||||||
|
QFile openFile(file.absoluteFilePath());
|
||||||
|
if (!openFile.open(QFile::ReadOnly)) {
|
||||||
|
qWarning() << "Could not open" << file << "for hashing";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray data = openFile.readAll();
|
||||||
|
if (openFile.error() != QFileDevice::NoError) {
|
||||||
|
qWarning() << "Could not read" << file;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sha512.addData(data);
|
||||||
|
|
||||||
|
auto allMods = mcInstance->loaderModList()->allMods();
|
||||||
|
if (auto modIter = std::find_if(allMods.begin(), allMods.end(), [&file](Mod* mod) { return mod->fileinfo() == file; });
|
||||||
|
modIter != allMods.end()) {
|
||||||
|
const Mod* mod = *modIter;
|
||||||
|
if (mod->metadata() != nullptr) {
|
||||||
|
QUrl& url = mod->metadata()->url;
|
||||||
|
// ensure the url is permitted on modrinth.com
|
||||||
|
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
|
||||||
|
qDebug() << "Resolving" << relative << "from index";
|
||||||
|
|
||||||
|
QCryptographicHash sha1(QCryptographicHash::Algorithm::Sha1);
|
||||||
|
sha1.addData(data);
|
||||||
|
|
||||||
|
ResolvedFile file{ sha1.result().toHex(), sha512.result().toHex(), url.toString(), openFile.size() };
|
||||||
|
resolvedFiles[relative] = file;
|
||||||
|
|
||||||
|
// nice! we've managed to resolve based on local metadata!
|
||||||
|
// no need to enqueue it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Enqueueing" << relative << "for Modrinth query";
|
||||||
|
pendingHashes[relative] = sha512.result().toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAbortable(true);
|
||||||
|
makeApiRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::makeApiRequest()
|
||||||
|
{
|
||||||
|
if (pendingHashes.isEmpty())
|
||||||
|
buildZip();
|
||||||
|
else {
|
||||||
|
QByteArray* response = new QByteArray;
|
||||||
|
task = api.currentVersions(pendingHashes.values(), "sha512", response);
|
||||||
|
connect(task.get(), &NetJob::succeeded, [this, response]() { parseApiResponse(response); });
|
||||||
|
connect(task.get(), &NetJob::failed, this, &ModrinthPackExportTask::emitFailed);
|
||||||
|
task->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::parseApiResponse(const QByteArray* response)
|
||||||
|
{
|
||||||
|
task = nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const QJsonDocument doc = Json::requireDocument(*response);
|
||||||
|
|
||||||
|
QMapIterator<QString, QString> iterator(pendingHashes);
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next();
|
||||||
|
|
||||||
|
const QJsonObject obj = doc[iterator.value()].toObject();
|
||||||
|
if (obj.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const QJsonArray files = obj["files"].toArray();
|
||||||
|
if (auto fileIter = std::find_if(files.begin(), files.end(),
|
||||||
|
[&iterator](const QJsonValue& file) { return file["hashes"]["sha512"] == iterator.value(); });
|
||||||
|
fileIter != files.end()) {
|
||||||
|
// map the file to the url
|
||||||
|
resolvedFiles[iterator.key()] =
|
||||||
|
ResolvedFile{ fileIter->toObject()["hashes"].toObject()["sha1"].toString(), iterator.value(),
|
||||||
|
fileIter->toObject()["url"].toString(), fileIter->toObject()["size"].toInt() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const Json::JsonException& e) {
|
||||||
|
emitFailed(tr("Failed to parse versions response: %1").arg(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingHashes.clear();
|
||||||
|
buildZip();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::buildZip()
|
||||||
|
{
|
||||||
|
setStatus(tr("Adding files..."));
|
||||||
|
|
||||||
|
buildZipFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this]() {
|
||||||
|
QuaZip zip(output);
|
||||||
|
if (!zip.open(QuaZip::mdCreate)) {
|
||||||
|
QFile::remove(output);
|
||||||
|
return BuildZipResult(tr("Could not create file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buildZipFuture.isCanceled())
|
||||||
|
return BuildZipResult();
|
||||||
|
|
||||||
|
QuaZipFile indexFile(&zip);
|
||||||
|
if (!indexFile.open(QIODevice::WriteOnly, QuaZipNewInfo("modrinth.index.json"))) {
|
||||||
|
QFile::remove(output);
|
||||||
|
return BuildZipResult(tr("Could not create index"));
|
||||||
|
}
|
||||||
|
indexFile.write(generateIndex());
|
||||||
|
|
||||||
|
size_t progress = 0;
|
||||||
|
for (const QFileInfo& file : files) {
|
||||||
|
if (buildZipFuture.isCanceled()) {
|
||||||
|
QFile::remove(output);
|
||||||
|
return BuildZipResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
setProgress(progress, files.length());
|
||||||
|
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
|
||||||
|
if (!resolvedFiles.contains(relative) && !JlCompress::compressFile(&zip, file.absoluteFilePath(), "overrides/" + relative)) {
|
||||||
|
QFile::remove(output);
|
||||||
|
return BuildZipResult(tr("Could not read and compress %1").arg(relative));
|
||||||
|
}
|
||||||
|
progress++;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip.close();
|
||||||
|
|
||||||
|
if (zip.getZipError() != 0) {
|
||||||
|
QFile::remove(output);
|
||||||
|
return BuildZipResult(tr("A zip error occurred"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BuildZipResult();
|
||||||
|
});
|
||||||
|
connect(&buildZipWatcher, &QFutureWatcher<BuildZipResult>::finished, this, &ModrinthPackExportTask::finish);
|
||||||
|
buildZipWatcher.setFuture(buildZipFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModrinthPackExportTask::finish()
|
||||||
|
{
|
||||||
|
if (buildZipFuture.isCanceled())
|
||||||
|
emitAborted();
|
||||||
|
else {
|
||||||
|
const BuildZipResult result = buildZipFuture.result();
|
||||||
|
if (result.has_value())
|
||||||
|
emitFailed(result.value());
|
||||||
|
else
|
||||||
|
emitSucceeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ModrinthPackExportTask::generateIndex()
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["formatVersion"] = 1;
|
||||||
|
obj["game"] = "minecraft";
|
||||||
|
obj["name"] = name;
|
||||||
|
obj["versionId"] = version;
|
||||||
|
if (!summary.isEmpty())
|
||||||
|
obj["summary"] = summary;
|
||||||
|
|
||||||
|
if (mcInstance) {
|
||||||
|
auto profile = mcInstance->getPackProfile();
|
||||||
|
// collect all supported components
|
||||||
|
const ComponentPtr minecraft = profile->getComponent("net.minecraft");
|
||||||
|
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
|
||||||
|
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
|
||||||
|
const ComponentPtr forge = profile->getComponent("net.minecraftforge");
|
||||||
|
|
||||||
|
// convert all available components to mrpack dependencies
|
||||||
|
QJsonObject dependencies;
|
||||||
|
if (minecraft != nullptr)
|
||||||
|
dependencies["minecraft"] = minecraft->m_version;
|
||||||
|
if (quilt != nullptr)
|
||||||
|
dependencies["quilt-loader"] = quilt->m_version;
|
||||||
|
if (fabric != nullptr)
|
||||||
|
dependencies["fabric-loader"] = fabric->m_version;
|
||||||
|
if (forge != nullptr)
|
||||||
|
dependencies["forge"] = forge->m_version;
|
||||||
|
|
||||||
|
obj["dependencies"] = dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray files;
|
||||||
|
QMapIterator<QString, ResolvedFile> iterator(resolvedFiles);
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next();
|
||||||
|
|
||||||
|
const ResolvedFile& value = iterator.value();
|
||||||
|
|
||||||
|
QJsonObject file;
|
||||||
|
file["path"] = iterator.key();
|
||||||
|
file["downloads"] = QJsonArray({ iterator.value().url });
|
||||||
|
|
||||||
|
QJsonObject hashes;
|
||||||
|
hashes["sha1"] = value.sha1;
|
||||||
|
hashes["sha512"] = value.sha512;
|
||||||
|
|
||||||
|
file["hashes"] = hashes;
|
||||||
|
file["fileSize"] = value.size;
|
||||||
|
|
||||||
|
files << file;
|
||||||
|
}
|
||||||
|
obj["files"] = files;
|
||||||
|
|
||||||
|
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||||
|
}
|
77
launcher/modplatform/modrinth/ModrinthPackExportTask.h
Normal file
77
launcher/modplatform/modrinth/ModrinthPackExportTask.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 <QFuture>
|
||||||
|
#include <QFutureWatcher>
|
||||||
|
#include "BaseInstance.h"
|
||||||
|
#include "MMCZip.h"
|
||||||
|
#include "minecraft/MinecraftInstance.h"
|
||||||
|
#include "modplatform/modrinth/ModrinthAPI.h"
|
||||||
|
#include "tasks/Task.h"
|
||||||
|
|
||||||
|
class ModrinthPackExportTask : public Task {
|
||||||
|
public:
|
||||||
|
ModrinthPackExportTask(const QString& name,
|
||||||
|
const QString& version,
|
||||||
|
const QString& summary,
|
||||||
|
InstancePtr instance,
|
||||||
|
const QString& output,
|
||||||
|
MMCZip::FilterFunction filter);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void executeTask() override;
|
||||||
|
bool abort() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ResolvedFile {
|
||||||
|
QString sha1, sha512, url;
|
||||||
|
qint64 size;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QStringList PREFIXES;
|
||||||
|
static const QStringList FILE_EXTENSIONS;
|
||||||
|
|
||||||
|
// inputs
|
||||||
|
const QString name, version, summary;
|
||||||
|
const InstancePtr instance;
|
||||||
|
MinecraftInstance* mcInstance;
|
||||||
|
const QDir gameRoot;
|
||||||
|
const QString output;
|
||||||
|
const MMCZip::FilterFunction filter;
|
||||||
|
|
||||||
|
typedef std::optional<QString> BuildZipResult;
|
||||||
|
|
||||||
|
ModrinthAPI api;
|
||||||
|
QFileInfoList files;
|
||||||
|
QMap<QString, QString> pendingHashes;
|
||||||
|
QMap<QString, ResolvedFile> resolvedFiles;
|
||||||
|
Task::Ptr task;
|
||||||
|
QFuture<BuildZipResult> buildZipFuture;
|
||||||
|
QFutureWatcher<BuildZipResult> buildZipWatcher;
|
||||||
|
|
||||||
|
void collectFiles();
|
||||||
|
void collectHashes();
|
||||||
|
void makeApiRequest();
|
||||||
|
void parseApiResponse(const QByteArray* response);
|
||||||
|
void buildZip();
|
||||||
|
void finish();
|
||||||
|
|
||||||
|
QByteArray generateIndex();
|
||||||
|
};
|
@ -50,6 +50,7 @@ void Technic::SingleZipPackInstallTask::executeTask()
|
|||||||
auto job = m_filesNetJob.get();
|
auto job = m_filesNetJob.get();
|
||||||
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
|
||||||
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
|
||||||
|
connect(job, &NetJob::stepProgress, this, &Technic::SingleZipPackInstallTask::propogateStepProgress);
|
||||||
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded()
|
|||||||
|
|
||||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
|
||||||
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
|
||||||
|
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &Technic::SolderPackInstallTask::propogateStepProgress);
|
||||||
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
|
||||||
connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
|
connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted);
|
||||||
m_filesNetJob->start();
|
m_filesNetJob->start();
|
||||||
|
@ -53,7 +53,10 @@ class ByteArraySink : public Sink {
|
|||||||
public:
|
public:
|
||||||
auto init(QNetworkRequest& request) -> Task::State override
|
auto init(QNetworkRequest& request) -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->clear();
|
if (m_output)
|
||||||
|
m_output->clear();
|
||||||
|
else
|
||||||
|
qWarning() << "ByteArraySink did not initialize the buffer because it's not addressable";
|
||||||
if (initAllValidators(request))
|
if (initAllValidators(request))
|
||||||
return Task::State::Running;
|
return Task::State::Running;
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
@ -61,7 +64,10 @@ class ByteArraySink : public Sink {
|
|||||||
|
|
||||||
auto write(QByteArray& data) -> Task::State override
|
auto write(QByteArray& data) -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->append(data);
|
if (m_output)
|
||||||
|
m_output->append(data);
|
||||||
|
else
|
||||||
|
qWarning() << "ByteArraySink did not write the buffer because it's not addressable";
|
||||||
if (writeAllValidators(data))
|
if (writeAllValidators(data))
|
||||||
return Task::State::Running;
|
return Task::State::Running;
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
@ -69,7 +75,10 @@ class ByteArraySink : public Sink {
|
|||||||
|
|
||||||
auto abort() -> Task::State override
|
auto abort() -> Task::State override
|
||||||
{
|
{
|
||||||
m_output->clear();
|
if (m_output)
|
||||||
|
m_output->clear();
|
||||||
|
else
|
||||||
|
qWarning() << "ByteArraySink did not clear the buffer because it's not addressable";
|
||||||
failAllValidators();
|
failAllValidators();
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -36,17 +37,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Download.h"
|
#include "Download.h"
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
|
||||||
#include "ByteArraySink.h"
|
#include "ByteArraySink.h"
|
||||||
#include "ChecksumValidator.h"
|
#include "ChecksumValidator.h"
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "MetaCacheSink.h"
|
#include "MetaCacheSink.h"
|
||||||
|
|
||||||
#include "BuildConfig.h"
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
#include "BuildConfig.h"
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
#include "net/NetAction.h"
|
||||||
|
|
||||||
|
#include "MMCTime.h"
|
||||||
|
#include "StringUtils.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
@ -54,6 +61,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down
|
|||||||
{
|
{
|
||||||
auto dl = makeShared<Download>();
|
auto dl = makeShared<Download>();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("CACHE:") + url.toString());
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
auto md5Node = new ChecksumValidator(QCryptographicHash::Md5);
|
||||||
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
|
auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal));
|
||||||
@ -65,6 +73,7 @@ auto Download::makeByteArray(QUrl url, QByteArray* output, Options options) -> D
|
|||||||
{
|
{
|
||||||
auto dl = makeShared<Download>();
|
auto dl = makeShared<Download>();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("BYTES:") + url.toString());
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
dl->m_sink.reset(new ByteArraySink(output));
|
dl->m_sink.reset(new ByteArraySink(output));
|
||||||
return dl;
|
return dl;
|
||||||
@ -74,6 +83,7 @@ auto Download::makeFile(QUrl url, QString path, Options options) -> Download::Pt
|
|||||||
{
|
{
|
||||||
auto dl = makeShared<Download>();
|
auto dl = makeShared<Download>();
|
||||||
dl->m_url = url;
|
dl->m_url = url;
|
||||||
|
dl->setObjectName(QString("FILE:") + url.toString());
|
||||||
dl->m_options = options;
|
dl->m_options = options;
|
||||||
dl->m_sink.reset(new FileSink(path));
|
dl->m_sink.reset(new FileSink(path));
|
||||||
return dl;
|
return dl;
|
||||||
@ -86,10 +96,10 @@ void Download::addValidator(Validator* v)
|
|||||||
|
|
||||||
void Download::executeTask()
|
void Download::executeTask()
|
||||||
{
|
{
|
||||||
setStatus(tr("Downloading %1").arg(m_url.toString()));
|
setStatus(tr("Downloading %1").arg(StringUtils::truncateUrlHumanFriendly(m_url, 80)));
|
||||||
|
|
||||||
if (getState() == Task::State::AbortedByUser) {
|
if (getState() == Task::State::AbortedByUser) {
|
||||||
qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
|
qCWarning(taskDownloadLogC) << getUid().toString() << "Attempt to start an aborted Download:" << m_url.toString();
|
||||||
emitAborted();
|
emitAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -99,10 +109,10 @@ void Download::executeTask()
|
|||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case State::Succeeded:
|
case State::Succeeded:
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
qDebug() << "Download cache hit " << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download cache hit " << m_url.toString();
|
||||||
return;
|
return;
|
||||||
case State::Running:
|
case State::Running:
|
||||||
qDebug() << "Downloading " << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Downloading " << m_url.toString();
|
||||||
break;
|
break;
|
||||||
case State::Inactive:
|
case State::Inactive:
|
||||||
case State::Failed:
|
case State::Failed:
|
||||||
@ -124,15 +134,21 @@ void Download::executeTask()
|
|||||||
request.setRawHeader("Authorization", token.toUtf8());
|
request.setRawHeader("Authorization", token.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply* rep = m_network->get(request);
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
request.setTransferTimeout();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_last_progress_time = m_clock.now();
|
||||||
|
m_last_progress_bytes = 0;
|
||||||
|
|
||||||
|
QNetworkReply* rep = m_network->get(request);
|
||||||
m_reply.reset(rep);
|
m_reply.reset(rep);
|
||||||
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
|
connect(rep, &QNetworkReply::downloadProgress, this, &Download::downloadProgress);
|
||||||
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
|
connect(rep, &QNetworkReply::finished, this, &Download::downloadFinished);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
connect(rep, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, &QNetworkReply::errorOccurred, this, &Download::downloadError);
|
||||||
#else
|
#else
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Download::downloadError);
|
||||||
#endif
|
#endif
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
|
||||||
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
|
||||||
@ -140,13 +156,39 @@ void Download::executeTask()
|
|||||||
|
|
||||||
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
|
auto now = m_clock.now();
|
||||||
|
auto elapsed = now - m_last_progress_time;
|
||||||
|
|
||||||
|
// use milliseconds for speed precision
|
||||||
|
auto elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
||||||
|
auto bytes_received_since = bytesReceived - m_last_progress_bytes;
|
||||||
|
auto dl_speed_bps = (double)bytes_received_since / elapsed_ms.count() * 1000;
|
||||||
|
auto remaing_time_s = (bytesTotal - bytesReceived) / dl_speed_bps;
|
||||||
|
|
||||||
|
//: Current amount of bytes downloaded, out of the total amount of bytes in the download
|
||||||
|
QString dl_progress =
|
||||||
|
tr("%1 / %2").arg(StringUtils::humanReadableFileSize(bytesReceived)).arg(StringUtils::humanReadableFileSize(bytesTotal));
|
||||||
|
|
||||||
|
QString dl_speed_str;
|
||||||
|
if (elapsed_ms.count() > 0) {
|
||||||
|
auto str_eta = bytesTotal > 0 ? Time::humanReadableDuration(remaing_time_s) : tr("unknown");
|
||||||
|
//: Download speed, in bytes per second (remaining download time in parenthesis)
|
||||||
|
dl_speed_str =
|
||||||
|
tr("%1 /s (%2)").arg(StringUtils::humanReadableFileSize(dl_speed_bps)).arg(str_eta);
|
||||||
|
} else {
|
||||||
|
//: Download speed at 0 bytes per second
|
||||||
|
dl_speed_str = tr("0 B/s");
|
||||||
|
}
|
||||||
|
|
||||||
|
setDetails(dl_progress + "\n" + dl_speed_str);
|
||||||
|
|
||||||
setProgress(bytesReceived, bytesTotal);
|
setProgress(bytesReceived, bytesTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::downloadError(QNetworkReply::NetworkError error)
|
void Download::downloadError(QNetworkReply::NetworkError error)
|
||||||
{
|
{
|
||||||
if (error == QNetworkReply::OperationCanceledError) {
|
if (error == QNetworkReply::OperationCanceledError) {
|
||||||
qCritical() << "Aborted " << m_url.toString();
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Aborted " << m_url.toString();
|
||||||
m_state = State::AbortedByUser;
|
m_state = State::AbortedByUser;
|
||||||
} else {
|
} else {
|
||||||
if (m_options & Option::AcceptLocalFiles) {
|
if (m_options & Option::AcceptLocalFiles) {
|
||||||
@ -156,7 +198,7 @@ void Download::downloadError(QNetworkReply::NetworkError error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
||||||
m_state = State::Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,9 +207,10 @@ void Download::sslErrors(const QList<QSslError>& errors)
|
|||||||
{
|
{
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (auto error : errors) {
|
for (auto error : errors) {
|
||||||
qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Download" << m_url.toString() << "SSL Error #" << i << " : "
|
||||||
|
<< error.errorString();
|
||||||
auto cert = error.certificate();
|
auto cert = error.certificate();
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,17 +253,17 @@ auto Download::handleRedirect() -> bool
|
|||||||
*/
|
*/
|
||||||
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
||||||
if (!redirect.isValid()) {
|
if (!redirect.isValid()) {
|
||||||
qWarning() << "Failed to parse redirect URL:" << redirectStr;
|
qCWarning(taskDownloadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
|
||||||
downloadError(QNetworkReply::ProtocolFailure);
|
downloadError(QNetworkReply::ProtocolFailure);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qDebug() << "Fixed location header:" << redirect;
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Fixed location header:" << redirect;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Location header:" << redirect;
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Location header:" << redirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
m_url = QUrl(redirect.toString());
|
||||||
qDebug() << "Following redirect to " << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||||
startAction(m_network);
|
startAction(m_network);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -230,26 +273,26 @@ void Download::downloadFinished()
|
|||||||
{
|
{
|
||||||
// handle HTTP redirection first
|
// handle HTTP redirection first
|
||||||
if (handleRedirect()) {
|
if (handleRedirect()) {
|
||||||
qDebug() << "Download redirected:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download redirected:" << m_url.toString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the download failed before this point ...
|
// if the download failed before this point ...
|
||||||
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
if (m_state == State::Succeeded) // pretend to succeed so we continue processing :)
|
||||||
{
|
{
|
||||||
qDebug() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed but we are allowed to proceed:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qDebug() << "Download failed in previous step:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed("");
|
emit failed("");
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::AbortedByUser) {
|
} else if (m_state == State::AbortedByUser) {
|
||||||
qDebug() << "Download aborted in previous step:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download aborted in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit aborted();
|
emit aborted();
|
||||||
@ -259,14 +302,14 @@ void Download::downloadFinished()
|
|||||||
// make sure we got all the remaining data, if any
|
// make sure we got all the remaining data, if any
|
||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
if (data.size()) {
|
if (data.size()) {
|
||||||
qDebug() << "Writing extra" << data.size() << "bytes";
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
|
||||||
m_state = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, finalize the whole graph
|
// otherwise, finalize the whole graph
|
||||||
m_state = m_sink->finalize(*m_reply.get());
|
m_state = m_sink->finalize(*m_reply.get());
|
||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qDebug() << "Download failed to finalize:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed("");
|
emit failed("");
|
||||||
@ -274,7 +317,7 @@ void Download::downloadFinished()
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
qDebug() << "Download succeeded:" << m_url.toString();
|
qCDebug(taskDownloadLogC) << getUid().toString() << "Download succeeded:" << m_url.toString();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,11 +327,11 @@ void Download::downloadReadyRead()
|
|||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
m_state = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
if (m_state == State::Failed) {
|
if (m_state == State::Failed) {
|
||||||
qCritical() << "Failed to process response chunk";
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Failed to process response chunk";
|
||||||
}
|
}
|
||||||
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
// qDebug() << "Download" << m_url.toString() << "gained" << data.size() << "bytes";
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "Cannot write download data! illegal status " << m_status;
|
qCCritical(taskDownloadLogC) << getUid().toString() << "Cannot write download data! illegal status " << m_status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -22,6 +23,7 @@
|
|||||||
* Copyright 2013-2021 MultiMC Contributors
|
* Copyright 2013-2021 MultiMC Contributors
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
@ -36,6 +38,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include "HttpMetaCache.h"
|
#include "HttpMetaCache.h"
|
||||||
#include "NetAction.h"
|
#include "NetAction.h"
|
||||||
#include "Sink.h"
|
#include "Sink.h"
|
||||||
@ -70,7 +74,7 @@ class Download : public NetAction {
|
|||||||
protected slots:
|
protected slots:
|
||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
void downloadError(QNetworkReply::NetworkError error) override;
|
||||||
void sslErrors(const QList<QSslError>& errors);
|
void sslErrors(const QList<QSslError>& errors) override;
|
||||||
void downloadFinished() override;
|
void downloadFinished() override;
|
||||||
void downloadReadyRead() override;
|
void downloadReadyRead() override;
|
||||||
|
|
||||||
@ -80,6 +84,10 @@ class Download : public NetAction {
|
|||||||
private:
|
private:
|
||||||
std::unique_ptr<Sink> m_sink;
|
std::unique_ptr<Sink> m_sink;
|
||||||
Options m_options;
|
Options m_options;
|
||||||
|
|
||||||
|
std::chrono::steady_clock m_clock;
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> m_last_progress_time;
|
||||||
|
qint64 m_last_progress_bytes;
|
||||||
};
|
};
|
||||||
} // namespace Net
|
} // namespace Net
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -37,6 +37,8 @@
|
|||||||
|
|
||||||
#include "FileSystem.h"
|
#include "FileSystem.h"
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
Task::State FileSink::init(QNetworkRequest& request)
|
Task::State FileSink::init(QNetworkRequest& request)
|
||||||
@ -48,14 +50,14 @@ Task::State FileSink::init(QNetworkRequest& request)
|
|||||||
|
|
||||||
// create a new save file and open it for writing
|
// create a new save file and open it for writing
|
||||||
if (!FS::ensureFilePathExists(m_filename)) {
|
if (!FS::ensureFilePathExists(m_filename)) {
|
||||||
qCritical() << "Could not create folder for " + m_filename;
|
qCCritical(taskNetLogC) << "Could not create folder for " + m_filename;
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
wroteAnyData = false;
|
wroteAnyData = false;
|
||||||
m_output_file.reset(new QSaveFile(m_filename));
|
m_output_file.reset(new QSaveFile(m_filename));
|
||||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||||
qCritical() << "Could not open " + m_filename + " for writing";
|
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ Task::State FileSink::init(QNetworkRequest& request)
|
|||||||
Task::State FileSink::write(QByteArray& data)
|
Task::State FileSink::write(QByteArray& data)
|
||||||
{
|
{
|
||||||
if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
|
if (!writeAllValidators(data) || m_output_file->write(data) != data.size()) {
|
||||||
qCritical() << "Failed writing into " + m_filename;
|
qCCritical(taskNetLogC) << "Failed writing into " + m_filename;
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
m_output_file.reset();
|
m_output_file.reset();
|
||||||
wroteAnyData = false;
|
wroteAnyData = false;
|
||||||
@ -106,7 +108,7 @@ Task::State FileSink::finalize(QNetworkReply& reply)
|
|||||||
|
|
||||||
// nothing went wrong...
|
// nothing went wrong...
|
||||||
if (!m_output_file->commit()) {
|
if (!m_output_file->commit()) {
|
||||||
qCritical() << "Failed to commit changes to " << m_filename;
|
qCCritical(taskNetLogC) << "Failed to commit changes to " << m_filename;
|
||||||
m_output_file->cancelWriting();
|
m_output_file->cancelWriting();
|
||||||
return Task::State::Failed;
|
return Task::State::Failed;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -44,6 +44,8 @@
|
|||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
auto MetaEntry::getFullPath() -> QString
|
auto MetaEntry::getFullPath() -> QString
|
||||||
{
|
{
|
||||||
// FIXME: make local?
|
// FIXME: make local?
|
||||||
@ -55,7 +57,7 @@ HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
|||||||
saveBatchingTimer.setSingleShot(true);
|
saveBatchingTimer.setSingleShot(true);
|
||||||
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
|
saveBatchingTimer.setTimerType(Qt::VeryCoarseTimer);
|
||||||
|
|
||||||
connect(&saveBatchingTimer, SIGNAL(timeout()), SLOT(SaveNow()));
|
connect(&saveBatchingTimer, &QTimer::timeout, this, &HttpMetaCache::SaveNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpMetaCache::~HttpMetaCache()
|
HttpMetaCache::~HttpMetaCache()
|
||||||
@ -124,7 +126,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
|||||||
// Get rid of old entries, to prevent cache problems
|
// Get rid of old entries, to prevent cache problems
|
||||||
auto current_time = QDateTime::currentSecsSinceEpoch();
|
auto current_time = QDateTime::currentSecsSinceEpoch();
|
||||||
if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
|
if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) {
|
||||||
qWarning() << "Removing cache entry because of old age!";
|
qCWarning(taskNetLogC) << "[HttpMetaCache]" << "Removing cache entry because of old age!";
|
||||||
selected_base.entry_list.remove(resource_path);
|
selected_base.entry_list.remove(resource_path);
|
||||||
return staleEntry(base, resource_path);
|
return staleEntry(base, resource_path);
|
||||||
}
|
}
|
||||||
@ -137,12 +139,12 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
|||||||
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||||
{
|
{
|
||||||
if (!m_entries.contains(stale_entry->m_baseId)) {
|
if (!m_entries.contains(stale_entry->m_baseId)) {
|
||||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
|
qCCritical(taskHttpMetaCacheLogC) << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stale_entry->m_stale) {
|
if (stale_entry->m_stale) {
|
||||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
qCCritical(taskHttpMetaCacheLogC) << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,10 +168,10 @@ void HttpMetaCache::evictAll()
|
|||||||
{
|
{
|
||||||
for (QString& base : m_entries.keys()) {
|
for (QString& base : m_entries.keys()) {
|
||||||
EntryMap& map = m_entries[base];
|
EntryMap& map = m_entries[base];
|
||||||
qDebug() << "Evicting base" << base;
|
qCDebug(taskHttpMetaCacheLogC) << "Evicting base" << base;
|
||||||
for (MetaEntryPtr entry : map.entry_list) {
|
for (MetaEntryPtr entry : map.entry_list) {
|
||||||
if (!evictEntry(entry))
|
if (!evictEntry(entry))
|
||||||
qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
|
qCWarning(taskHttpMetaCacheLogC) << "Unexpected missing cache entry" << entry->m_basePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +269,7 @@ void HttpMetaCache::SaveNow()
|
|||||||
if (m_index_file.isNull())
|
if (m_index_file.isNull())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries";
|
qCDebug(taskHttpMetaCacheLogC) << "Saving metacache with" << m_entries.size() << "entries";
|
||||||
|
|
||||||
QJsonObject toplevel;
|
QJsonObject toplevel;
|
||||||
Json::writeString(toplevel, "version", "1");
|
Json::writeString(toplevel, "version", "1");
|
||||||
@ -302,6 +304,6 @@ void HttpMetaCache::SaveNow()
|
|||||||
try {
|
try {
|
||||||
Json::write(toplevel, m_index_file);
|
Json::write(toplevel, m_index_file);
|
||||||
} catch (const Exception& e) {
|
} catch (const Exception& e) {
|
||||||
qWarning() << e.what();
|
qCWarning(taskHttpMetaCacheLogC) << "Error writing cache:" << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
26
launcher/net/Logging.cpp
Normal file
26
launcher/net/Logging.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 "net/Logging.h"
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(taskNetLogC, "launcher.task.net")
|
||||||
|
Q_LOGGING_CATEGORY(taskDownloadLogC, "launcher.task.net.download")
|
||||||
|
Q_LOGGING_CATEGORY(taskUploadLogC, "launcher.task.net.upload")
|
||||||
|
Q_LOGGING_CATEGORY(taskMetaCacheLogC, "launcher.task.net.metacache")
|
||||||
|
Q_LOGGING_CATEGORY(taskHttpMetaCacheLogC, "launcher.task.net.metacache.http")
|
28
launcher/net/Logging.h
Normal file
28
launcher/net/Logging.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
/*
|
||||||
|
* Prism Launcher - Minecraft Launcher
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.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 <QLoggingCategory>
|
||||||
|
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(taskNetLogC)
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(taskDownloadLogC)
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(taskUploadLogC)
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(taskMetaCacheLogC)
|
||||||
|
Q_DECLARE_LOGGING_CATEGORY(taskHttpMetaCacheLogC)
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@ -39,6 +39,8 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
/** Maximum time to hold a cache entry
|
/** Maximum time to hold a cache entry
|
||||||
@ -97,11 +99,11 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
|||||||
|
|
||||||
{ // Cache lifetime
|
{ // Cache lifetime
|
||||||
if (m_is_eternal) {
|
if (m_is_eternal) {
|
||||||
qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath();
|
qCDebug(taskMetaCacheLogC) << "Adding eternal cache entry:" << m_entry->getFullPath();
|
||||||
m_entry->makeEternal(true);
|
m_entry->makeEternal(true);
|
||||||
} else if (reply.hasRawHeader("Cache-Control")) {
|
} else if (reply.hasRawHeader("Cache-Control")) {
|
||||||
auto cache_control_header = reply.rawHeader("Cache-Control");
|
auto cache_control_header = reply.rawHeader("Cache-Control");
|
||||||
// qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header;
|
qCDebug(taskMetaCacheLogC) << "Parsing 'Cache-Control' header with" << cache_control_header;
|
||||||
|
|
||||||
QRegularExpression max_age_expr("max-age=([0-9]+)");
|
QRegularExpression max_age_expr("max-age=([0-9]+)");
|
||||||
qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
|
qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong();
|
||||||
@ -109,7 +111,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
|||||||
|
|
||||||
} else if (reply.hasRawHeader("Expires")) {
|
} else if (reply.hasRawHeader("Expires")) {
|
||||||
auto expires_header = reply.rawHeader("Expires");
|
auto expires_header = reply.rawHeader("Expires");
|
||||||
// qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header;
|
qCDebug(taskMetaCacheLogC) << "Parsing 'Expires' header with" << expires_header;
|
||||||
|
|
||||||
qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
|
qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch();
|
||||||
m_entry->setMaximumAge(max_age);
|
m_entry->setMaximumAge(max_age);
|
||||||
@ -119,7 +121,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply)
|
|||||||
|
|
||||||
if (reply.hasRawHeader("Age")) {
|
if (reply.hasRawHeader("Age")) {
|
||||||
auto age_header = reply.rawHeader("Age");
|
auto age_header = reply.rawHeader("Age");
|
||||||
// qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header;
|
qCDebug(taskMetaCacheLogC) << "Parsing 'Age' header with" << age_header;
|
||||||
|
|
||||||
qint64 current_age = age_header.toLongLong();
|
qint64 current_age = age_header.toLongLong();
|
||||||
m_entry->setCurrentAge(current_age);
|
m_entry->setCurrentAge(current_age);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -44,7 +45,7 @@
|
|||||||
class NetAction : public Task {
|
class NetAction : public Task {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
protected:
|
protected:
|
||||||
explicit NetAction() : Task() {};
|
explicit NetAction() : Task(){};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Ptr = shared_qobject_ptr<NetAction>;
|
using Ptr = shared_qobject_ptr<NetAction>;
|
||||||
@ -61,6 +62,17 @@ class NetAction : public Task {
|
|||||||
virtual void downloadFinished() = 0;
|
virtual void downloadFinished() = 0;
|
||||||
virtual void downloadReadyRead() = 0;
|
virtual void downloadReadyRead() = 0;
|
||||||
|
|
||||||
|
virtual void sslErrors(const QList<QSslError>& errors) {
|
||||||
|
int i = 1;
|
||||||
|
for (auto error : errors) {
|
||||||
|
qCritical() << "Network SSL Error #" << i << " : " << error.errorString();
|
||||||
|
auto cert = error.certificate();
|
||||||
|
qCritical() << "Certificate in question:\n" << cert.toText();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
|
void startAction(shared_qobject_ptr<QNetworkAccessManager> network)
|
||||||
{
|
{
|
||||||
@ -69,7 +81,7 @@ class NetAction : public Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void executeTask() override {};
|
void executeTask() override{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
* Copyright (C) 2022 Swirl <swurl@swurl.xyz>
|
* Copyright (C) 2022 Swirl <swurl@swurl.xyz>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
@ -47,6 +47,8 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
|
std::array<PasteUpload::PasteTypeInfo, 4> PasteUpload::PasteTypes = {
|
||||||
{{"0x0.st", "https://0x0.st", ""},
|
{{"0x0.st", "https://0x0.st", ""},
|
||||||
{"hastebin", "https://hst.sh", "/documents"},
|
{"hastebin", "https://hst.sh", "/documents"},
|
||||||
@ -147,7 +149,7 @@ void PasteUpload::executeTask()
|
|||||||
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
void PasteUpload::downloadError(QNetworkReply::NetworkError error)
|
||||||
{
|
{
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCritical() << "Network error: " << error;
|
qCCritical(taskUploadLogC) << getUid().toString() << "Network error: " << error;
|
||||||
emitFailed(m_reply->errorString());
|
emitFailed(m_reply->errorString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +168,7 @@ void PasteUpload::downloadFinished()
|
|||||||
{
|
{
|
||||||
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
QString reasonPhrase = m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
|
||||||
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
|
emitFailed(tr("Error: %1 returned unexpected status code %2 %3").arg(m_uploadUrl).arg(statusCode).arg(reasonPhrase));
|
||||||
qCritical() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned unexpected status code " << statusCode << " with body: " << data;
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -187,7 +189,7 @@ void PasteUpload::downloadFinished()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -206,15 +208,15 @@ void PasteUpload::downloadFinished()
|
|||||||
{
|
{
|
||||||
QString error = jsonObj["error"].toString();
|
QString error = jsonObj["error"].toString();
|
||||||
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
emitFailed(tr("Error: %1 returned an error: %2").arg(m_uploadUrl, error));
|
||||||
qCritical() << m_uploadUrl << " returned error: " << error;
|
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
||||||
qCritical() << "Response body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -234,16 +236,16 @@ void PasteUpload::downloadFinished()
|
|||||||
QString error = jsonObj["error"].toString();
|
QString error = jsonObj["error"].toString();
|
||||||
QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
QString message = (jsonObj.contains("message") && jsonObj["message"].isString()) ? jsonObj["message"].toString() : "none";
|
||||||
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
emitFailed(tr("Error: %1 returned an error code: %2\nError message: %3").arg(m_uploadUrl, error, message));
|
||||||
qCritical() << m_uploadUrl << " returned error: " << error;
|
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned error: " << error;
|
||||||
qCritical() << "Error message: " << message;
|
qCCritical(taskUploadLogC) << getUid().toString() << "Error message: " << message;
|
||||||
qCritical() << "Response body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << "Response body: " << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
emitFailed(tr("Error: %1 returned a malformed response body").arg(m_uploadUrl));
|
||||||
qCritical() << m_uploadUrl << " returned malformed response body: " << data;
|
qCCritical(taskUploadLogC) << getUid().toString() << m_uploadUrl << " returned malformed response body: " << data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
* Copyright (C) 2022 Lenny McLennington <lenny@sneed.church>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -42,6 +43,8 @@
|
|||||||
#include "BuildConfig.h"
|
#include "BuildConfig.h"
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include "net/Logging.h"
|
||||||
|
|
||||||
namespace Net {
|
namespace Net {
|
||||||
|
|
||||||
bool Upload::abort()
|
bool Upload::abort()
|
||||||
@ -60,11 +63,11 @@ namespace Net {
|
|||||||
|
|
||||||
void Upload::downloadError(QNetworkReply::NetworkError error) {
|
void Upload::downloadError(QNetworkReply::NetworkError error) {
|
||||||
if (error == QNetworkReply::OperationCanceledError) {
|
if (error == QNetworkReply::OperationCanceledError) {
|
||||||
qCritical() << "Aborted " << m_url.toString();
|
qCCritical(taskUploadLogC) << getUid().toString() << "Aborted " << m_url.toString();
|
||||||
m_state = State::AbortedByUser;
|
m_state = State::AbortedByUser;
|
||||||
} else {
|
} else {
|
||||||
// error happened during download.
|
// error happened during download.
|
||||||
qCritical() << "Failed " << m_url.toString() << " with reason " << error;
|
qCCritical(taskUploadLogC) << getUid().toString() << "Failed " << m_url.toString() << " with reason " << error;
|
||||||
m_state = State::Failed;
|
m_state = State::Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,9 +75,9 @@ namespace Net {
|
|||||||
void Upload::sslErrors(const QList<QSslError> &errors) {
|
void Upload::sslErrors(const QList<QSslError> &errors) {
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (const auto& error : errors) {
|
for (const auto& error : errors) {
|
||||||
qCritical() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
|
qCCritical(taskUploadLogC) << getUid().toString() << "Upload" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
|
||||||
auto cert = error.certificate();
|
auto cert = error.certificate();
|
||||||
qCritical() << "Certificate in question:\n" << cert.toText();
|
qCCritical(taskUploadLogC) << getUid().toString() << "Certificate in question:\n" << cert.toText();
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,17 +120,17 @@ namespace Net {
|
|||||||
*/
|
*/
|
||||||
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
redirect = QUrl(redirectStr, QUrl::TolerantMode);
|
||||||
if (!redirect.isValid()) {
|
if (!redirect.isValid()) {
|
||||||
qWarning() << "Failed to parse redirect URL:" << redirectStr;
|
qCWarning(taskUploadLogC) << getUid().toString() << "Failed to parse redirect URL:" << redirectStr;
|
||||||
downloadError(QNetworkReply::ProtocolFailure);
|
downloadError(QNetworkReply::ProtocolFailure);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qDebug() << "Fixed location header:" << redirect;
|
qCDebug(taskUploadLogC) << getUid().toString() << "Fixed location header:" << redirect;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Location header:" << redirect;
|
qCDebug(taskUploadLogC) << getUid().toString() << "Location header:" << redirect;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_url = QUrl(redirect.toString());
|
m_url = QUrl(redirect.toString());
|
||||||
qDebug() << "Following redirect to " << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Following redirect to " << m_url.toString();
|
||||||
startAction(m_network);
|
startAction(m_network);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -136,25 +139,25 @@ namespace Net {
|
|||||||
// handle HTTP redirection first
|
// handle HTTP redirection first
|
||||||
// very unlikely for post requests, still can happen
|
// very unlikely for post requests, still can happen
|
||||||
if (handleRedirect()) {
|
if (handleRedirect()) {
|
||||||
qDebug() << "Upload redirected:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload redirected:" << m_url.toString();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the download failed before this point ...
|
// if the download failed before this point ...
|
||||||
if (m_state == State::Succeeded) {
|
if (m_state == State::Succeeded) {
|
||||||
qDebug() << "Upload failed but we are allowed to proceed:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed but we are allowed to proceed:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::Failed) {
|
} else if (m_state == State::Failed) {
|
||||||
qDebug() << "Upload failed in previous step:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed("");
|
emit failed("");
|
||||||
return;
|
return;
|
||||||
} else if (m_state == State::AbortedByUser) {
|
} else if (m_state == State::AbortedByUser) {
|
||||||
qDebug() << "Upload aborted in previous step:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload aborted in previous step:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit aborted();
|
emit aborted();
|
||||||
@ -164,21 +167,21 @@ namespace Net {
|
|||||||
// make sure we got all the remaining data, if any
|
// make sure we got all the remaining data, if any
|
||||||
auto data = m_reply->readAll();
|
auto data = m_reply->readAll();
|
||||||
if (data.size()) {
|
if (data.size()) {
|
||||||
qDebug() << "Writing extra" << data.size() << "bytes";
|
qCDebug(taskUploadLogC) << getUid().toString() << "Writing extra" << data.size() << "bytes";
|
||||||
m_state = m_sink->write(data);
|
m_state = m_sink->write(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, finalize the whole graph
|
// otherwise, finalize the whole graph
|
||||||
m_state = m_sink->finalize(*m_reply.get());
|
m_state = m_sink->finalize(*m_reply.get());
|
||||||
if (m_state != State::Succeeded) {
|
if (m_state != State::Succeeded) {
|
||||||
qDebug() << "Upload failed to finalize:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload failed to finalize:" << m_url.toString();
|
||||||
m_sink->abort();
|
m_sink->abort();
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
emit failed("");
|
emit failed("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_reply.reset();
|
m_reply.reset();
|
||||||
qDebug() << "Upload succeeded:" << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload succeeded:" << m_url.toString();
|
||||||
emit succeeded();
|
emit succeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +196,7 @@ namespace Net {
|
|||||||
setStatus(tr("Uploading %1").arg(m_url.toString()));
|
setStatus(tr("Uploading %1").arg(m_url.toString()));
|
||||||
|
|
||||||
if (m_state == State::AbortedByUser) {
|
if (m_state == State::AbortedByUser) {
|
||||||
qWarning() << "Attempt to start an aborted Upload:" << m_url.toString();
|
qCWarning(taskUploadLogC) << getUid().toString() << "Attempt to start an aborted Upload:" << m_url.toString();
|
||||||
emit aborted();
|
emit aborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -202,10 +205,10 @@ namespace Net {
|
|||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case State::Succeeded:
|
case State::Succeeded:
|
||||||
emitSucceeded();
|
emitSucceeded();
|
||||||
qDebug() << "Upload cache hit " << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Upload cache hit " << m_url.toString();
|
||||||
return;
|
return;
|
||||||
case State::Running:
|
case State::Running:
|
||||||
qDebug() << "Uploading " << m_url.toString();
|
qCDebug(taskUploadLogC) << getUid().toString() << "Uploading " << m_url.toString();
|
||||||
break;
|
break;
|
||||||
case State::Inactive:
|
case State::Inactive:
|
||||||
case State::Failed:
|
case State::Failed:
|
||||||
@ -232,9 +235,13 @@ namespace Net {
|
|||||||
QNetworkReply* rep = m_network->post(request, m_post_data);
|
QNetworkReply* rep = m_network->post(request, m_post_data);
|
||||||
|
|
||||||
m_reply.reset(rep);
|
m_reply.reset(rep);
|
||||||
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
|
connect(rep, &QNetworkReply::downloadProgress, this, &Upload::downloadProgress);
|
||||||
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
|
connect(rep, &QNetworkReply::finished, this, &Upload::downloadFinished);
|
||||||
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) // QNetworkReply::errorOccurred added in 5.15
|
||||||
|
connect(rep, &QNetworkReply::errorOccurred, this, &Upload::downloadError);
|
||||||
|
#else
|
||||||
|
connect(rep, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &Upload::downloadError);
|
||||||
|
#endif
|
||||||
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
|
connect(rep, &QNetworkReply::sslErrors, this, &Upload::sslErrors);
|
||||||
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
|
connect(rep, &QNetworkReply::readyRead, this, &Upload::downloadReadyRead);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
/*
|
/*
|
||||||
* PolyMC - Minecraft Launcher
|
* Prism Launcher - Minecraft Launcher
|
||||||
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
* Copyright (c) 2022 flowln <flowlnlnln@gmail.com>
|
||||||
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
|
||||||
|
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -54,7 +55,7 @@ namespace Net {
|
|||||||
protected slots:
|
protected slots:
|
||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
|
||||||
void downloadError(QNetworkReply::NetworkError error) override;
|
void downloadError(QNetworkReply::NetworkError error) override;
|
||||||
void sslErrors(const QList<QSslError> & errors);
|
void sslErrors(const QList<QSslError> & errors) override;
|
||||||
void downloadFinished() override;
|
void downloadFinished() override;
|
||||||
void downloadReadyRead() override;
|
void downloadReadyRead() override;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user