mirror of
https://github.com/PrismLauncher/PrismLauncher.git
synced 2025-06-13 05:37:42 +02:00
Merge remote-tracking branch 'upstream/develop' into rework-settings
Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
239
.github/workflows/blocked-prs.yml
vendored
Normal file
239
.github/workflows/blocked-prs.yml
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
name: Blocked/Stacked Pull Requests Automation
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_id:
|
||||
description: Local Pull Request number to work on
|
||||
required: true
|
||||
type: number
|
||||
|
||||
jobs:
|
||||
blocked_status:
|
||||
name: Check Blocked Status
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.PULL_REQUEST_APP_ID }}
|
||||
private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Setup From Dispatch Event
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: dispatch_event_setup
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
PR_NUMBER: ${{ inputs.pr_id }}
|
||||
run: |
|
||||
# setup env for the rest of the workflow
|
||||
OWNER=$(dirname "${{ github.repository }}")
|
||||
REPO=$(basename "${{ github.repository }}")
|
||||
PR_JSON=$(
|
||||
gh api \
|
||||
-H "Accept: application/vnd.github.raw+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/$OWNER/$REPO/pulls/$PR_NUMBER"
|
||||
)
|
||||
echo "PR_JSON=$PR_JSON" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Environment
|
||||
id: env_setup
|
||||
env:
|
||||
EVENT_PR_JSON: ${{ toJSON(github.event.pull_request) }}
|
||||
run: |
|
||||
# setup env for the rest of the workflow
|
||||
PR_JSON=${PR_JSON:-"$EVENT_PR_JSON"}
|
||||
{
|
||||
echo "REPO=$(jq -r '.base.repo.name' <<< "$PR_JSON")"
|
||||
echo "OWNER=$(jq -r '.base.repo.owner.login' <<< "$PR_JSON")"
|
||||
echo "PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")"
|
||||
echo "JOB_DATA=$(jq -c '
|
||||
{
|
||||
"repo": .base.repo.name,
|
||||
"owner": .base.repo.owner.login,
|
||||
"repoUrl": .base.repo.html_url,
|
||||
"prNumber": .number,
|
||||
"prHeadSha": .head.sha,
|
||||
"prHeadLabel": .head.label,
|
||||
"prBody": .body,
|
||||
"prLabels": (reduce .labels[].name as $l ([]; . + [$l]))
|
||||
}
|
||||
' <<< "$PR_JSON")"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
|
||||
- name: Find Blocked/Stacked PRs in body
|
||||
id: pr_ids
|
||||
run: |
|
||||
prs=$(
|
||||
jq -c '
|
||||
.prBody as $body
|
||||
| (
|
||||
$body |
|
||||
reduce (
|
||||
. | scan("blocked (?:by|on):? #([0-9]+)")
|
||||
| map({
|
||||
"type": "Blocked on",
|
||||
"number": ( . | tonumber )
|
||||
})
|
||||
) as $i ([]; . + [$i[]])
|
||||
) as $bprs
|
||||
| (
|
||||
$body |
|
||||
reduce (
|
||||
. | scan("stacked on:? #([0-9]+)")
|
||||
| map({
|
||||
"type": "Stacked on",
|
||||
"number": ( . | tonumber )
|
||||
})
|
||||
) as $i ([]; . + [$i[]])
|
||||
) as $sprs
|
||||
| ($bprs + $sprs) as $prs
|
||||
| {
|
||||
"blocking": $prs,
|
||||
"numBlocking": ( $prs | length),
|
||||
}
|
||||
' <<< "$JOB_DATA"
|
||||
)
|
||||
echo "prs=$prs" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Collect Blocked PR Data
|
||||
id: blocking_data
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
BLOCKING_PRS: ${{ steps.pr_ids.outputs.prs }}
|
||||
run: |
|
||||
blocked_pr_data=$(
|
||||
while read -r pr_data ; do
|
||||
gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/$OWNER/$REPO/pulls/$(jq -r '.number' <<< "$pr_data")" \
|
||||
| jq -c --arg type "$(jq -r '.type' <<< "$pr_data")" \
|
||||
'
|
||||
. | {
|
||||
"type": $type,
|
||||
"number": .number,
|
||||
"merged": .merged,
|
||||
"labels": (reduce .labels[].name as $l ([]; . + [$l])),
|
||||
"basePrUrl": .html_url,
|
||||
"baseRepoName": .head.repo.name,
|
||||
"baseRepoOwner": .head.repo.owner.login,
|
||||
"baseRepoUrl": .head.repo.html_url,
|
||||
"baseSha": .head.sha,
|
||||
"baseRefName": .head.ref,
|
||||
}
|
||||
'
|
||||
done < <(jq -c '.blocking[]' <<< "$BLOCKING_PRS") | jq -c -s
|
||||
)
|
||||
{
|
||||
echo "data=$blocked_pr_data";
|
||||
echo "all_merged=$(jq -r 'all(.[].merged; .)' <<< "$blocked_pr_data")";
|
||||
echo "current_blocking=$(jq -c 'map( select( .merged | not ) | .number )' <<< "$blocked_pr_data" )";
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Add 'blocked' Label is Missing
|
||||
id: label_blocked
|
||||
if: (fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0) && !contains(fromJSON(env.JOB_DATA).prLabels, 'blocked') && !fromJSON(steps.blocking_data.outputs.all_merged)
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
run: |
|
||||
gh -R ${{ github.repository }} issue edit --add-label 'blocked' "$PR_NUMBER"
|
||||
|
||||
- name: Remove 'blocked' Label if All Dependencies Are Merged
|
||||
id: unlabel_blocked
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0 && fromJSON(steps.blocking_data.outputs.all_merged)
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
run: |
|
||||
gh -R ${{ github.repository }} issue edit --remove-label 'blocked' "$PR_NUMBER"
|
||||
|
||||
- name: Apply 'blocking' Label to Unmerged Dependencies
|
||||
id: label_blocking
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
BLOCKING_ISSUES: ${{ steps.blocking_data.outputs.current_blocking }}
|
||||
run: |
|
||||
while read -r pr ; do
|
||||
gh -R ${{ github.repository }} issue edit --add-label 'blocking' "$pr" || true
|
||||
done < <(jq -c '.[]' <<< "$BLOCKING_ISSUES")
|
||||
|
||||
- name: Apply Blocking PR Status Check
|
||||
id: blocked_check
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }}
|
||||
run: |
|
||||
pr_head_sha=$(jq -r '.prHeadSha' <<< "$JOB_DATA")
|
||||
# create commit Status, overwrites previous identical context
|
||||
while read -r pr_data ; do
|
||||
DESC=$(
|
||||
jq -r ' "Blocking PR #" + (.number | tostring) + " is " + (if .merged then "" else "not yet " end) + "merged"' <<< "$pr_data"
|
||||
)
|
||||
gh api \
|
||||
--method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${OWNER}/${REPO}/statuses/${pr_head_sha}" \
|
||||
-f "state=$(jq -r 'if .merged then "success" else "failure" end' <<< "$pr_data")" \
|
||||
-f "target_url=$(jq -r '.basePrUrl' <<< "$pr_data" )" \
|
||||
-f "description=$DESC" \
|
||||
-f "context=ci/blocking-pr-check:$(jq '.number' <<< "$pr_data")"
|
||||
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
||||
|
||||
- name: Context Comment
|
||||
id: generate-comment
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
|
||||
continue-on-error: true
|
||||
env:
|
||||
BLOCKING_DATA: ${{ steps.blocking_data.outputs.data }}
|
||||
run: |
|
||||
COMMENT_PATH="$(pwd)/temp_comment_file.txt"
|
||||
echo '<h3>PR Dependencies :pushpin:</h3>' > "$COMMENT_PATH"
|
||||
echo >> "$COMMENT_PATH"
|
||||
pr_head_label=$(jq -r '.prHeadLabel' <<< "$JOB_DATA")
|
||||
while read -r pr_data ; do
|
||||
base_pr=$(jq -r '.number' <<< "$pr_data")
|
||||
base_ref_name=$(jq -r '.baseRefName' <<< "$pr_data")
|
||||
base_repo_owner=$(jq -r '.baseRepoOwner' <<< "$pr_data")
|
||||
base_repo_name=$(jq -r '.baseRepoName' <<< "$pr_data")
|
||||
compare_url="https://github.com/$base_repo_owner/$base_repo_name/compare/$base_ref_name...$pr_head_label"
|
||||
status=$(jq -r 'if .merged then ":heavy_check_mark: Merged" else ":x: Not Merged" end' <<< "$pr_data")
|
||||
type=$(jq -r '.type' <<< "$pr_data")
|
||||
echo " - $type #$base_pr $status [(compare)]($compare_url)" >> "$COMMENT_PATH"
|
||||
done < <(jq -c '.[]' <<< "$BLOCKING_DATA")
|
||||
|
||||
{
|
||||
echo 'body<<EOF';
|
||||
cat "${COMMENT_PATH}";
|
||||
echo 'EOF';
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 💬 PR Comment
|
||||
if: fromJSON(steps.pr_ids.outputs.prs).numBlocking > 0
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
COMMENT_BODY: ${{ steps.generate-comment.outputs.body }}
|
||||
run: |
|
||||
gh -R ${{ github.repository }} issue comment "$PR_NUMBER" \
|
||||
--body "$COMMENT_BODY" \
|
||||
--create-if-none \
|
||||
--edit-last
|
||||
|
75
.github/workflows/build.yml
vendored
75
.github/workflows/build.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
- os: ubuntu-22.04
|
||||
qt_ver: 5
|
||||
qt_host: linux
|
||||
qt_arch: ""
|
||||
@ -63,8 +63,11 @@ jobs:
|
||||
qt_ver: 6
|
||||
qt_host: linux
|
||||
qt_arch: ""
|
||||
qt_version: "6.5.3"
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
linuxdeploy_hash: "4648f278ab3ef31f819e67c30d50f462640e5365a77637d7e6f2ad9fd0b4522a linuxdeploy-x86_64.AppImage"
|
||||
linuxdeploy_qt_hash: "15106be885c1c48a021198e7e1e9a48ce9d02a86dd0a1848f00bdbf3c1c92724 linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
appimageupdate_hash: "f1747cf60058e99f1bb9099ee9787d16c10241313b7acec81810ea1b1e568c11 AppImageUpdate-x86_64.AppImage"
|
||||
|
||||
- os: windows-2022
|
||||
name: "Windows-MinGW-w64"
|
||||
@ -106,14 +109,6 @@ jobs:
|
||||
qt_version: "6.8.1"
|
||||
qt_modules: "qt5compat qtimageformats qtnetworkauth"
|
||||
|
||||
- os: macos-14
|
||||
name: macOS-Legacy
|
||||
macosx_deployment_target: 10.13
|
||||
qt_ver: 5
|
||||
qt_host: mac
|
||||
qt_version: "5.15.2"
|
||||
qt_modules: "qtnetworkauth"
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
@ -168,9 +163,15 @@ jobs:
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Use ccache on Debug builds only
|
||||
if: inputs.build_type == 'Debug'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
||||
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
@ -187,11 +188,16 @@ jobs:
|
||||
ccache -p # Show config
|
||||
ccache -z # Zero stats
|
||||
|
||||
- name: Use ccache on Debug builds only
|
||||
if: inputs.build_type == 'Debug'
|
||||
shell: bash
|
||||
- name: Configure ccache (Windows MSVC)
|
||||
if: ${{ runner.os == 'Windows' && matrix.msystem == '' && inputs.build_type == 'Debug' }}
|
||||
run: |
|
||||
echo "CCACHE_VAR=ccache" >> $GITHUB_ENV
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
|
||||
echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
|
||||
echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
|
||||
echo "TrackFileAccess=false" >> $env:GITHUB_ENV
|
||||
# Needed for ccache, but also speeds up compile
|
||||
echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Set short version
|
||||
shell: bash
|
||||
@ -249,14 +255,21 @@ jobs:
|
||||
|
||||
- name: Prepare AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
env:
|
||||
APPIMAGEUPDATE_HASH: ${{ matrix.appimageupdate_hash }}
|
||||
LINUXDEPLOY_HASH: ${{ matrix.linuxdeploy_hash }}
|
||||
LINUXDEPLOY_QT_HASH: ${{ matrix.linuxdeploy_qt_hash }}
|
||||
run: |
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage"
|
||||
wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20250213-1/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||||
|
||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/continuous/AppImageUpdate-x86_64.AppImage"
|
||||
wget "https://github.com/AppImageCommunity/AppImageUpdate/releases/download/2.0.0-alpha-1-20241225/AppImageUpdate-x86_64.AppImage"
|
||||
|
||||
sudo apt install libopengl0 libfuse2
|
||||
sha256sum -c - <<< "$LINUXDEPLOY_HASH"
|
||||
sha256sum -c - <<< "$LINUXDEPLOY_QT_HASH"
|
||||
sha256sum -c - <<< "$APPIMAGEUPDATE_HASH"
|
||||
|
||||
sudo apt install libopengl0
|
||||
|
||||
- name: Add QT_HOST_PATH var (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'arm64'
|
||||
@ -278,11 +291,6 @@ jobs:
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja
|
||||
|
||||
- name: Configure CMake (macOS-Legacy)
|
||||
if: runner.os == 'macOS' && matrix.qt_ver == 5
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -DCMAKE_OSX_ARCHITECTURES="x86_64" -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
shell: msys2 {0}
|
||||
@ -290,19 +298,14 @@ jobs:
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=6 -DCMAKE_OBJDUMP=/mingw64/bin/objdump.exe -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MSVC)
|
||||
if: runner.os == 'Windows' && matrix.msystem == ''
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture != 'arm64'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }} -G Ninja
|
||||
|
||||
- name: Configure CMake (Windows MSVC arm64)
|
||||
if: runner.os == 'Windows' && matrix.msystem == '' && matrix.architecture == 'arm64'
|
||||
run: |
|
||||
cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_ENABLE_JAVA_DOWNLOADER=ON -DLauncher_BUILD_PLATFORM=official -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreadedDLL" -A${{ matrix.architecture}} -DLauncher_FORCE_BUNDLED_LIBS=ON -DLauncher_BUILD_ARTIFACT=${{ matrix.name }}-Qt${{ matrix.qt_ver }}
|
||||
# https://github.com/ccache/ccache/wiki/MS-Visual-Studio (I coudn't figure out the compiler prefix)
|
||||
if ("${{ env.CCACHE_VAR }}")
|
||||
{
|
||||
Copy-Item C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/ccache.exe -Destination C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/cl.exe
|
||||
echo "CLToolExe=cl.exe" >> $env:GITHUB_ENV
|
||||
echo "CLToolPath=C:/ProgramData/chocolatey/lib/ccache/tools/ccache-4.7.1-windows-x86_64/" >> $env:GITHUB_ENV
|
||||
echo "TrackFileAccess=false" >> $env:GITHUB_ENV
|
||||
}
|
||||
# Needed for ccache, but also speeds up compile
|
||||
echo "UseMultiToolTask=true" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Configure CMake (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
|
56
.github/workflows/merge-blocking-pr.yml
vendored
Normal file
56
.github/workflows/merge-blocking-pr.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
name: Merged Blocking Pull Request Automation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
update-blocked-status:
|
||||
name: Update Blocked Status
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# a pr that was a `blocking:<id>` label was merged.
|
||||
# find the open pr's it was blocked by and trigger a refresh of their state
|
||||
if: github.event.pull_request.merged == true && contains( join( github.event.pull_request.labels.*.name, ',' ), 'blocking' )
|
||||
|
||||
steps:
|
||||
- name: Generate token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ vars.PULL_REQUEST_APP_ID }}
|
||||
private-key: ${{ secrets.PULL_REQUEST_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Gather Dependent PRs
|
||||
id: gather_deps
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
blocked_prs=$(
|
||||
gh -R ${{ github.repository }} pr list --label 'blocked' --json 'number,body' \
|
||||
| jq -c --argjson pr "${{ github.event.pull_request.number }}" '
|
||||
reduce ( .[] | select(
|
||||
.body |
|
||||
scan("(?:blocked (?:by|on)|stacked on):? #([0-9]+)") |
|
||||
map(tonumber) |
|
||||
any(.[]; . == $pr)
|
||||
)) as $i ([]; . + [$i])
|
||||
'
|
||||
)
|
||||
{
|
||||
echo "deps=$blocked_prs"
|
||||
echo "numdeps=$(jq -r '. | length' <<< "$blocked_prs")"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Trigger Blocked PR Workflows for Dependants
|
||||
if: fromJSON(steps.gather_deps.outputs.numdeps) > 0
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
DEPS: ${{ steps.gather_deps.outputs.deps }}
|
||||
run: |
|
||||
while read -r pr ; do
|
||||
gh -R ${{ github.repository }} workflow run 'blocked-prs.yml' -r "${{ github.ref_name }}" -f pr_id="$pr"
|
||||
done < <(jq -c '.[].number' <<< "$DEPS")
|
||||
|
42
.github/workflows/nix.yml
vendored
42
.github/workflows/nix.yml
vendored
@ -2,19 +2,30 @@ name: Nix
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "!.github/workflows/nix.yml"
|
||||
- "flatpak/"
|
||||
- "scripts/"
|
||||
|
||||
- ".git*"
|
||||
- ".envrc"
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
- "flatpak/**"
|
||||
- "!COPYING.md"
|
||||
- "renovate.json"
|
||||
pull_request_target:
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "flatpak/"
|
||||
- "scripts/"
|
||||
|
||||
- ".git*"
|
||||
- ".envrc"
|
||||
- "**.md"
|
||||
- "**/LICENSE"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
- "flatpak/**"
|
||||
- "!COPYING.md"
|
||||
- "renovate.json"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@ -60,7 +71,7 @@ jobs:
|
||||
|
||||
# For PRs
|
||||
- name: Setup Nix Magic Cache
|
||||
if: ${{ env.USE_DETERMINATE }}
|
||||
if: ${{ env.USE_DETERMINATE == 'true' }}
|
||||
uses: DeterminateSystems/flakehub-cache-action@v1
|
||||
|
||||
# For in-tree builds
|
||||
@ -76,15 +87,18 @@ jobs:
|
||||
nix flake check --print-build-logs --show-trace
|
||||
|
||||
- name: Build debug package
|
||||
if: ${{ env.DEBUG }}
|
||||
if: ${{ env.DEBUG == 'true' }}
|
||||
run: |
|
||||
nix build \
|
||||
--no-link --print-build-logs --print-out-paths \
|
||||
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Build release package
|
||||
if: ${{ !env.DEBUG }}
|
||||
if: ${{ env.DEBUG == 'false' }}
|
||||
env:
|
||||
TAG: ${{ github.ref_name }}
|
||||
SYSTEM: ${{ matrix.system }}
|
||||
run: |
|
||||
nix build \
|
||||
--no-link --print-build-logs --print-out-paths \
|
||||
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"
|
||||
nix build --no-link --print-out-paths .#prismlauncher \
|
||||
| tee -a "$GITHUB_STEP_SUMMARY" \
|
||||
| xargs cachix pin prismlauncher "$TAG"-"$SYSTEM"
|
||||
|
22
.github/workflows/publish.yml
vendored
22
.github/workflows/publish.yml
vendored
@ -8,28 +8,6 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
flakehub:
|
||||
name: FlakeHub
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
|
||||
- name: Publish on FlakeHub
|
||||
uses: determinatesystems/flakehub-push@v5
|
||||
with:
|
||||
visibility: "public"
|
||||
|
||||
winget:
|
||||
name: Winget
|
||||
|
||||
|
2
.github/workflows/trigger_release.yml
vendored
2
.github/workflows/trigger_release.yml
vendored
@ -49,7 +49,6 @@ jobs:
|
||||
mv PrismLauncher-Linux-Qt5-Portable*/PrismLauncher-portable.tar.gz PrismLauncher-Linux-Qt5-Portable-${{ env.VERSION }}.tar.gz
|
||||
mv PrismLauncher-*.AppImage/PrismLauncher-*.AppImage PrismLauncher-Linux-x86_64.AppImage
|
||||
mv PrismLauncher-*.AppImage.zsync/PrismLauncher-*.AppImage.zsync PrismLauncher-Linux-x86_64.AppImage.zsync
|
||||
mv PrismLauncher-macOS-Legacy*/PrismLauncher.zip PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||
mv PrismLauncher-macOS*/PrismLauncher.zip PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||
|
||||
tar --exclude='.git' -czf PrismLauncher-${{ env.VERSION }}.tar.gz PrismLauncher-${{ env.VERSION }}
|
||||
@ -104,5 +103,4 @@ jobs:
|
||||
PrismLauncher-Windows-MSVC-Portable-${{ env.VERSION }}.zip
|
||||
PrismLauncher-Windows-MSVC-Setup-${{ env.VERSION }}.exe
|
||||
PrismLauncher-macOS-${{ env.VERSION }}.zip
|
||||
PrismLauncher-macOS-Legacy-${{ env.VERSION }}.zip
|
||||
PrismLauncher-${{ env.VERSION }}.tar.gz
|
||||
|
2
.github/workflows/update-flake.yml
vendored
2
.github/workflows/update-flake.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
|
||||
- uses: cachix/install-nix-action@d1ca217b388ee87b2507a9a93bf01368bde7cec2 # v31
|
||||
|
||||
- uses: DeterminateSystems/update-flake-lock@v24
|
||||
with:
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -21,6 +21,7 @@ CMakeCache.txt
|
||||
/.vs
|
||||
cmake-build-*/
|
||||
Debug
|
||||
compile_commands.json
|
||||
|
||||
# Build dirs
|
||||
build
|
||||
@ -47,8 +48,12 @@ run/
|
||||
|
||||
# Nix/NixOS
|
||||
.direnv/
|
||||
.pre-commit-config.yaml
|
||||
## Used when manually invoking stdenv phases
|
||||
outputs/
|
||||
## Regular artifacts
|
||||
result
|
||||
result-*
|
||||
repl-result-*
|
||||
|
||||
# Flatpak
|
||||
.flatpak-builder
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,9 +4,6 @@
|
||||
[submodule "libraries/tomlplusplus"]
|
||||
path = libraries/tomlplusplus
|
||||
url = https://github.com/marzer/tomlplusplus.git
|
||||
[submodule "libraries/filesystem"]
|
||||
path = libraries/filesystem
|
||||
url = https://github.com/gulrak/filesystem
|
||||
[submodule "libraries/libnbtplusplus"]
|
||||
path = libraries/libnbtplusplus
|
||||
url = https://github.com/PrismLauncher/libnbtplusplus.git
|
||||
|
@ -99,6 +99,12 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTOML_ENABLE_FLOAT16=0")
|
||||
# set CXXFLAGS for build targets
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS_RELEASE}")
|
||||
|
||||
# Export compile commands for debug builds if we can (useful in LSPs like clangd)
|
||||
# https://cmake.org/cmake/help/v3.31/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html
|
||||
if(CMAKE_GENERATOR STREQUAL "Unix Makefiles" OR CMAKE_GENERATOR STREQUAL "Ninja" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
endif()
|
||||
|
||||
option(DEBUG_ADDRESS_SANITIZER "Enable Address Sanitizer in Debug builds" OFF)
|
||||
|
||||
# If this is a Debug build turn on address sanitiser
|
||||
@ -189,10 +195,11 @@ set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE S
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 10)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
set(Launcher_VERSION_PATCH 0)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
|
||||
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0")
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}")
|
||||
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_PATCH}.0")
|
||||
set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_PATCH},0")
|
||||
|
||||
# Build platform.
|
||||
set(Launcher_BUILD_PLATFORM "unknown" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.")
|
||||
@ -236,7 +243,7 @@ set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT ON)
|
||||
# differing Linux/BSD/etc distributions. Downstream packagers should be explicitly opt-ing into this
|
||||
# feature if they know it will work with their distribution.
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
|
||||
set(Launcher_ENABLE_JAVA_DOWNLOADER_DEFAULT OFF)
|
||||
endif()
|
||||
|
||||
# Java downloader
|
||||
@ -357,9 +364,6 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
# Find toml++
|
||||
find_package(tomlplusplus 3.2.0 QUIET)
|
||||
|
||||
# Find ghc_filesystem
|
||||
find_package(ghc_filesystem QUIET)
|
||||
|
||||
# Find cmark
|
||||
find_package(cmark QUIET)
|
||||
endif()
|
||||
@ -377,7 +381,7 @@ set(Launcher_ENABLE_UPDATER NO)
|
||||
set(Launcher_BUILD_UPDATER NO)
|
||||
|
||||
if (NOT APPLE AND (NOT Launcher_UPDATER_GITHUB_REPO STREQUAL "" AND NOT Launcher_BUILD_ARTIFACT STREQUAL ""))
|
||||
set(Launcher_BUILD_UPDATER YES)
|
||||
set(Launcher_BUILD_UPDATER YES)
|
||||
endif()
|
||||
|
||||
if(NOT (UNIX AND APPLE))
|
||||
@ -554,12 +558,6 @@ else()
|
||||
endif()
|
||||
add_subdirectory(libraries/gamemode)
|
||||
add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API
|
||||
if (NOT ghc_filesystem_FOUND)
|
||||
message(STATUS "Using bundled ghc_filesystem")
|
||||
add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS
|
||||
else()
|
||||
message(STATUS "Using system ghc_filesystem")
|
||||
endif()
|
||||
add_subdirectory(libraries/qdcss) # css parser
|
||||
|
||||
############################### Built Artifacts ###############################
|
||||
|
22
COPYING.md
22
COPYING.md
@ -362,28 +362,6 @@
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
## gulrak/filesystem
|
||||
|
||||
Copyright (c) 2018, Steffen Schümann <s.schuemann@pobox.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## Breeze icons
|
||||
|
||||
Copyright (C) 2014 Uri Herrera <uri_herrera@nitrux.in> and others
|
||||
|
@ -34,8 +34,8 @@
|
||||
*/
|
||||
|
||||
#include <qstringliteral.h>
|
||||
#include "BuildConfig.h"
|
||||
#include <QObject>
|
||||
#include "BuildConfig.h"
|
||||
|
||||
const Config BuildConfig;
|
||||
|
||||
@ -58,6 +58,7 @@ Config::Config()
|
||||
// Version information
|
||||
VERSION_MAJOR = @Launcher_VERSION_MAJOR@;
|
||||
VERSION_MINOR = @Launcher_VERSION_MINOR@;
|
||||
VERSION_PATCH = @Launcher_VERSION_PATCH@;
|
||||
|
||||
BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@";
|
||||
BUILD_ARTIFACT = "@Launcher_BUILD_ARTIFACT@";
|
||||
@ -74,14 +75,13 @@ Config::Config()
|
||||
MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@";
|
||||
MAC_SPARKLE_APPCAST_URL = "@MACOSX_SPARKLE_UPDATE_FEED_URL@";
|
||||
|
||||
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty())
|
||||
{
|
||||
if (!MAC_SPARKLE_PUB_KEY.isEmpty() && !MAC_SPARKLE_APPCAST_URL.isEmpty()) {
|
||||
UPDATER_ENABLED = true;
|
||||
} else if(!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
|
||||
} else if (!UPDATER_GITHUB_REPO.isEmpty() && !BUILD_ARTIFACT.isEmpty()) {
|
||||
UPDATER_ENABLED = true;
|
||||
}
|
||||
|
||||
#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
|
||||
#cmakedefine01 Launcher_ENABLE_JAVA_DOWNLOADER
|
||||
JAVA_DOWNLOADER_ENABLED = Launcher_ENABLE_JAVA_DOWNLOADER;
|
||||
|
||||
GIT_COMMIT = "@Launcher_GIT_COMMIT@";
|
||||
@ -89,27 +89,19 @@ Config::Config()
|
||||
GIT_REFSPEC = "@Launcher_GIT_REFSPEC@";
|
||||
|
||||
// Assume that builds outside of Git repos are "stable"
|
||||
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND")
|
||||
|| GIT_TAG == QStringLiteral("GITDIR-NOTFOUND")
|
||||
|| GIT_REFSPEC == QStringLiteral("")
|
||||
|| GIT_TAG == QStringLiteral("GIT-NOTFOUND"))
|
||||
{
|
||||
if (GIT_REFSPEC == QStringLiteral("GITDIR-NOTFOUND") || GIT_TAG == QStringLiteral("GITDIR-NOTFOUND") ||
|
||||
GIT_REFSPEC == QStringLiteral("") || GIT_TAG == QStringLiteral("GIT-NOTFOUND")) {
|
||||
GIT_REFSPEC = "refs/heads/stable";
|
||||
GIT_TAG = versionString();
|
||||
GIT_COMMIT = "";
|
||||
}
|
||||
|
||||
if (GIT_REFSPEC.startsWith("refs/heads/"))
|
||||
{
|
||||
if (GIT_REFSPEC.startsWith("refs/heads/")) {
|
||||
VERSION_CHANNEL = GIT_REFSPEC;
|
||||
VERSION_CHANNEL.remove("refs/heads/");
|
||||
}
|
||||
else if (!GIT_COMMIT.isEmpty())
|
||||
{
|
||||
VERSION_CHANNEL.remove("refs/heads/");
|
||||
} else if (!GIT_COMMIT.isEmpty()) {
|
||||
VERSION_CHANNEL = GIT_COMMIT.mid(0, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
VERSION_CHANNEL = "unknown";
|
||||
}
|
||||
|
||||
@ -136,7 +128,7 @@ Config::Config()
|
||||
|
||||
QString Config::versionString() const
|
||||
{
|
||||
return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR);
|
||||
return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_PATCH);
|
||||
}
|
||||
|
||||
QString Config::printableVersionString() const
|
||||
@ -144,8 +136,7 @@ QString Config::printableVersionString() const
|
||||
QString vstr = versionString();
|
||||
|
||||
// If the build is not a main release, append the channel
|
||||
if(VERSION_CHANNEL != "stable" && GIT_TAG != vstr)
|
||||
{
|
||||
if (VERSION_CHANNEL != "stable" && GIT_TAG != vstr) {
|
||||
vstr += "-" + VERSION_CHANNEL;
|
||||
}
|
||||
return vstr;
|
||||
@ -162,4 +153,3 @@ QString Config::systemID() const
|
||||
{
|
||||
return QStringLiteral("%1 %2 %3").arg(COMPILER_TARGET_SYSTEM, COMPILER_TARGET_SYSTEM_VERSION, COMPILER_TARGET_SYSTEM_PROCESSOR);
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,8 @@ class Config {
|
||||
int VERSION_MAJOR;
|
||||
/// The minor version number.
|
||||
int VERSION_MINOR;
|
||||
/// The patch version number.
|
||||
int VERSION_PATCH;
|
||||
|
||||
/**
|
||||
* The version channel
|
||||
|
13
default.nix
13
default.nix
@ -1,9 +1,4 @@
|
||||
(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
|
||||
(import (fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec.tar.gz";
|
||||
sha256 = "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=";
|
||||
}) { src = ./.; }).defaultNix
|
||||
|
39
flake.lock
generated
39
flake.lock
generated
@ -1,21 +1,5 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"libnbtplusplus": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
@ -32,28 +16,13 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1731533336,
|
||||
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1741851582,
|
||||
"narHash": "sha256-cPfs8qMccim2RBgtKGF+x9IBCduRvd/N5F4nYpU0TVE=",
|
||||
"lastModified": 1744463964,
|
||||
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6607cf789e541e7873d40d3a8f7815ea92204f32",
|
||||
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -65,9 +34,7 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"libnbtplusplus": "libnbtplusplus",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
|
151
flake.nix
151
flake.nix
@ -15,28 +15,6 @@
|
||||
url = "github:PrismLauncher/libnbtplusplus";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
|
||||
/*
|
||||
Inputs below this are optional and can be removed
|
||||
|
||||
```
|
||||
{
|
||||
inputs.prismlauncher = {
|
||||
url = "github:PrismLauncher/PrismLauncher";
|
||||
inputs = {
|
||||
flake-compat.follows = "";
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
*/
|
||||
|
||||
flake-compat = {
|
||||
url = "github:edolstra/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
@ -44,9 +22,8 @@
|
||||
self,
|
||||
nixpkgs,
|
||||
libnbtplusplus,
|
||||
nix-filter,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
@ -58,27 +35,128 @@
|
||||
forAllSystems = lib.genAttrs systems;
|
||||
nixpkgsFor = forAllSystems (system: nixpkgs.legacyPackages.${system});
|
||||
in
|
||||
|
||||
{
|
||||
checks = forAllSystems (
|
||||
system:
|
||||
|
||||
let
|
||||
checks' = nixpkgsFor.${system}.callPackage ./nix/checks.nix { inherit self; };
|
||||
pkgs = nixpkgsFor.${system};
|
||||
llvm = pkgs.llvmPackages_19;
|
||||
in
|
||||
lib.filterAttrs (_: lib.isDerivation) checks'
|
||||
|
||||
{
|
||||
formatting =
|
||||
pkgs.runCommand "check-formatting"
|
||||
{
|
||||
nativeBuildInputs = with pkgs; [
|
||||
deadnix
|
||||
llvm.clang-tools
|
||||
markdownlint-cli
|
||||
nixfmt-rfc-style
|
||||
statix
|
||||
];
|
||||
}
|
||||
''
|
||||
cd ${self}
|
||||
|
||||
echo "Running clang-format...."
|
||||
clang-format --dry-run --style='file' --Werror */**.{c,cc,cpp,h,hh,hpp}
|
||||
|
||||
echo "Running deadnix..."
|
||||
deadnix --fail
|
||||
|
||||
echo "Running markdownlint..."
|
||||
markdownlint --dot .
|
||||
|
||||
echo "Running nixfmt..."
|
||||
find -type f -name '*.nix' -exec nixfmt --check {} +
|
||||
|
||||
echo "Running statix"
|
||||
statix check .
|
||||
|
||||
touch $out
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
devShells = forAllSystems (
|
||||
system:
|
||||
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
llvm = pkgs.llvmPackages_19;
|
||||
|
||||
packages' = self.packages.${system};
|
||||
|
||||
welcomeMessage = ''
|
||||
Welcome to the Prism Launcher repository! 🌈
|
||||
|
||||
We just set some things up for you. To get building, you can run:
|
||||
|
||||
```
|
||||
$ cd "$cmakeBuildDir"
|
||||
$ ninjaBuildPhase
|
||||
$ ninjaInstallPhase
|
||||
```
|
||||
|
||||
Feel free to ask any questions in our Discord server or Matrix space:
|
||||
- https://prismlauncher.org/discord
|
||||
- https://matrix.to/#/#prismlauncher:matrix.org
|
||||
|
||||
And thanks for helping out :)
|
||||
'';
|
||||
|
||||
# Re-use our package wrapper to wrap our development environment
|
||||
qt-wrapper-env = packages'.prismlauncher.overrideAttrs (old: {
|
||||
name = "qt-wrapper-env";
|
||||
|
||||
# Required to use script-based makeWrapper below
|
||||
strictDeps = true;
|
||||
|
||||
# We don't need/want the unwrapped Prism package
|
||||
paths = [ ];
|
||||
|
||||
nativeBuildInputs = old.nativeBuildInputs or [ ] ++ [
|
||||
# Ensure the wrapper is script based so it can be sourced
|
||||
pkgs.makeWrapper
|
||||
];
|
||||
|
||||
# Inspired by https://discourse.nixos.org/t/python-qt-woes/11808/10
|
||||
buildCommand = ''
|
||||
makeQtWrapper ${lib.getExe pkgs.runtimeShellPackage} "$out"
|
||||
sed -i '/^exec/d' "$out"
|
||||
'';
|
||||
});
|
||||
in
|
||||
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
inputsFrom = [ self.packages.${system}.prismlauncher-unwrapped ];
|
||||
buildInputs = with pkgs; [
|
||||
inputsFrom = [ packages'.prismlauncher-unwrapped ];
|
||||
|
||||
packages = with pkgs; [
|
||||
ccache
|
||||
ninja
|
||||
llvm.clang-tools
|
||||
];
|
||||
|
||||
cmakeBuildType = "Debug";
|
||||
cmakeFlags = [ "-GNinja" ] ++ packages'.prismlauncher.cmakeFlags;
|
||||
dontFixCmake = true;
|
||||
|
||||
shellHook = ''
|
||||
echo "Sourcing ${qt-wrapper-env}"
|
||||
source ${qt-wrapper-env}
|
||||
|
||||
git submodule update --init --force
|
||||
|
||||
if [ ! -f compile_commands.json ]; then
|
||||
cmakeConfigurePhase
|
||||
cd ..
|
||||
ln -s "$cmakeBuildDir"/compile_commands.json compile_commands.json
|
||||
fi
|
||||
|
||||
echo ${lib.escapeShellArg welcomeMessage}
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -89,7 +167,6 @@
|
||||
prismlauncher-unwrapped = prev.callPackage ./nix/unwrapped.nix {
|
||||
inherit
|
||||
libnbtplusplus
|
||||
nix-filter
|
||||
self
|
||||
;
|
||||
};
|
||||
@ -99,6 +176,7 @@
|
||||
|
||||
packages = forAllSystems (
|
||||
system:
|
||||
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
|
||||
@ -111,6 +189,7 @@
|
||||
default = prismPackages.prismlauncher;
|
||||
};
|
||||
in
|
||||
|
||||
# Only output them if they're available on the current system
|
||||
lib.filterAttrs (_: lib.meta.availableOn pkgs.stdenv.hostPlatform) packages
|
||||
);
|
||||
@ -118,16 +197,18 @@
|
||||
# We put these under legacyPackages as they are meant for CI, not end user consumption
|
||||
legacyPackages = forAllSystems (
|
||||
system:
|
||||
|
||||
let
|
||||
prismPackages = self.packages.${system};
|
||||
legacyPackages = self.legacyPackages.${system};
|
||||
packages' = self.packages.${system};
|
||||
legacyPackages' = self.legacyPackages.${system};
|
||||
in
|
||||
|
||||
{
|
||||
prismlauncher-debug = prismPackages.prismlauncher.override {
|
||||
prismlauncher-unwrapped = legacyPackages.prismlauncher-unwrapped-debug;
|
||||
prismlauncher-debug = packages'.prismlauncher.override {
|
||||
prismlauncher-unwrapped = legacyPackages'.prismlauncher-unwrapped-debug;
|
||||
};
|
||||
|
||||
prismlauncher-unwrapped-debug = prismPackages.prismlauncher-unwrapped.overrideAttrs {
|
||||
prismlauncher-unwrapped-debug = packages'.prismlauncher-unwrapped.overrideAttrs {
|
||||
cmakeBuildType = "Debug";
|
||||
dontStrip = true;
|
||||
};
|
||||
|
@ -376,19 +376,20 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
m_peerInstance = new LocalPeer(this, appID);
|
||||
connect(m_peerInstance, &LocalPeer::messageReceived, this, &Application::messageReceived);
|
||||
if (m_peerInstance->isClient()) {
|
||||
bool sentMessage = false;
|
||||
int timeout = 2000;
|
||||
|
||||
if (m_instanceIdToLaunch.isEmpty()) {
|
||||
ApplicationMessage activate;
|
||||
activate.command = "activate";
|
||||
m_peerInstance->sendMessage(activate.serialize(), timeout);
|
||||
sentMessage = m_peerInstance->sendMessage(activate.serialize(), timeout);
|
||||
|
||||
if (!m_urlsToImport.isEmpty()) {
|
||||
for (auto url : m_urlsToImport) {
|
||||
ApplicationMessage import;
|
||||
import.command = "import";
|
||||
import.args.insert("url", url.toString());
|
||||
m_peerInstance->sendMessage(import.serialize(), timeout);
|
||||
sentMessage = m_peerInstance->sendMessage(import.serialize(), timeout);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -408,10 +409,16 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
launch.args["offline_enabled"] = "true";
|
||||
launch.args["offline_name"] = m_offlineName;
|
||||
}
|
||||
m_peerInstance->sendMessage(launch.serialize(), timeout);
|
||||
sentMessage = m_peerInstance->sendMessage(launch.serialize(), timeout);
|
||||
}
|
||||
if (sentMessage) {
|
||||
m_status = Application::Succeeded;
|
||||
return;
|
||||
} else {
|
||||
std::cerr << "Unable to redirect command to already running instance\n";
|
||||
// C function not Qt function - event loop not started yet
|
||||
::exit(1);
|
||||
}
|
||||
m_status = Application::Succeeded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -710,7 +717,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
||||
|
||||
m_settings->registerSetting("ToolbarsLocked", false);
|
||||
|
||||
// Instance
|
||||
m_settings->registerSetting("InstSortMode", "Name");
|
||||
m_settings->registerSetting("InstRenamingMode", "AskEverytime");
|
||||
m_settings->registerSetting("SelectedInstance", QString());
|
||||
|
||||
// Window state and geometry
|
||||
|
@ -386,6 +386,12 @@ void BaseInstance::setName(QString val)
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
bool BaseInstance::syncInstanceDirName(const QString& newRoot) const
|
||||
{
|
||||
auto oldRoot = instanceRoot();
|
||||
return oldRoot == newRoot || QFile::rename(oldRoot, newRoot);
|
||||
}
|
||||
|
||||
QString BaseInstance::name() const
|
||||
{
|
||||
return m_settings->get("name").toString();
|
||||
|
@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
|
||||
/// Sync name and rename instance dir accordingly; returns true if successful
|
||||
bool syncInstanceDirName(const QString& newRoot) const;
|
||||
|
||||
/// Value used for instance window titles
|
||||
QString windowTitle() const;
|
||||
|
||||
|
@ -21,6 +21,8 @@ set(CORE_SOURCES
|
||||
BaseVersion.h
|
||||
BaseInstance.h
|
||||
BaseInstance.cpp
|
||||
InstanceDirUpdate.h
|
||||
InstanceDirUpdate.cpp
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
@ -1286,7 +1288,6 @@ target_link_libraries(Launcher_logic
|
||||
qdcss
|
||||
BuildConfig
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
ghcFilesystem::ghc_filesystem
|
||||
)
|
||||
|
||||
if (UNIX AND NOT CYGWIN AND NOT APPLE)
|
||||
@ -1373,7 +1374,6 @@ if(Launcher_BUILD_UPDATER)
|
||||
${ZLIB_LIBRARIES}
|
||||
systeminfo
|
||||
BuildConfig
|
||||
ghcFilesystem::ghc_filesystem
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
@ -1412,7 +1412,6 @@ if(WIN32 OR (DEFINED Launcher_BUILD_FILELINKER AND Launcher_BUILD_FILELINKER))
|
||||
target_link_libraries(filelink_logic
|
||||
systeminfo
|
||||
BuildConfig
|
||||
ghcFilesystem::ghc_filesystem
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
|
@ -15,7 +15,7 @@
|
||||
DataMigrationTask::DataMigrationTask(const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathMatcher)
|
||||
: Task(), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
|
||||
{
|
||||
m_copy.matcher(m_pathMatcher.get()).whitelist(true);
|
||||
m_copy.matcher(m_pathMatcher).whitelist(true);
|
||||
}
|
||||
|
||||
void DataMigrationTask::executeTask()
|
||||
|
@ -77,24 +77,8 @@
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h> // for deployment target to support pre-catalina targets without std::fs
|
||||
#endif // __APPLE__
|
||||
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
|
||||
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
|
||||
#define GHC_USE_STD_FS
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
#endif // MacOS min version check
|
||||
#endif // Other OSes version check
|
||||
|
||||
#ifndef GHC_USE_STD_FS
|
||||
#include <ghc/filesystem.hpp>
|
||||
namespace fs = ghc::filesystem;
|
||||
#endif
|
||||
|
||||
// clone
|
||||
#if defined(Q_OS_LINUX)
|
||||
@ -950,7 +934,7 @@ bool createShortcut(QString destination, QString target, QStringList args, QStri
|
||||
QDir content = application.path() + "/Contents/";
|
||||
QDir resources = content.path() + "/Resources/";
|
||||
QDir binaryDir = content.path() + "/MacOS/";
|
||||
QFile info = content.path() + "/Info.plist";
|
||||
QFile info(content.path() + "/Info.plist");
|
||||
|
||||
if (!(content.mkpath(".") && resources.mkpath(".") && binaryDir.mkpath("."))) {
|
||||
qWarning() << "Couldn't create directories within application";
|
||||
|
@ -115,7 +115,7 @@ class copy : public QObject {
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy& matcher(const IPathMatcher* filter)
|
||||
copy& matcher(IPathMatcher::Ptr filter)
|
||||
{
|
||||
m_matcher = filter;
|
||||
return *this;
|
||||
@ -147,7 +147,7 @@ class copy : public QObject {
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
IPathMatcher::Ptr m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
bool m_overwrite = false;
|
||||
QDir m_src;
|
||||
@ -209,7 +209,7 @@ class create_link : public QObject {
|
||||
m_useHardLinks = useHard;
|
||||
return *this;
|
||||
}
|
||||
create_link& matcher(const IPathMatcher* filter)
|
||||
create_link& matcher(IPathMatcher::Ptr filter)
|
||||
{
|
||||
m_matcher = filter;
|
||||
return *this;
|
||||
@ -260,7 +260,7 @@ class create_link : public QObject {
|
||||
|
||||
private:
|
||||
bool m_useHardLinks = false;
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
IPathMatcher::Ptr m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
bool m_recursive = true;
|
||||
|
||||
@ -488,7 +488,7 @@ class clone : public QObject {
|
||||
m_src.setPath(src);
|
||||
m_dst.setPath(dst);
|
||||
}
|
||||
clone& matcher(const IPathMatcher* filter)
|
||||
clone& matcher(IPathMatcher::Ptr filter)
|
||||
{
|
||||
m_matcher = filter;
|
||||
return *this;
|
||||
@ -514,7 +514,7 @@ class clone : public QObject {
|
||||
bool operator()(const QString& offset, bool dryRun = false);
|
||||
|
||||
private:
|
||||
const IPathMatcher* m_matcher = nullptr;
|
||||
IPathMatcher::Ptr m_matcher = nullptr;
|
||||
bool m_whitelist = false;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
|
@ -43,7 +43,7 @@ void InstanceCopyTask::executeTask()
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||
if (m_useClone) {
|
||||
FS::clone folderClone(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderClone.matcher(m_matcher.get());
|
||||
folderClone.matcher(m_matcher);
|
||||
|
||||
folderClone(true);
|
||||
setProgress(0, folderClone.totalCloned());
|
||||
@ -72,7 +72,7 @@ void InstanceCopyTask::executeTask()
|
||||
}
|
||||
FS::create_link folderLink(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
int depth = m_linkRecursively ? -1 : 0; // we need to at least link the top level instead of the instance folder
|
||||
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher.get());
|
||||
folderLink.linkRecursively(true).setMaxDepth(depth).useHardLinks(m_useHardLinks).matcher(m_matcher);
|
||||
|
||||
folderLink(true);
|
||||
setProgress(0, m_progressTotal + folderLink.totalToLink());
|
||||
@ -127,7 +127,7 @@ void InstanceCopyTask::executeTask()
|
||||
return !there_were_errors;
|
||||
}
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).matcher(m_matcher.get());
|
||||
folderCopy.followSymlinks(false).matcher(m_matcher);
|
||||
|
||||
folderCopy(true);
|
||||
setProgress(0, folderCopy.totalCopied());
|
||||
|
@ -28,7 +28,7 @@ class InstanceCopyTask : public InstanceTask {
|
||||
InstancePtr m_origInstance;
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
std::unique_ptr<IPathMatcher> m_matcher;
|
||||
IPathMatcher::Ptr m_matcher;
|
||||
bool m_keepPlaytime;
|
||||
bool m_useLinks = false;
|
||||
bool m_useHardLinks = false;
|
||||
|
126
launcher/InstanceDirUpdate.cpp
Normal file
126
launcher/InstanceDirUpdate.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
*
|
||||
* 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 "InstanceDirUpdate.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent)
|
||||
{
|
||||
if (oldName == newName)
|
||||
return QString();
|
||||
|
||||
QString renamingMode = APPLICATION->settings()->get("InstRenamingMode").toString();
|
||||
if (renamingMode == "MetadataOnly")
|
||||
return QString();
|
||||
|
||||
auto oldRoot = instance->instanceRoot();
|
||||
auto newDirName = FS::DirNameFromString(newName, QFileInfo(oldRoot).dir().absolutePath());
|
||||
auto newRoot = FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newDirName);
|
||||
if (oldRoot == newRoot)
|
||||
return QString();
|
||||
if (oldRoot == FS::PathCombine(QFileInfo(oldRoot).dir().absolutePath(), newName))
|
||||
return QString();
|
||||
|
||||
// Check for conflict
|
||||
if (QDir(newRoot).exists()) {
|
||||
QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
|
||||
QObject::tr("New instance root (%1) already exists. <br />Only the metadata will be renamed.").arg(newRoot));
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Ask if we should rename
|
||||
if (renamingMode == "AskEverytime") {
|
||||
auto checkBox = new QCheckBox(QObject::tr("&Remember my choice"), parent);
|
||||
auto dialog =
|
||||
CustomMessageBox::selectable(parent, QObject::tr("Rename instance folder"),
|
||||
QObject::tr("Would you also like to rename the instance folder?\n\n"
|
||||
"Old name: %1\n"
|
||||
"New name: %2")
|
||||
.arg(oldName, newName),
|
||||
QMessageBox::Question, QMessageBox::No | QMessageBox::Yes, QMessageBox::NoButton, checkBox);
|
||||
|
||||
auto res = dialog->exec();
|
||||
if (checkBox->isChecked()) {
|
||||
if (res == QMessageBox::Yes)
|
||||
APPLICATION->settings()->set("InstRenamingMode", "PhysicalDir");
|
||||
else
|
||||
APPLICATION->settings()->set("InstRenamingMode", "MetadataOnly");
|
||||
}
|
||||
if (res == QMessageBox::No)
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Check for linked instances
|
||||
if (!checkLinkedInstances(instance->id(), parent, QObject::tr("Renaming")))
|
||||
return QString();
|
||||
|
||||
// Now we can confirm that a renaming is happening
|
||||
if (!instance->syncInstanceDirName(newRoot)) {
|
||||
QMessageBox::warning(parent, QObject::tr("Cannot rename instance"),
|
||||
QObject::tr("An error occurred when performing the following renaming operation: <br/>"
|
||||
" - Old instance root: %1<br/>"
|
||||
" - New instance root: %2<br/>"
|
||||
"Only the metadata is renamed.")
|
||||
.arg(oldRoot, newRoot));
|
||||
return QString();
|
||||
}
|
||||
return newRoot;
|
||||
}
|
||||
|
||||
bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb)
|
||||
{
|
||||
auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
|
||||
if (!linkedInstances.empty()) {
|
||||
auto response = CustomMessageBox::selectable(parent, QObject::tr("There are linked instances"),
|
||||
QObject::tr("The following instance(s) might reference files in this instance:\n\n"
|
||||
"%1\n\n"
|
||||
"%2 it could break the other instance(s), \n\n"
|
||||
"Do you wish to proceed?",
|
||||
nullptr, linkedInstances.count())
|
||||
.arg(linkedInstances.join("\n"))
|
||||
.arg(verb),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
if (response != QMessageBox::Yes)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
43
launcher/InstanceDirUpdate.h
Normal file
43
launcher/InstanceDirUpdate.h
Normal file
@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
/*
|
||||
* Prism Launcher - Minecraft Launcher
|
||||
*
|
||||
* 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 "BaseInstance.h"
|
||||
|
||||
/// Update instanceRoot to make it sync with name/id; return newRoot if a directory rename happened
|
||||
QString askToUpdateInstanceDirName(InstancePtr instance, const QString& oldName, const QString& newName, QWidget* parent);
|
||||
|
||||
/// Check if there are linked instances, and display a warning; return true if the operation should proceed
|
||||
bool checkLinkedInstances(const QString& id, QWidget* parent, const QString& verb);
|
@ -24,7 +24,7 @@ class TestCheck : public QObject {
|
||||
TestCheck(QWidget* parent, QString path, QString args, int minMem, int maxMem, int permGen)
|
||||
: m_parent(parent), m_path(path), m_args(args), m_minMem(minMem), m_maxMem(maxMem), m_permGen(permGen)
|
||||
{}
|
||||
virtual ~TestCheck() {};
|
||||
virtual ~TestCheck() = default;
|
||||
|
||||
void run();
|
||||
|
||||
|
@ -193,8 +193,8 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
|
||||
if (value.toBool()) {
|
||||
return tr("Recommended");
|
||||
} else if (hasLatest) {
|
||||
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if (value.toBool()) {
|
||||
auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if (latest.toBool()) {
|
||||
return tr("Latest");
|
||||
}
|
||||
}
|
||||
@ -203,33 +203,27 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
switch (column) {
|
||||
case Name: {
|
||||
if (hasRecommended) {
|
||||
auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if (recommenced.toBool()) {
|
||||
return APPLICATION->getThemedIcon("star");
|
||||
} else if (hasLatest) {
|
||||
auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if (latest.toBool()) {
|
||||
return APPLICATION->getThemedIcon("bug");
|
||||
}
|
||||
}
|
||||
QPixmap pixmap;
|
||||
QPixmapCache::find("placeholder", &pixmap);
|
||||
if (!pixmap) {
|
||||
QPixmap px(16, 16);
|
||||
px.fill(Qt::transparent);
|
||||
QPixmapCache::insert("placeholder", px);
|
||||
return px;
|
||||
}
|
||||
return pixmap;
|
||||
if (column == Name && hasRecommended) {
|
||||
auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
|
||||
if (recommenced.toBool()) {
|
||||
return APPLICATION->getThemedIcon("star");
|
||||
} else if (hasLatest) {
|
||||
auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
|
||||
if (latest.toBool()) {
|
||||
return APPLICATION->getThemedIcon("bug");
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return QVariant();
|
||||
QPixmap pixmap;
|
||||
QPixmapCache::find("placeholder", &pixmap);
|
||||
if (!pixmap) {
|
||||
QPixmap px(16, 16);
|
||||
px.fill(Qt::transparent);
|
||||
QPixmapCache::insert("placeholder", px);
|
||||
return px;
|
||||
}
|
||||
return pixmap;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
default: {
|
||||
if (roles.contains((BaseVersionList::ModelRoles)role)) {
|
||||
|
@ -40,24 +40,8 @@
|
||||
#include "WindowsConsole.h"
|
||||
#endif
|
||||
|
||||
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <Availability.h> // for deployment target to support pre-catalina targets without std::fs
|
||||
#endif // __APPLE__
|
||||
|
||||
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
|
||||
#if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
|
||||
#define GHC_USE_STD_FS
|
||||
#include <filesystem>
|
||||
namespace fs = std::filesystem;
|
||||
#endif // MacOS min version check
|
||||
#endif // Other OSes version check
|
||||
|
||||
#ifndef GHC_USE_STD_FS
|
||||
#include <ghc/filesystem.hpp>
|
||||
namespace fs = ghc::filesystem;
|
||||
#endif
|
||||
|
||||
FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
|
||||
{
|
||||
|
@ -517,13 +517,9 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const
|
||||
|
||||
switch (role) {
|
||||
case Qt::CheckStateRole: {
|
||||
switch (column) {
|
||||
case NameColumn: {
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
if (column == NameColumn)
|
||||
return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return QVariant();
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
switch (column) {
|
||||
|
@ -208,13 +208,9 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
case Qt::UserRole:
|
||||
switch (column) {
|
||||
case SizeColumn:
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
|
||||
default:
|
||||
return data(index, Qt::DisplayRole);
|
||||
}
|
||||
if (column == SizeColumn)
|
||||
return QVariant::fromValue<qlonglong>(world.bytes());
|
||||
return data(index, Qt::DisplayRole);
|
||||
|
||||
case Qt::ToolTipRole: {
|
||||
if (column == InfoColumn) {
|
||||
|
@ -260,6 +260,30 @@ int AccountList::count() const
|
||||
return m_accounts.count();
|
||||
}
|
||||
|
||||
QString getAccountStatus(AccountState status)
|
||||
{
|
||||
switch (status) {
|
||||
case AccountState::Unchecked:
|
||||
return QObject::tr("Unchecked", "Account status");
|
||||
case AccountState::Offline:
|
||||
return QObject::tr("Offline", "Account status");
|
||||
case AccountState::Online:
|
||||
return QObject::tr("Ready", "Account status");
|
||||
case AccountState::Working:
|
||||
return QObject::tr("Working", "Account status");
|
||||
case AccountState::Errored:
|
||||
return QObject::tr("Errored", "Account status");
|
||||
case AccountState::Expired:
|
||||
return QObject::tr("Expired", "Account status");
|
||||
case AccountState::Disabled:
|
||||
return QObject::tr("Disabled", "Account status");
|
||||
case AccountState::Gone:
|
||||
return QObject::tr("Gone", "Account status");
|
||||
default:
|
||||
return QObject::tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
|
||||
QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
@ -273,13 +297,10 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column()) {
|
||||
case ProfileNameColumn: {
|
||||
case ProfileNameColumn:
|
||||
return account->profileName();
|
||||
}
|
||||
|
||||
case NameColumn:
|
||||
return account->accountDisplayString();
|
||||
|
||||
case TypeColumn: {
|
||||
switch (account->accountType()) {
|
||||
case AccountType::MSA: {
|
||||
@ -291,39 +312,8 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return tr("Unknown", "Account type");
|
||||
}
|
||||
|
||||
case StatusColumn: {
|
||||
switch (account->accountState()) {
|
||||
case AccountState::Unchecked: {
|
||||
return tr("Unchecked", "Account status");
|
||||
}
|
||||
case AccountState::Offline: {
|
||||
return tr("Offline", "Account status");
|
||||
}
|
||||
case AccountState::Online: {
|
||||
return tr("Ready", "Account status");
|
||||
}
|
||||
case AccountState::Working: {
|
||||
return tr("Working", "Account status");
|
||||
}
|
||||
case AccountState::Errored: {
|
||||
return tr("Errored", "Account status");
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
return tr("Expired", "Account status");
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
return tr("Disabled", "Account status");
|
||||
}
|
||||
case AccountState::Gone: {
|
||||
return tr("Gone", "Account status");
|
||||
}
|
||||
default: {
|
||||
return tr("Unknown", "Account status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case StatusColumn:
|
||||
return getAccountStatus(account->accountState());
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -335,11 +325,9 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
|
||||
return QVariant::fromValue(account);
|
||||
|
||||
case Qt::CheckStateRole:
|
||||
if (index.column() == ProfileNameColumn) {
|
||||
if (index.column() == ProfileNameColumn)
|
||||
return account == m_defaultAccount ? Qt::Checked : Qt::Unchecked;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
return QVariant();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
@ -461,18 +449,14 @@ bool AccountList::loadList()
|
||||
|
||||
// Make sure the format version matches.
|
||||
auto listVersion = root.value("formatVersion").toVariant().toInt();
|
||||
switch (listVersion) {
|
||||
case AccountListVersion::MojangMSA: {
|
||||
return loadV3(root);
|
||||
} break;
|
||||
default: {
|
||||
QString newName = "accounts-old.json";
|
||||
qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
|
||||
// Attempt to rename the old version.
|
||||
file.rename(newName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (listVersion == AccountListVersion::MojangMSA)
|
||||
return loadV3(root);
|
||||
|
||||
QString newName = "accounts-old.json";
|
||||
qWarning() << "Unknown format version when loading account list. Existing one will be renamed to" << newName;
|
||||
// Attempt to rename the old version.
|
||||
file.rename(newName);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AccountList::loadV3(QJsonObject& root)
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QMultiMap>
|
||||
#include <QString>
|
||||
#include <memory>
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class MinecraftAccount;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
struct AuthSession {
|
||||
bool MakeOffline(QString offline_playername);
|
||||
|
@ -87,16 +87,12 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const
|
||||
if (row < 0 || row >= int(contents.size()))
|
||||
return QVariant();
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
if (column == 0) {
|
||||
return contents[row].key;
|
||||
} else {
|
||||
return contents[row].value;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (column == 0)
|
||||
return contents[row].key;
|
||||
return contents[row].value;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int GameOptions::rowCount(const QModelIndex&) const
|
||||
|
@ -105,19 +105,16 @@ std::pair<Version, Version> DataPack::compatibleVersions() const
|
||||
int DataPack::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto const& cast_other = static_cast<DataPack const&>(other);
|
||||
switch (type) {
|
||||
default:
|
||||
return Resource::compare(other, type);
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
auto other_ver = cast_other.packFormat();
|
||||
if (type == SortType::PACK_FORMAT) {
|
||||
auto this_ver = packFormat();
|
||||
auto other_ver = cast_other.packFormat();
|
||||
|
||||
if (this_ver > other_ver)
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
if (this_ver > other_ver)
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return -1;
|
||||
} else {
|
||||
return Resource::compare(other, type);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -35,8 +35,6 @@ class Version;
|
||||
class DataPack : public Resource {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<Resource>;
|
||||
|
||||
DataPack(QObject* parent = nullptr) : Resource(parent) {}
|
||||
DataPack(QFileInfo file_info) : Resource(file_info) {}
|
||||
|
||||
@ -59,6 +57,8 @@ class DataPack : public Resource {
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
virtual QString directory() { return "/data"; }
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
|
@ -138,6 +138,15 @@ auto Mod::name() const -> QString
|
||||
return Resource::name();
|
||||
}
|
||||
|
||||
auto Mod::mod_id() const -> QString
|
||||
{
|
||||
auto d_mod_id = details().mod_id;
|
||||
if (!d_mod_id.isEmpty())
|
||||
return d_mod_id;
|
||||
|
||||
return Resource::name();
|
||||
}
|
||||
|
||||
auto Mod::version() const -> QString
|
||||
{
|
||||
return details().version;
|
||||
|
@ -61,6 +61,7 @@ class Mod : public Resource {
|
||||
|
||||
auto details() const -> const ModDetails&;
|
||||
auto name() const -> QString override;
|
||||
auto mod_id() const -> QString;
|
||||
auto version() const -> QString;
|
||||
auto homepage() const -> QString override;
|
||||
auto description() const -> QString;
|
||||
|
@ -145,12 +145,9 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
if (column == ActiveColumn)
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
@ -152,6 +152,9 @@ class Resource : public QObject {
|
||||
|
||||
[[nodiscard]] bool isMoreThanOneHardLink() const;
|
||||
|
||||
[[nodiscard]] auto mod_id() const -> QString { return m_mod_id; }
|
||||
void setModId(const QString& modId) { m_mod_id = modId; }
|
||||
|
||||
protected:
|
||||
/* The file corresponding to this resource. */
|
||||
QFileInfo m_file_info;
|
||||
@ -162,6 +165,7 @@ class Resource : public QObject {
|
||||
QString m_internal_id;
|
||||
/* Name as reported via the file name. In the absence of a better name, this is shown to the user. */
|
||||
QString m_name;
|
||||
QString m_mod_id;
|
||||
|
||||
/* The type of file we're dealing with. */
|
||||
ResourceType m_type = ResourceType::UNKNOWN;
|
||||
|
@ -513,12 +513,9 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
return {};
|
||||
}
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (column == ActiveColumn)
|
||||
return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -42,13 +42,6 @@ void ResourcePack::setPackFormat(int new_format_id)
|
||||
m_pack_format = new_format_id;
|
||||
}
|
||||
|
||||
void ResourcePack::setDescription(QString new_description)
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
|
||||
m_description = new_description;
|
||||
}
|
||||
|
||||
void ResourcePack::setImage(QImage new_image) const
|
||||
{
|
||||
QMutexLocker locker(&m_data_lock);
|
||||
@ -90,7 +83,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
|
||||
}
|
||||
|
||||
// Imaged got evicted from the cache. Re-process it and retry.
|
||||
ResourcePackUtils::processPackPNG(*this);
|
||||
ResourcePackUtils::processPackPNG(this);
|
||||
return image(size);
|
||||
}
|
||||
|
||||
@ -102,44 +95,3 @@ std::pair<Version, Version> ResourcePack::compatibleVersions() const
|
||||
|
||||
return s_pack_format_versions.constFind(m_pack_format).value();
|
||||
}
|
||||
|
||||
int ResourcePack::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
auto const& cast_other = static_cast<ResourcePack const&>(other);
|
||||
switch (type) {
|
||||
default:
|
||||
return Resource::compare(other, type);
|
||||
case SortType::PACK_FORMAT: {
|
||||
auto this_ver = packFormat();
|
||||
auto other_ver = cast_other.packFormat();
|
||||
|
||||
if (this_ver > other_ver)
|
||||
return 1;
|
||||
if (this_ver < other_ver)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ResourcePack::applyFilter(QRegularExpression filter) const
|
||||
{
|
||||
if (filter.match(description()).hasMatch())
|
||||
return true;
|
||||
|
||||
if (filter.match(QString::number(packFormat())).hasMatch())
|
||||
return true;
|
||||
|
||||
if (filter.match(compatibleVersions().first.toString()).hasMatch())
|
||||
return true;
|
||||
if (filter.match(compatibleVersions().second.toString()).hasMatch())
|
||||
return true;
|
||||
|
||||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
bool ResourcePack::valid() const
|
||||
{
|
||||
return m_pack_format != 0;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Resource.h"
|
||||
#include "minecraft/mod/DataPack.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
@ -14,51 +15,27 @@ class Version;
|
||||
* Store localized descriptions
|
||||
* */
|
||||
|
||||
class ResourcePack : public Resource {
|
||||
class ResourcePack : public DataPack {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<Resource>;
|
||||
ResourcePack(QObject* parent = nullptr) : DataPack(parent) {}
|
||||
ResourcePack(QFileInfo file_info) : DataPack(file_info) {}
|
||||
|
||||
ResourcePack(QObject* parent = nullptr) : Resource(parent) {}
|
||||
ResourcePack(QFileInfo file_info) : Resource(file_info) {}
|
||||
|
||||
/** Gets the numerical ID of the pack format. */
|
||||
[[nodiscard]] int packFormat() const { return m_pack_format; }
|
||||
/** Gets, respectively, the lower and upper versions supported by the set pack format. */
|
||||
[[nodiscard]] std::pair<Version, Version> compatibleVersions() const;
|
||||
|
||||
/** Gets the description of the resource pack. */
|
||||
[[nodiscard]] QString description() const { return m_description; }
|
||||
|
||||
/** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */
|
||||
[[nodiscard]] QPixmap image(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
|
||||
|
||||
/** Thread-safe. */
|
||||
void setPackFormat(int new_format_id);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setDescription(QString new_description);
|
||||
|
||||
/** Thread-safe. */
|
||||
void setImage(QImage new_image) const;
|
||||
|
||||
bool valid() const override;
|
||||
/** Thread-safe. */
|
||||
void setPackFormat(int new_format_id);
|
||||
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
virtual QString directory() { return "/assets"; }
|
||||
|
||||
protected:
|
||||
mutable QMutex m_data_lock;
|
||||
|
||||
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
|
||||
* See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
*/
|
||||
int m_pack_format = 0;
|
||||
|
||||
/** The resource pack's description, as defined in the pack.mcmeta file.
|
||||
*/
|
||||
QString m_description;
|
||||
|
||||
/** The resource pack's image file cache key, for access in the QPixmapCache global instance.
|
||||
*
|
||||
* The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true),
|
||||
|
@ -44,7 +44,7 @@
|
||||
#include "Application.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
|
||||
@ -128,12 +128,9 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
return {};
|
||||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
if (column == ActiveColumn)
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return {};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@ -191,5 +188,5 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
|
||||
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
|
||||
{
|
||||
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
|
||||
return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast<ResourcePack*>(&resource));
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
@ -32,9 +34,9 @@
|
||||
|
||||
namespace DataPackUtils {
|
||||
|
||||
bool process(DataPack& pack, ProcessingLevel level)
|
||||
bool process(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
switch (pack->type()) {
|
||||
case ResourceType::FOLDER:
|
||||
return DataPackUtils::processFolder(pack, level);
|
||||
case ResourceType::ZIPFILE:
|
||||
@ -45,16 +47,16 @@ bool process(DataPack& pack, ProcessingLevel level)
|
||||
}
|
||||
}
|
||||
|
||||
bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
bool processFolder(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
Q_ASSERT(pack->type() == ResourceType::FOLDER);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.mcmeta"));
|
||||
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
|
||||
QFile mcmeta_file(mcmeta_file_info.filePath());
|
||||
if (!mcmeta_file.open(QIODevice::ReadOnly))
|
||||
@ -72,7 +74,7 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
return mcmeta_invalid(); // mcmeta file isn't a valid file
|
||||
}
|
||||
|
||||
QFileInfo data_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "data"));
|
||||
QFileInfo data_dir_info(FS::PathCombine(pack->fileinfo().filePath(), pack->directory()));
|
||||
if (!data_dir_info.exists() || !data_dir_info.isDir()) {
|
||||
return false; // data dir does not exists or isn't valid
|
||||
}
|
||||
@ -80,22 +82,46 @@ bool processFolder(DataPack& pack, ProcessingLevel level)
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
if (auto rp = dynamic_cast<ResourcePack*>(pack)) {
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
return png_invalid(); // can't open pack.png file
|
||||
|
||||
auto data = pack_png_file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
|
||||
|
||||
pack_png_file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
}
|
||||
|
||||
return true; // all tests passed
|
||||
}
|
||||
|
||||
bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
bool processZIP(DataPack* pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
Q_ASSERT(pack->type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Data pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
qWarning() << "Data pack at" << pack->fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
@ -119,7 +145,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
}
|
||||
|
||||
QuaZipDir zipDir(&zip);
|
||||
if (!zipDir.exists("/data")) {
|
||||
if (!zipDir.exists(pack->directory())) {
|
||||
return false; // data dir does not exists at zip root
|
||||
}
|
||||
|
||||
@ -127,21 +153,49 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
|
||||
zip.close();
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
if (auto rp = dynamic_cast<ResourcePack*>(pack)) {
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(rp, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
}
|
||||
zip.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://minecraft.wiki/w/Data_pack#pack.mcmeta
|
||||
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
|
||||
// https://minecraft.wiki/w/Raw_JSON_text_format
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
bool processMCMeta(DataPack* pack, QByteArray&& raw_data)
|
||||
{
|
||||
try {
|
||||
auto json_doc = QJsonDocument::fromJson(raw_data);
|
||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||
|
||||
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
pack.setDescription(Json::ensureString(pack_obj, "description", ""));
|
||||
pack->setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
pack->setDescription(ResourcePackUtils::processComponent(pack_obj.value("description")));
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||
return false;
|
||||
@ -152,26 +206,19 @@ bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
DataPack dp{ file };
|
||||
return DataPackUtils::process(dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
||||
return DataPackUtils::process(&dp, ProcessingLevel::BasicInfoOnly) && dp.valid();
|
||||
}
|
||||
|
||||
} // namespace DataPackUtils
|
||||
|
||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack& dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
||||
|
||||
bool LocalDataPackParseTask::abort()
|
||||
{
|
||||
m_aborted = true;
|
||||
return true;
|
||||
}
|
||||
LocalDataPackParseTask::LocalDataPackParseTask(int token, DataPack* dp) : Task(false), m_token(token), m_data_pack(dp) {}
|
||||
|
||||
void LocalDataPackParseTask::executeTask()
|
||||
{
|
||||
if (!DataPackUtils::process(m_data_pack))
|
||||
if (!DataPackUtils::process(m_data_pack)) {
|
||||
emitFailed("process failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
else
|
||||
emitSucceeded();
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -32,12 +32,12 @@ namespace DataPackUtils {
|
||||
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool process(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processZIP(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(DataPack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processZIP(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(DataPack* pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processMCMeta(DataPack& pack, QByteArray&& raw_data);
|
||||
bool processMCMeta(DataPack* pack, QByteArray&& raw_data);
|
||||
|
||||
/** Checks whether a file is valid as a data pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
@ -47,10 +47,7 @@ bool validate(QFileInfo file);
|
||||
class LocalDataPackParseTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalDataPackParseTask(int token, DataPack& dp);
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
LocalDataPackParseTask(int token, DataPack* dp);
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
@ -59,7 +56,5 @@ class LocalDataPackParseTask : public Task {
|
||||
private:
|
||||
int m_token;
|
||||
|
||||
DataPack& m_data_pack;
|
||||
|
||||
bool m_aborted = false;
|
||||
DataPack* m_data_pack;
|
||||
};
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/tasks/LocalDataPackParseTask.h"
|
||||
|
||||
#include <quazip/quazip.h>
|
||||
#include <quazip/quazipdir.h>
|
||||
@ -29,155 +30,6 @@
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
switch (pack.type()) {
|
||||
case ResourceType::FOLDER:
|
||||
return ResourcePackUtils::processFolder(pack, level);
|
||||
case ResourceType::ZIPFILE:
|
||||
return ResourcePackUtils::processZIP(pack, level);
|
||||
default:
|
||||
qWarning() << "Invalid type for resource pack parse task!";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::FOLDER);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta"));
|
||||
if (mcmeta_file_info.exists() && mcmeta_file_info.isFile()) {
|
||||
QFile mcmeta_file(mcmeta_file_info.filePath());
|
||||
if (!mcmeta_file.open(QIODevice::ReadOnly))
|
||||
return mcmeta_invalid(); // can't open mcmeta file
|
||||
|
||||
auto data = mcmeta_file.readAll();
|
||||
|
||||
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
mcmeta_file.close();
|
||||
if (!mcmeta_result) {
|
||||
return mcmeta_invalid(); // mcmeta invalid
|
||||
}
|
||||
} else {
|
||||
return mcmeta_invalid(); // mcmeta file isn't a valid file
|
||||
}
|
||||
|
||||
QFileInfo assets_dir_info(FS::PathCombine(pack.fileinfo().filePath(), "assets"));
|
||||
if (!assets_dir_info.exists() || !assets_dir_info.isDir()) {
|
||||
return false; // assets dir does not exists or isn't valid
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
return png_invalid(); // can't open pack.png file
|
||||
|
||||
auto data = pack_png_file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
pack_png_file.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
return png_invalid(); // pack.png does not exists or is not a valid file.
|
||||
}
|
||||
|
||||
return true; // all tests passed
|
||||
}
|
||||
|
||||
bool processZIP(ResourcePack& pack, ProcessingLevel level)
|
||||
{
|
||||
Q_ASSERT(pack.type() == ResourceType::ZIPFILE);
|
||||
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
QuaZipFile file(&zip);
|
||||
|
||||
auto mcmeta_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.mcmeta";
|
||||
return false; // the mcmeta is not optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.mcmeta")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return mcmeta_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool mcmeta_result = ResourcePackUtils::processMCMeta(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
if (!mcmeta_result) {
|
||||
return mcmeta_invalid(); // mcmeta invalid
|
||||
}
|
||||
} else {
|
||||
return mcmeta_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
QuaZipDir zipDir(&zip);
|
||||
if (!zipDir.exists("/assets")) {
|
||||
return false; // assets dir does not exists at zip root
|
||||
}
|
||||
|
||||
if (level == ProcessingLevel::BasicInfoOnly) {
|
||||
zip.close();
|
||||
return true; // only need basic info already checked
|
||||
}
|
||||
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return true; // the png is optional
|
||||
};
|
||||
|
||||
if (zip.setCurrentFile("pack.png")) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open file in zip.";
|
||||
zip.close();
|
||||
return png_invalid();
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
bool pack_png_result = ResourcePackUtils::processPackPNG(pack, std::move(data));
|
||||
|
||||
file.close();
|
||||
zip.close();
|
||||
if (!pack_png_result) {
|
||||
return png_invalid(); // pack.png invalid
|
||||
}
|
||||
} else {
|
||||
zip.close();
|
||||
return png_invalid(); // could not set pack.mcmeta as current file.
|
||||
}
|
||||
|
||||
zip.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
QString buildStyle(const QJsonObject& obj)
|
||||
{
|
||||
QStringList styles;
|
||||
@ -259,30 +111,11 @@ QString processComponent(const QJsonValue& value, bool strikethrough, bool under
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://minecraft.wiki/w/Raw_JSON_text_format
|
||||
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
|
||||
{
|
||||
try {
|
||||
auto json_doc = QJsonDocument::fromJson(raw_data);
|
||||
auto pack_obj = Json::requireObject(json_doc.object(), "pack", {});
|
||||
|
||||
pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0));
|
||||
|
||||
pack.setDescription(processComponent(pack_obj.value("description")));
|
||||
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << "JsonException: " << e.what() << e.cause();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data)
|
||||
{
|
||||
auto img = QImage::fromData(raw_data);
|
||||
if (!img.isNull()) {
|
||||
pack.setImage(img);
|
||||
pack->setImage(img);
|
||||
} else {
|
||||
qWarning() << "Failed to parse pack.png.";
|
||||
return false;
|
||||
@ -290,16 +123,16 @@ bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool processPackPNG(const ResourcePack& pack)
|
||||
bool processPackPNG(const ResourcePack* pack)
|
||||
{
|
||||
auto png_invalid = [&pack]() {
|
||||
qWarning() << "Resource pack at" << pack.fileinfo().filePath() << "does not have a valid pack.png";
|
||||
qWarning() << "Resource pack at" << pack->fileinfo().filePath() << "does not have a valid pack.png";
|
||||
return false;
|
||||
};
|
||||
|
||||
switch (pack.type()) {
|
||||
switch (pack->type()) {
|
||||
case ResourceType::FOLDER: {
|
||||
QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png"));
|
||||
QFileInfo image_file_info(FS::PathCombine(pack->fileinfo().filePath(), "pack.png"));
|
||||
if (image_file_info.exists() && image_file_info.isFile()) {
|
||||
QFile pack_png_file(image_file_info.filePath());
|
||||
if (!pack_png_file.open(QIODevice::ReadOnly))
|
||||
@ -319,7 +152,7 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
return false; // not processed correctly; https://github.com/PrismLauncher/PrismLauncher/issues/1740
|
||||
}
|
||||
case ResourceType::ZIPFILE: {
|
||||
QuaZip zip(pack.fileinfo().filePath());
|
||||
QuaZip zip(pack->fileinfo().filePath());
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
return false; // can't open zip file
|
||||
|
||||
@ -353,28 +186,7 @@ bool processPackPNG(const ResourcePack& pack)
|
||||
bool validate(QFileInfo file)
|
||||
{
|
||||
ResourcePack rp{ file };
|
||||
return ResourcePackUtils::process(rp, ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
return DataPackUtils::process(&rp, DataPackUtils::ProcessingLevel::BasicInfoOnly) && rp.valid();
|
||||
}
|
||||
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) : Task(false), m_token(token), m_resource_pack(rp) {}
|
||||
|
||||
bool LocalResourcePackParseTask::abort()
|
||||
{
|
||||
m_aborted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalResourcePackParseTask::executeTask()
|
||||
{
|
||||
if (!ResourcePackUtils::process(m_resource_pack)) {
|
||||
emitFailed("this is not a resource pack");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
|
@ -23,44 +23,14 @@
|
||||
|
||||
#include "minecraft/mod/ResourcePack.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
|
||||
namespace ResourcePackUtils {
|
||||
|
||||
enum class ProcessingLevel { Full, BasicInfoOnly };
|
||||
|
||||
bool process(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
bool processZIP(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
bool processFolder(ResourcePack& pack, ProcessingLevel level = ProcessingLevel::Full);
|
||||
|
||||
QString processComponent(const QJsonValue& value, bool strikethrough = false, bool underline = false);
|
||||
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack& pack, QByteArray&& raw_data);
|
||||
bool processPackPNG(const ResourcePack* pack, QByteArray&& raw_data);
|
||||
|
||||
/// processes ONLY the pack.png (rest of the pack may be invalid)
|
||||
bool processPackPNG(const ResourcePack& pack);
|
||||
bool processPackPNG(const ResourcePack* pack);
|
||||
|
||||
/** Checks whether a file is valid as a resource pack or not. */
|
||||
bool validate(QFileInfo file);
|
||||
} // namespace ResourcePackUtils
|
||||
|
||||
class LocalResourcePackParseTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocalResourcePackParseTask(int token, ResourcePack& rp);
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override;
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
[[nodiscard]] int token() const { return m_token; }
|
||||
|
||||
private:
|
||||
int m_token;
|
||||
|
||||
ResourcePack& m_resource_pack;
|
||||
|
||||
bool m_aborted = false;
|
||||
};
|
||||
|
@ -180,8 +180,10 @@ bool LocalWorldSaveParseTask::abort()
|
||||
|
||||
void LocalWorldSaveParseTask::executeTask()
|
||||
{
|
||||
if (!WorldSaveUtils::process(m_save))
|
||||
if (!WorldSaveUtils::process(m_save)) {
|
||||
emitFailed("this is not a world");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emitAborted();
|
||||
|
@ -62,7 +62,7 @@ class UserInteractionSupport {
|
||||
/**
|
||||
* Requests a user interaction to select which optional mods should be installed.
|
||||
*/
|
||||
virtual std::optional<QVector<QString>> chooseOptionalMods(PackVersion version, QVector<ATLauncher::VersionMod> mods) = 0;
|
||||
virtual std::optional<QVector<QString>> chooseOptionalMods(const PackVersion& version, QVector<ATLauncher::VersionMod> mods) = 0;
|
||||
|
||||
/**
|
||||
* Requests a user interaction to select a component version from a given version list
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include "Json.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
@ -33,9 +32,7 @@
|
||||
static const FlameAPI flameAPI;
|
||||
static ModrinthAPI modrinthAPI;
|
||||
|
||||
Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess)
|
||||
: m_network(network), m_manifest(toProcess)
|
||||
{}
|
||||
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) : m_manifest(toProcess) {}
|
||||
|
||||
bool Flame::FileResolvingTask::abort()
|
||||
{
|
||||
|
@ -17,8 +17,6 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include "PackManifest.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
@ -26,7 +24,7 @@ namespace Flame {
|
||||
class FileResolvingTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FileResolvingTask(const shared_qobject_ptr<QNetworkAccessManager>& network, Flame::Manifest& toProcess);
|
||||
explicit FileResolvingTask(Flame::Manifest& toProcess);
|
||||
virtual ~FileResolvingTask() = default;
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
@ -44,7 +42,6 @@ class FileResolvingTask : public Task {
|
||||
void getFlameProjects();
|
||||
|
||||
private: /* data */
|
||||
shared_qobject_ptr<QNetworkAccessManager> m_network;
|
||||
Flame::Manifest m_manifest;
|
||||
std::shared_ptr<QByteArray> m_result;
|
||||
Task::Ptr m_task;
|
||||
|
@ -102,57 +102,6 @@ QString FlameAPI::getModDescription(int modId)
|
||||
return description;
|
||||
}
|
||||
|
||||
QList<ModPlatform::IndexedVersion> FlameAPI::getLatestVersions(VersionSearchArgs&& args)
|
||||
{
|
||||
auto versions_url_optional = getVersionsURL(args);
|
||||
if (!versions_url_optional.has_value())
|
||||
return {};
|
||||
|
||||
auto versions_url = versions_url_optional.value();
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto netJob = makeShared<NetJob>(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network());
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
QList<ModPlatform::IndexedVersion> ver;
|
||||
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
auto arr = Json::requireArray(obj, "data");
|
||||
|
||||
for (auto file : arr) {
|
||||
auto file_obj = Json::requireObject(file);
|
||||
ver.append(FlameMod::loadIndexedPackVersion(file_obj));
|
||||
}
|
||||
|
||||
} catch (Json::JsonException& e) {
|
||||
qCritical() << "Failed to parse response from a version request.";
|
||||
qCritical() << e.what();
|
||||
qDebug() << doc;
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
netJob->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
return ver;
|
||||
}
|
||||
|
||||
Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
|
||||
{
|
||||
auto netJob = makeShared<NetJob>(QString("Flame::GetProjects"), APPLICATION->network());
|
||||
@ -266,7 +215,7 @@ QList<ModPlatform::Category> FlameAPI::loadModCategories(std::shared_ptr<QByteAr
|
||||
return categories;
|
||||
};
|
||||
|
||||
std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
|
||||
std::optional<ModPlatform::IndexedVersion> FlameAPI::getLatestVersion(QVector<ModPlatform::IndexedVersion> versions,
|
||||
QList<ModPlatform::ModLoaderType> instanceLoaders,
|
||||
ModPlatform::ModLoaderTypes modLoaders)
|
||||
{
|
||||
|
@ -15,8 +15,7 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
QString getModFileChangelog(int modId, int fileId);
|
||||
QString getModDescription(int modId);
|
||||
|
||||
QList<ModPlatform::IndexedVersion> getLatestVersions(VersionSearchArgs&& args);
|
||||
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QList<ModPlatform::IndexedVersion> versions,
|
||||
std::optional<ModPlatform::IndexedVersion> getLatestVersion(QVector<ModPlatform::IndexedVersion> versions,
|
||||
QList<ModPlatform::ModLoaderType> instanceLoaders,
|
||||
ModPlatform::ModLoaderTypes fallback);
|
||||
|
||||
@ -108,12 +107,6 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
||||
{
|
||||
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
|
||||
{
|
||||
auto addonId = args.pack.addonId.toString();
|
||||
@ -129,6 +122,11 @@ class FlameAPI : public NetworkResourceAPI {
|
||||
return url;
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::optional<QString> getInfoURL(QString const& id) const override
|
||||
{
|
||||
return QString("https://api.curseforge.com/v1/mods/%1").arg(id);
|
||||
}
|
||||
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
|
||||
{
|
||||
auto addonId = args.dependency.addonId.toString();
|
||||
|
@ -3,115 +3,31 @@
|
||||
#include "FlameAPI.h"
|
||||
#include "FlameModIndex.h"
|
||||
|
||||
#include <MurmurHash2.h>
|
||||
#include <QHash>
|
||||
#include <memory>
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "ResourceDownloadTask.h"
|
||||
|
||||
#include "minecraft/mod/ModFolderModel.h"
|
||||
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "net/ApiDownload.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
static FlameAPI api;
|
||||
|
||||
bool FlameCheckUpdate::abort()
|
||||
{
|
||||
m_was_aborted = true;
|
||||
if (m_net_job)
|
||||
return m_net_job->abort();
|
||||
return true;
|
||||
}
|
||||
|
||||
ModPlatform::IndexedPack FlameCheckUpdate::getProjectInfo(ModPlatform::IndexedVersion& ver_info)
|
||||
{
|
||||
ModPlatform::IndexedPack pack;
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto get_project_job = new NetJob("Flame::GetProjectJob", APPLICATION->network());
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||
get_project_job->addNetAction(dl);
|
||||
|
||||
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto doc_obj = Json::requireObject(doc);
|
||||
auto data_obj = Json::requireObject(doc_obj, "data");
|
||||
FlameMod::loadIndexedPack(pack, data_obj);
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << e.cause();
|
||||
qDebug() << doc;
|
||||
}
|
||||
});
|
||||
|
||||
connect(get_project_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
||||
QObject::connect(get_project_job, &NetJob::finished, [&loop, get_project_job] {
|
||||
get_project_job->deleteLater();
|
||||
loop.quit();
|
||||
});
|
||||
|
||||
get_project_job->start();
|
||||
loop.exec();
|
||||
|
||||
return pack;
|
||||
}
|
||||
|
||||
ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileId)
|
||||
{
|
||||
ModPlatform::IndexedVersion ver;
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto get_file_info_job = new NetJob("Flame::GetFileInfoJob", APPLICATION->network());
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
|
||||
auto dl = Net::ApiDownload::makeByteArray(url, response);
|
||||
get_file_info_job->addNetAction(dl);
|
||||
|
||||
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from FlameCheckUpdate at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto doc_obj = Json::requireObject(doc);
|
||||
auto data_obj = Json::requireObject(doc_obj, "data");
|
||||
ver = FlameMod::loadIndexedPackVersion(data_obj);
|
||||
} catch (Json::JsonException& e) {
|
||||
qWarning() << e.cause();
|
||||
qDebug() << doc;
|
||||
}
|
||||
});
|
||||
connect(get_file_info_job, &NetJob::failed, this, &FlameCheckUpdate::emitFailed);
|
||||
QObject::connect(get_file_info_job, &NetJob::finished, [&loop, get_file_info_job] {
|
||||
get_file_info_job->deleteLater();
|
||||
loop.quit();
|
||||
});
|
||||
|
||||
get_file_info_job->start();
|
||||
loop.exec();
|
||||
|
||||
return ver;
|
||||
bool result = false;
|
||||
if (m_task && m_task->canAbort()) {
|
||||
result = m_task->abort();
|
||||
}
|
||||
Task::abort();
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Check for update:
|
||||
@ -123,66 +39,163 @@ void FlameCheckUpdate::executeTask()
|
||||
{
|
||||
setStatus(tr("Preparing resources for CurseForge..."));
|
||||
|
||||
int i = 0;
|
||||
auto netJob = new NetJob("Get latest versions", APPLICATION->network());
|
||||
connect(netJob, &Task::finished, this, &FlameCheckUpdate::collectBlockedMods);
|
||||
|
||||
connect(netJob, &Task::progress, this, &FlameCheckUpdate::setProgress);
|
||||
connect(netJob, &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
|
||||
connect(netJob, &Task::details, this, &FlameCheckUpdate::setDetails);
|
||||
for (auto* resource : m_resources) {
|
||||
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
setProgress(i++, m_resources.size());
|
||||
|
||||
auto latest_vers = api.getLatestVersions({ { resource->metadata()->project_id.toString() }, m_game_versions });
|
||||
|
||||
// Check if we were aborted while getting the latest version
|
||||
if (m_was_aborted) {
|
||||
aborted();
|
||||
return;
|
||||
}
|
||||
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
|
||||
|
||||
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
|
||||
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
|
||||
QString reason;
|
||||
if (dynamic_cast<Mod*>(resource) != nullptr)
|
||||
reason =
|
||||
tr("No valid version found for this resource. It's probably unavailable for the current game "
|
||||
"version / mod loader.");
|
||||
else
|
||||
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
||||
|
||||
emit checkFailed(resource, reason);
|
||||
auto versions_url_optional = api.getVersionsURL({ { resource->metadata()->project_id.toString() }, m_game_versions });
|
||||
if (!versions_url_optional.has_value())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
|
||||
auto pack = getProjectInfo(latest_ver.value());
|
||||
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, latest_ver->fileId.toString());
|
||||
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto task = Net::ApiDownload::makeByteArray(versions_url_optional.value(), response);
|
||||
|
||||
continue;
|
||||
}
|
||||
connect(task.get(), &Task::succeeded, this, [this, resource, response] { getLatestVersionCallback(resource, response); });
|
||||
netJob->addNetAction(task);
|
||||
}
|
||||
m_task.reset(netJob);
|
||||
m_task->start();
|
||||
}
|
||||
|
||||
// Fake pack with the necessary info to pass to the download task :)
|
||||
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
pack->name = resource->name();
|
||||
pack->slug = resource->metadata()->slug;
|
||||
pack->addonId = resource->metadata()->project_id;
|
||||
pack->provider = ModPlatform::ResourceProvider::FLAME;
|
||||
if (!latest_ver->hash.isEmpty() &&
|
||||
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
||||
auto old_version = resource->metadata()->version_number;
|
||||
if (old_version.isEmpty()) {
|
||||
if (resource->status() == ResourceStatus::NOT_INSTALLED)
|
||||
old_version = tr("Not installed");
|
||||
else
|
||||
old_version = tr("Unknown");
|
||||
}
|
||||
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resource_model);
|
||||
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
|
||||
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
|
||||
ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
|
||||
}
|
||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
|
||||
void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr<QByteArray> response)
|
||||
{
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from latest mod version at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
// Fake pack with the necessary info to pass to the download task :)
|
||||
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
pack->name = resource->name();
|
||||
pack->slug = resource->metadata()->slug;
|
||||
pack->addonId = resource->metadata()->project_id;
|
||||
pack->provider = ModPlatform::ResourceProvider::FLAME;
|
||||
try {
|
||||
auto obj = Json::requireObject(doc);
|
||||
auto arr = Json::requireArray(obj, "data");
|
||||
|
||||
FlameMod::loadIndexedPackVersions(*pack.get(), arr);
|
||||
} catch (Json::JsonException& e) {
|
||||
qCritical() << "Failed to parse response from a version request.";
|
||||
qCritical() << e.what();
|
||||
qDebug() << doc;
|
||||
}
|
||||
auto latest_ver = api.getLatestVersion(pack->versions, m_loaders_list, resource->metadata()->loaders);
|
||||
|
||||
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
|
||||
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
|
||||
QString reason;
|
||||
if (dynamic_cast<Mod*>(resource) != nullptr)
|
||||
reason =
|
||||
tr("No valid version found for this resource. It's probably unavailable for the current game "
|
||||
"version / mod loader.");
|
||||
else
|
||||
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
||||
|
||||
emit checkFailed(resource, reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != resource->metadata()->file_id) {
|
||||
m_blocked[resource] = latest_ver->fileId.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!latest_ver->hash.isEmpty() &&
|
||||
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
||||
auto old_version = resource->metadata()->version_number;
|
||||
if (old_version.isEmpty()) {
|
||||
if (resource->status() == ResourceStatus::NOT_INSTALLED)
|
||||
old_version = tr("Not installed");
|
||||
else
|
||||
old_version = tr("Unknown");
|
||||
}
|
||||
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resource_model);
|
||||
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
|
||||
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
|
||||
ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
|
||||
}
|
||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
|
||||
}
|
||||
|
||||
void FlameCheckUpdate::collectBlockedMods()
|
||||
{
|
||||
QStringList addonIds;
|
||||
QHash<QString, Resource*> quickSearch;
|
||||
for (auto const& resource : m_blocked.keys()) {
|
||||
auto addonId = resource->metadata()->project_id.toString();
|
||||
addonIds.append(addonId);
|
||||
quickSearch[addonId] = resource;
|
||||
}
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
Task::Ptr projTask;
|
||||
|
||||
if (addonIds.isEmpty()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
} else if (addonIds.size() == 1) {
|
||||
projTask = api.getProject(*addonIds.begin(), response);
|
||||
} else {
|
||||
projTask = api.getProjects(addonIds, response);
|
||||
}
|
||||
|
||||
connect(projTask.get(), &Task::succeeded, this, [this, response, addonIds, quickSearch] {
|
||||
QJsonParseError parse_error{};
|
||||
auto doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response from Flame projects task at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qWarning() << *response;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QJsonArray entries;
|
||||
if (addonIds.size() == 1)
|
||||
entries = { Json::requireObject(Json::requireObject(doc), "data") };
|
||||
else
|
||||
entries = Json::requireArray(Json::requireObject(doc), "data");
|
||||
|
||||
for (auto entry : entries) {
|
||||
auto entry_obj = Json::requireObject(entry);
|
||||
|
||||
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
|
||||
|
||||
auto resource = quickSearch.find(id).value();
|
||||
|
||||
ModPlatform::IndexedPack pack;
|
||||
try {
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
|
||||
FlameMod::loadIndexedPack(pack, entry_obj);
|
||||
auto recover_url = QString("%1/download/%2").arg(pack.websiteUrl, m_blocked[resource]);
|
||||
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."),
|
||||
recover_url);
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
qDebug() << entries;
|
||||
}
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
qDebug() << doc;
|
||||
}
|
||||
});
|
||||
|
||||
connect(projTask.get(), &Task::finished, this, &FlameCheckUpdate::emitSucceeded); // do not care much about error
|
||||
connect(projTask.get(), &Task::progress, this, &FlameCheckUpdate::setProgress);
|
||||
connect(projTask.get(), &Task::stepProgress, this, &FlameCheckUpdate::propagateStepProgress);
|
||||
connect(projTask.get(), &Task::details, this, &FlameCheckUpdate::setDetails);
|
||||
m_task.reset(projTask);
|
||||
m_task->start();
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "modplatform/CheckUpdateTask.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
class FlameCheckUpdate : public CheckUpdateTask {
|
||||
Q_OBJECT
|
||||
@ -19,12 +18,12 @@ class FlameCheckUpdate : public CheckUpdateTask {
|
||||
|
||||
protected slots:
|
||||
void executeTask() override;
|
||||
private slots:
|
||||
void getLatestVersionCallback(Resource* resource, std::shared_ptr<QByteArray> response);
|
||||
void collectBlockedMods();
|
||||
|
||||
private:
|
||||
ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info);
|
||||
ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId);
|
||||
Task::Ptr m_task = nullptr;
|
||||
|
||||
NetJob* m_net_job = nullptr;
|
||||
|
||||
bool m_was_aborted = false;
|
||||
QHash<Resource*, QString> m_blocked;
|
||||
};
|
||||
|
@ -442,7 +442,7 @@ bool FlameCreationTask::createInstance()
|
||||
|
||||
instance.setName(name());
|
||||
|
||||
m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
|
||||
m_modIdResolver.reset(new Flame::FileResolvingTask(m_pack));
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [this, &loop](QString reason) {
|
||||
m_modIdResolver.reset();
|
||||
|
@ -76,10 +76,7 @@ static QString enumToString(int hash_algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QJsonArray& arr,
|
||||
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
const BaseInstance* inst)
|
||||
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr)
|
||||
{
|
||||
QVector<ModPlatform::IndexedVersion> unsortedVersions;
|
||||
for (auto versionIter : arr) {
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include "BaseInstance.h"
|
||||
|
||||
namespace FlameMod {
|
||||
@ -14,10 +13,7 @@ namespace FlameMod {
|
||||
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
|
||||
QJsonArray& arr,
|
||||
const shared_qobject_ptr<QNetworkAccessManager>& network,
|
||||
const BaseInstance* inst);
|
||||
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr);
|
||||
ModPlatform::IndexedVersion loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false);
|
||||
ModPlatform::IndexedVersion loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst);
|
||||
} // namespace FlameMod
|
@ -203,6 +203,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
|
||||
for (auto mod : mods) {
|
||||
auto meta = mod->metadata();
|
||||
auto modName = mod->name();
|
||||
auto modID = mod->mod_id();
|
||||
auto url = mod->homepage();
|
||||
auto ver = mod->version();
|
||||
if (ver.isEmpty() && meta != nullptr)
|
||||
@ -211,6 +212,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
|
||||
auto filename = mod->fileinfo().fileName();
|
||||
lines << QString(lineTemplate)
|
||||
.replace("{name}", modName)
|
||||
.replace("{mod_id}", modID)
|
||||
.replace("{url}", url)
|
||||
.replace("{version}", ver)
|
||||
.replace("{authors}", authors)
|
||||
|
@ -40,7 +40,7 @@ class PackFetchTask : public QObject {
|
||||
void failed(QString reason);
|
||||
void aborted();
|
||||
|
||||
void privateFileDownloadFinished(Modpack modpack);
|
||||
void privateFileDownloadFinished(const Modpack& modpack);
|
||||
void privateFileDownloadFailed(QString reason, QString packCode);
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@
|
||||
|
||||
namespace LegacyFTB {
|
||||
|
||||
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version)
|
||||
PackInstallTask::PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const Modpack& pack, QString version)
|
||||
{
|
||||
m_pack = pack;
|
||||
m_version = version;
|
||||
|
@ -18,7 +18,7 @@ class PackInstallTask : public InstanceTask {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, Modpack pack, QString version);
|
||||
explicit PackInstallTask(shared_qobject_ptr<QNetworkAccessManager> network, const Modpack& pack, QString version);
|
||||
virtual ~PackInstallTask() {}
|
||||
|
||||
bool canAbort() const override { return true; }
|
||||
|
@ -112,7 +112,7 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
|
||||
pack.extraDataLoaded = true;
|
||||
}
|
||||
|
||||
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
|
||||
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr)
|
||||
{
|
||||
QVector<ModPlatform::IndexedVersion> unsortedVersions;
|
||||
for (auto versionIter : arr) {
|
||||
|
@ -19,14 +19,13 @@
|
||||
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include "BaseInstance.h"
|
||||
|
||||
namespace Modrinth {
|
||||
|
||||
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
|
||||
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr);
|
||||
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
|
||||
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
|
||||
|
||||
|
@ -54,7 +54,7 @@ Task::State FileSink::init(QNetworkRequest& request)
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
wroteAnyData = false;
|
||||
m_wroteAnyData = false;
|
||||
m_output_file.reset(new PSaveFile(m_filename));
|
||||
if (!m_output_file->open(QIODevice::WriteOnly)) {
|
||||
qCCritical(taskNetLogC) << "Could not open " + m_filename + " for writing";
|
||||
@ -72,17 +72,19 @@ Task::State FileSink::write(QByteArray& data)
|
||||
qCCritical(taskNetLogC) << "Failed writing into " + m_filename;
|
||||
m_output_file->cancelWriting();
|
||||
m_output_file.reset();
|
||||
wroteAnyData = false;
|
||||
m_wroteAnyData = false;
|
||||
return Task::State::Failed;
|
||||
}
|
||||
|
||||
wroteAnyData = true;
|
||||
m_wroteAnyData = true;
|
||||
return Task::State::Running;
|
||||
}
|
||||
|
||||
Task::State FileSink::abort()
|
||||
{
|
||||
m_output_file->cancelWriting();
|
||||
if (m_output_file) {
|
||||
m_output_file->cancelWriting();
|
||||
}
|
||||
failAllValidators();
|
||||
return Task::State::Failed;
|
||||
}
|
||||
@ -100,7 +102,7 @@ Task::State FileSink::finalize(QNetworkReply& reply)
|
||||
|
||||
// if we wrote any data to the save file, we try to commit the data to the real file.
|
||||
// if it actually got a proper file, we write it even if it was empty
|
||||
if (gotFile || wroteAnyData) {
|
||||
if (gotFile || m_wroteAnyData) {
|
||||
// ask validators for data consistency
|
||||
// we only do this for actual downloads, not 'your data is still the same' cache hits
|
||||
if (!finalizeAllValidators(reply))
|
||||
|
@ -58,7 +58,7 @@ class FileSink : public Sink {
|
||||
|
||||
protected:
|
||||
QString m_filename;
|
||||
bool wroteAnyData = false;
|
||||
bool m_wroteAnyData = false;
|
||||
std::unique_ptr<PSaveFile> m_output_file;
|
||||
};
|
||||
} // namespace Net
|
||||
|
@ -78,7 +78,7 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply& reply)
|
||||
{
|
||||
QFileInfo output_file_info(m_filename);
|
||||
|
||||
if (wroteAnyData) {
|
||||
if (m_wroteAnyData) {
|
||||
m_entry->setMD5Sum(m_md5Node->hash().toHex().constData());
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ void NetRequest::executeTask()
|
||||
emit finished();
|
||||
return;
|
||||
case State::Running:
|
||||
qCDebug(logCat) << getUid().toString() << "Runninng " << m_url.toString();
|
||||
qCDebug(logCat) << getUid().toString() << "Running " << m_url.toString();
|
||||
break;
|
||||
case State::Inactive:
|
||||
case State::Failed:
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <SeparatorPrefixTree.h>
|
||||
#include <QRegularExpression>
|
||||
#include "IPathMatcher.h"
|
||||
|
@ -1,3 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include "IPathMatcher.h"
|
||||
|
||||
|
@ -123,6 +123,7 @@
|
||||
#include "KonamiCode.h"
|
||||
|
||||
#include "InstanceCopyTask.h"
|
||||
#include "InstanceDirUpdate.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
@ -288,10 +289,27 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
||||
|
||||
view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
// FIXME: leaks ListViewDelegate
|
||||
view->setItemDelegate(new ListViewDelegate(this));
|
||||
auto delegate = new ListViewDelegate(this);
|
||||
view->setItemDelegate(delegate);
|
||||
view->setFrameShape(QFrame::NoFrame);
|
||||
// do not show ugly blue border on the mac
|
||||
view->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
connect(delegate, &ListViewDelegate::textChanged, this, [this](QString before, QString after) {
|
||||
if (auto newRoot = askToUpdateInstanceDirName(m_selectedInstance, before, after, this); !newRoot.isEmpty()) {
|
||||
auto oldID = m_selectedInstance->id();
|
||||
auto newID = QFileInfo(newRoot).fileName();
|
||||
QString origGroup(APPLICATION->instances()->getInstanceGroup(oldID));
|
||||
bool syncGroup = origGroup != GroupId() && oldID != newID;
|
||||
if (syncGroup)
|
||||
APPLICATION->instances()->setInstanceGroup(oldID, GroupId());
|
||||
|
||||
refreshInstances();
|
||||
setSelectedInstanceById(newID);
|
||||
|
||||
if (syncGroup)
|
||||
APPLICATION->instances()->setInstanceGroup(newID, origGroup);
|
||||
}
|
||||
});
|
||||
|
||||
view->installEventFilter(this);
|
||||
view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
@ -1377,20 +1395,8 @@ void MainWindow::on_actionDeleteInstance_triggered()
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
auto linkedInstances = APPLICATION->instances()->getLinkedInstancesById(id);
|
||||
if (!linkedInstances.empty()) {
|
||||
response = CustomMessageBox::selectable(this, tr("There are linked instances"),
|
||||
tr("The following instance(s) might reference files in this instance:\n\n"
|
||||
"%1\n\n"
|
||||
"Deleting it could break the other instance(s), \n\n"
|
||||
"Do you wish to proceed?",
|
||||
nullptr, linkedInstances.count())
|
||||
.arg(linkedInstances.join("\n")),
|
||||
QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
|
||||
->exec();
|
||||
if (response != QMessageBox::Yes)
|
||||
return;
|
||||
}
|
||||
if (!checkLinkedInstances(id, this, tr("Deleting")))
|
||||
return;
|
||||
|
||||
if (APPLICATION->instances()->trashInstance(id)) {
|
||||
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
|
||||
|
@ -21,7 +21,8 @@ QMessageBox* selectable(QWidget* parent,
|
||||
const QString& text,
|
||||
QMessageBox::Icon icon,
|
||||
QMessageBox::StandardButtons buttons,
|
||||
QMessageBox::StandardButton defaultButton)
|
||||
QMessageBox::StandardButton defaultButton,
|
||||
QCheckBox* checkBox)
|
||||
{
|
||||
QMessageBox* messageBox = new QMessageBox(parent);
|
||||
messageBox->setWindowTitle(title);
|
||||
@ -31,6 +32,8 @@ QMessageBox* selectable(QWidget* parent,
|
||||
messageBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
messageBox->setIcon(icon);
|
||||
messageBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
if (checkBox)
|
||||
messageBox->setCheckBox(checkBox);
|
||||
|
||||
return messageBox;
|
||||
}
|
||||
|
@ -23,5 +23,6 @@ QMessageBox* selectable(QWidget* parent,
|
||||
const QString& text,
|
||||
QMessageBox::Icon icon = QMessageBox::NoIcon,
|
||||
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
|
||||
QMessageBox::StandardButton defaultButton = QMessageBox::NoButton,
|
||||
QCheckBox* checkBox = nullptr);
|
||||
}
|
||||
|
@ -79,6 +79,9 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>This text supports the following placeholders: {name} - Mod name {mod_id} - Mod ID {url} - Mod URL {version} - Mod version {authors} - Mod authors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -92,7 +92,7 @@ ProgressDialog::~ProgressDialog()
|
||||
{
|
||||
for (auto conn : this->m_taskConnections) {
|
||||
disconnect(conn);
|
||||
}
|
||||
}
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ class ProgressDialog : public QDialog {
|
||||
Ui::ProgressDialog* ui;
|
||||
|
||||
Task* m_task;
|
||||
|
||||
|
||||
QList<QMetaObject::Connection> m_taskConnections;
|
||||
|
||||
bool m_is_multi_step = false;
|
||||
|
@ -282,6 +282,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
|
||||
bool skip_rest = false;
|
||||
ModPlatform::ResourceProvider provider_rest = ModPlatform::ResourceProvider::MODRINTH;
|
||||
|
||||
// adds resource to list based on provider
|
||||
auto addToTmp = [&modrinth_tmp, &flame_tmp](Resource* resource, ModPlatform::ResourceProvider p) {
|
||||
switch (p) {
|
||||
case ModPlatform::ResourceProvider::MODRINTH:
|
||||
@ -293,6 +294,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
|
||||
}
|
||||
};
|
||||
|
||||
// ask the user on what provider to seach for the mod first
|
||||
for (auto candidate : m_candidates) {
|
||||
if (candidate->status() != ResourceStatus::NO_METADATA) {
|
||||
onMetadataEnsured(candidate);
|
||||
@ -335,6 +337,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
|
||||
addToTmp(candidate, response.chosen);
|
||||
}
|
||||
|
||||
// prepare task for the modrinth mods
|
||||
if (!modrinth_tmp.empty()) {
|
||||
auto modrinth_task = makeShared<EnsureMetadataTask>(modrinth_tmp, index_dir, ModPlatform::ResourceProvider::MODRINTH);
|
||||
connect(modrinth_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
|
||||
@ -350,6 +353,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
|
||||
seq.addTask(modrinth_task);
|
||||
}
|
||||
|
||||
// prepare task for the flame mods
|
||||
if (!flame_tmp.empty()) {
|
||||
auto flame_task = makeShared<EnsureMetadataTask>(flame_tmp, index_dir, ModPlatform::ResourceProvider::FLAME);
|
||||
connect(flame_task.get(), &EnsureMetadataTask::metadataReady, [this](Resource* candidate) { onMetadataEnsured(candidate); });
|
||||
@ -367,6 +371,7 @@ auto ResourceUpdateDialog::ensureMetadata() -> bool
|
||||
|
||||
seq.addTask(m_second_try_metadata);
|
||||
|
||||
// execute all the tasks
|
||||
ProgressDialog checking_dialog(m_parent);
|
||||
checking_dialog.setSkipButton(true, tr("Abort"));
|
||||
checking_dialog.setWindowTitle(tr("Generating metadata..."));
|
||||
@ -477,13 +482,8 @@ void ResourceUpdateDialog::appendResource(CheckUpdateTask::Update const& info, Q
|
||||
auto changelog_area = new QTextBrowser();
|
||||
|
||||
QString text = info.changelog;
|
||||
switch (info.provider) {
|
||||
case ModPlatform::ResourceProvider::MODRINTH: {
|
||||
text = markdownToHTML(info.changelog.toUtf8());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
if (info.provider == ModPlatform::ResourceProvider::MODRINTH) {
|
||||
text = markdownToHTML(info.changelog.toUtf8());
|
||||
}
|
||||
|
||||
changelog_area->setHtml(StringUtils::htmlListPatch(text));
|
||||
|
@ -223,6 +223,8 @@ void SkinManageDialog::on_capeCombo_currentIndexChanged(int index)
|
||||
auto cape = m_capes.value(id.toString(), {});
|
||||
if (!cape.isNull()) {
|
||||
m_ui->capeImage->setPixmap(previewCape(cape).scaled(size() * (1. / 3), Qt::KeepAspectRatio, Qt::FastTransformation));
|
||||
} else {
|
||||
m_ui->capeImage->clear();
|
||||
}
|
||||
m_skinPreview->updateCape(cape);
|
||||
if (auto skin = getSelectedSkin(); skin) {
|
||||
@ -522,6 +524,8 @@ void SkinManageDialog::resizeEvent(QResizeEvent* event)
|
||||
auto cape = m_capes.value(id.toString(), {});
|
||||
if (!cape.isNull()) {
|
||||
m_ui->capeImage->setPixmap(previewCape(cape).scaled(s, Qt::KeepAspectRatio, Qt::FastTransformation));
|
||||
} else {
|
||||
m_ui->capeImage->clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,6 +397,7 @@ void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model,
|
||||
// Prevent instance names longer than 128 chars
|
||||
text.truncate(128);
|
||||
if (text.size() != 0) {
|
||||
emit textChanged(model->data(index).toString(), text);
|
||||
model->setData(index, text);
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ class ListViewDelegate : public QStyledItemDelegate {
|
||||
void setEditorData(QWidget* editor, const QModelIndex& index) const override;
|
||||
void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override;
|
||||
|
||||
signals:
|
||||
void textChanged(QString before, QString after) const;
|
||||
|
||||
private slots:
|
||||
void editingDone();
|
||||
};
|
||||
|
@ -66,7 +66,7 @@ class AccountListPage : public QMainWindow, public BasePage {
|
||||
return icon;
|
||||
}
|
||||
QString id() const override { return "accounts"; }
|
||||
QString helpPage() const override { return "/getting-started/adding-an-account"; }
|
||||
QString helpPage() const override { return "getting-started/adding-an-account"; }
|
||||
void retranslate() override;
|
||||
|
||||
public slots:
|
||||
|
@ -65,6 +65,15 @@ enum InstSortMode {
|
||||
Sort_LastLaunch
|
||||
};
|
||||
|
||||
enum InstRenamingMode {
|
||||
// Rename metadata only.
|
||||
Rename_Always,
|
||||
// Ask everytime.
|
||||
Rename_Ask,
|
||||
// Rename physical directory too.
|
||||
Rename_Never
|
||||
};
|
||||
|
||||
LauncherPage::LauncherPage(QWidget* parent) : QWidget(parent), ui(new Ui::LauncherPage)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
@ -221,6 +230,7 @@ void LauncherPage::applySettings()
|
||||
s->set("DownloadsDirWatchRecursive", ui->downloadsDirWatchRecursiveCheckBox->isChecked());
|
||||
s->set("MoveModsFromDownloadsDir", ui->downloadsDirMoveCheckBox->isChecked());
|
||||
|
||||
// Instance
|
||||
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
|
||||
switch (sortMode) {
|
||||
case Sort_LastLaunch:
|
||||
@ -232,6 +242,20 @@ void LauncherPage::applySettings()
|
||||
break;
|
||||
}
|
||||
|
||||
auto renamingMode = (InstRenamingMode)ui->renamingBehaviorComboBox->currentIndex();
|
||||
switch (renamingMode) {
|
||||
case Rename_Always:
|
||||
s->set("InstRenamingMode", "MetadataOnly");
|
||||
break;
|
||||
case Rename_Never:
|
||||
s->set("InstRenamingMode", "PhysicalDir");
|
||||
break;
|
||||
case Rename_Ask:
|
||||
default:
|
||||
s->set("InstRenamingMode", "AskEverytime");
|
||||
break;
|
||||
}
|
||||
|
||||
// Mods
|
||||
s->set("ModMetadataDisabled", !ui->metadataEnableBtn->isChecked());
|
||||
s->set("ModDependenciesDisabled", !ui->dependenciesEnableBtn->isChecked());
|
||||
@ -267,14 +291,25 @@ void LauncherPage::loadSettings()
|
||||
ui->downloadsDirWatchRecursiveCheckBox->setChecked(s->get("DownloadsDirWatchRecursive").toBool());
|
||||
ui->downloadsDirMoveCheckBox->setChecked(s->get("MoveModsFromDownloadsDir").toBool());
|
||||
|
||||
// Instance
|
||||
QString sortMode = s->get("InstSortMode").toString();
|
||||
|
||||
if (sortMode == "LastLaunch") {
|
||||
ui->sortLastLaunchedBtn->setChecked(true);
|
||||
} else {
|
||||
ui->sortByNameBtn->setChecked(true);
|
||||
}
|
||||
|
||||
QString renamingMode = s->get("InstRenamingMode").toString();
|
||||
InstRenamingMode renamingModeEnum;
|
||||
if (renamingMode == "MetadataOnly") {
|
||||
renamingModeEnum = Rename_Always;
|
||||
} else if (renamingMode == "PhysicalDir") {
|
||||
renamingModeEnum = Rename_Never;
|
||||
} else {
|
||||
renamingModeEnum = Rename_Ask;
|
||||
}
|
||||
ui->renamingBehaviorComboBox->setCurrentIndex(renamingModeEnum);
|
||||
|
||||
// Mods
|
||||
ui->metadataEnableBtn->setChecked(!s->get("ModMetadataDisabled").toBool());
|
||||
ui->metadataWarningLabel->setHidden(ui->metadataEnableBtn->isChecked());
|
||||
|
@ -43,7 +43,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>575</width>
|
||||
<height>1294</height>
|
||||
<height>1368</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
@ -99,6 +99,51 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="renamingBehaviorLabel">
|
||||
<property name="text">
|
||||
<string>When renaming instances...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="renamingBehaviorComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ask what to do with the folder</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always rename the folder</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Never rename the folder - only the displayed name</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>6</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="preferMenuBarCheckBox">
|
||||
<property name="toolTip">
|
||||
@ -185,29 +230,16 @@
|
||||
<string>Folders</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="foldersBoxLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelJavaDir">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelIconsDir">
|
||||
<property name="text">
|
||||
<string>&Java</string>
|
||||
<string>&Icons</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>javaDirTextBox</cstring>
|
||||
<cstring>iconsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelInstDir">
|
||||
<property name="text">
|
||||
<string>I&nstances</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>instDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="iconsDirTextBox"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="labelDownloadsDir">
|
||||
<property name="text">
|
||||
@ -218,56 +250,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelIconsDir">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelJavaDir">
|
||||
<property name="text">
|
||||
<string>&Icons</string>
|
||||
<string>&Java</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>iconsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="skinsDirTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="modsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="javaDirTextBox"/>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="downloadsDirTextBox"/>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QPushButton" name="downloadsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="modsDirTextBox"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelModsDir">
|
||||
<property name="text">
|
||||
<string>&Mods</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>modsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="javaDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
<cstring>javaDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -281,19 +270,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="instDirBrowseBtn">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelInstDir">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
<string>I&nstances</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>instDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="iconsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="javaDirTextBox"/>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QPushButton" name="skinsDirBrowseBtn">
|
||||
@ -302,9 +290,66 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="downloadsDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="iconsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="skinsDirTextBox"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="instDirTextBox"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="iconsDirTextBox"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="instDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="modsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QPushButton" name="downloadsDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="javaDirBrowseBtn">
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelModsDir">
|
||||
<property name="text">
|
||||
<string>&Mods</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>modsDirTextBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="modsDirTextBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -620,7 +665,6 @@
|
||||
<tabstop>downloadsDirBrowseBtn</tabstop>
|
||||
<tabstop>metadataEnableBtn</tabstop>
|
||||
<tabstop>dependenciesEnableBtn</tabstop>
|
||||
<tabstop>sortLastLaunchedBtn</tabstop>
|
||||
<tabstop>sortByNameBtn</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
@ -245,7 +245,6 @@ ModrinthManagedPackPage::ModrinthManagedPackPage(BaseInstance* inst, InstanceWin
|
||||
}
|
||||
|
||||
// MODRINTH
|
||||
|
||||
void ModrinthManagedPackPage::parseManagedPack()
|
||||
{
|
||||
qDebug() << "Parsing Modrinth pack";
|
||||
@ -338,6 +337,25 @@ void ModrinthManagedPackPage::suggestVersion()
|
||||
ManagedPackPage::suggestVersion();
|
||||
}
|
||||
|
||||
/// @brief Called when the update task has completed.
|
||||
/// Internally handles the closing of the instance window if the update was successful and shows a message box.
|
||||
/// @param did_succeed Whether the update task was successful.
|
||||
void ManagedPackPage::onUpdateTaskCompleted(bool did_succeed) const
|
||||
{
|
||||
// Close the window if the update was successful
|
||||
if (did_succeed) {
|
||||
if (m_instance_window != nullptr)
|
||||
m_instance_window->close();
|
||||
|
||||
CustomMessageBox::selectable(nullptr, tr("Update Successful"), tr("The instance updated to pack version %1 successfully.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Information)
|
||||
->show();
|
||||
} else {
|
||||
CustomMessageBox::selectable(nullptr, tr("Update Failed"), tr("The instance failed to update to pack version %1. Please check launcher logs for more information.").arg(m_inst->getManagedPackVersionName()), QMessageBox::Critical)
|
||||
->show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ModrinthManagedPackPage::update()
|
||||
{
|
||||
auto index = ui->versionsComboBox->currentIndex();
|
||||
@ -363,10 +381,9 @@ void ModrinthManagedPackPage::update()
|
||||
extracted->setIcon(m_inst->iconKey());
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
// Run our task then handle the result
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
onUpdateTaskCompleted(did_succeed);
|
||||
}
|
||||
|
||||
void ModrinthManagedPackPage::updateFromFile()
|
||||
@ -386,14 +403,12 @@ void ModrinthManagedPackPage::updateFromFile()
|
||||
extracted->setIcon(m_inst->iconKey());
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
// Run our task then handle the result
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
onUpdateTaskCompleted(did_succeed);
|
||||
}
|
||||
|
||||
// FLAME
|
||||
|
||||
FlameManagedPackPage::FlameManagedPackPage(BaseInstance* inst, InstanceWindow* instance_window, QWidget* parent)
|
||||
: ManagedPackPage(inst, instance_window, parent)
|
||||
{
|
||||
@ -531,9 +546,7 @@ void FlameManagedPackPage::update()
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
onUpdateTaskCompleted(did_succeed);
|
||||
}
|
||||
|
||||
void FlameManagedPackPage::updateFromFile()
|
||||
@ -555,8 +568,6 @@ void FlameManagedPackPage::updateFromFile()
|
||||
extracted->setConfirmUpdate(false);
|
||||
|
||||
auto did_succeed = runUpdateTask(extracted);
|
||||
|
||||
if (m_instance_window && did_succeed)
|
||||
m_instance_window->close();
|
||||
onUpdateTaskCompleted(did_succeed);
|
||||
}
|
||||
#include "ManagedPackPage.moc"
|
||||
|
@ -94,6 +94,8 @@ class ManagedPackPage : public QWidget, public BasePage {
|
||||
BaseInstance* m_inst;
|
||||
|
||||
bool m_loaded = false;
|
||||
|
||||
void onUpdateTaskCompleted(bool did_succeed) const;
|
||||
};
|
||||
|
||||
/** Simple page for when we aren't a managed pack. */
|
||||
|
@ -36,9 +36,9 @@
|
||||
*/
|
||||
|
||||
#include "ServersPage.h"
|
||||
#include "ServerPingTask.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui_ServersPage.h"
|
||||
#include "ServerPingTask.h"
|
||||
|
||||
#include <FileSystem.h>
|
||||
#include <io/stream_reader.h>
|
||||
@ -49,10 +49,10 @@
|
||||
#include <tag_string.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <tasks/ConcurrentTask.h>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QMenu>
|
||||
#include <QTimer>
|
||||
#include <tasks/ConcurrentTask.h>
|
||||
|
||||
static const int COLUMN_COUNT = 3; // 3 , TBD: latency and other nice things.
|
||||
|
||||
@ -113,8 +113,8 @@ struct Server {
|
||||
// Data - temporary
|
||||
bool m_checked = false;
|
||||
bool m_up = false;
|
||||
QString m_motd; // https://mctools.org/motd-creator
|
||||
std::optional<int> m_currentPlayers; // nullopt if not calculated/calculating
|
||||
QString m_motd; // https://mctools.org/motd-creator
|
||||
std::optional<int> m_currentPlayers; // nullopt if not calculated/calculating
|
||||
int m_maxPlayers = 0;
|
||||
};
|
||||
|
||||
@ -317,10 +317,10 @@ class ServersModel : public QAbstractListModel {
|
||||
if (row < 0 || row >= m_servers.size())
|
||||
return QVariant();
|
||||
|
||||
switch (column) {
|
||||
case 0:
|
||||
switch (role) {
|
||||
case Qt::DecorationRole: {
|
||||
switch (role) {
|
||||
case Qt::DecorationRole: {
|
||||
switch (column) {
|
||||
case 0: {
|
||||
auto& bytes = m_servers[row].m_icon;
|
||||
if (bytes.size()) {
|
||||
QPixmap px;
|
||||
@ -329,31 +329,32 @@ class ServersModel : public QAbstractListModel {
|
||||
}
|
||||
return APPLICATION->getThemedIcon("unknown_server");
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
return m_servers[row].m_name;
|
||||
case ServerPtrRole:
|
||||
return QVariant::fromValue<void*>((void*)&m_servers[row]);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 1:
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case 1:
|
||||
return m_servers[row].m_address;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case 2:
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case 2:
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (m_servers[row].m_currentPlayers) {
|
||||
return *m_servers[row].m_currentPlayers;
|
||||
} else {
|
||||
return "...";
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
if (column == 0)
|
||||
return m_servers[row].m_name;
|
||||
else
|
||||
return QVariant();
|
||||
case ServerPtrRole:
|
||||
if (column == 0)
|
||||
return QVariant::fromValue<void*>((void*)&m_servers[row]);
|
||||
else
|
||||
return QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
@ -447,22 +448,22 @@ class ServersModel : public QAbstractListModel {
|
||||
}
|
||||
|
||||
m_currentQueryTask = ConcurrentTask::Ptr(
|
||||
new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt())
|
||||
);
|
||||
new ConcurrentTask("Query servers status", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
|
||||
int row = 0;
|
||||
for (Server &server : m_servers) {
|
||||
for (Server& server : m_servers) {
|
||||
// reset current players
|
||||
server.m_currentPlayers = {};
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
|
||||
// Start task to query server status
|
||||
auto target = MinecraftTarget::parse(server.m_address, false);
|
||||
auto *task = new ServerPingTask(target.address, target.port);
|
||||
auto* task = new ServerPingTask(target.address, target.port);
|
||||
m_currentQueryTask->addTask(Task::Ptr(task));
|
||||
|
||||
// Update the model when the task is done
|
||||
connect(task, &Task::finished, this, [this, task, row]() {
|
||||
if (m_servers.size() < row) return;
|
||||
if (m_servers.size() < row)
|
||||
return;
|
||||
m_servers[row].m_currentPlayers = task->m_outputOnlinePlayers;
|
||||
emit dataChanged(index(row, 0), index(row, COLUMN_COUNT - 1));
|
||||
});
|
||||
@ -717,7 +718,7 @@ void ServersPage::openedImpl()
|
||||
|
||||
ui->toolBar->setVisibilityState(m_wide_bar_setting->get().toByteArray());
|
||||
|
||||
// ping servers
|
||||
// ping servers
|
||||
m_model->queryServersStatus();
|
||||
}
|
||||
|
||||
|
@ -166,12 +166,9 @@ void WorldListPage::retranslate()
|
||||
|
||||
bool WorldListPage::worldListFilter(QKeyEvent* keyEvent)
|
||||
{
|
||||
switch (keyEvent->key()) {
|
||||
case Qt::Key_Delete:
|
||||
on_actionRemove_triggered();
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
if (keyEvent->key() == Qt::Key_Delete) {
|
||||
on_actionRemove_triggered();
|
||||
return true;
|
||||
}
|
||||
return QWidget::eventFilter(ui->worldTreeView, keyEvent);
|
||||
}
|
||||
|
@ -45,7 +45,9 @@
|
||||
|
||||
#include "net/ApiDownload.h"
|
||||
|
||||
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||
AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent,
|
||||
const ATLauncher::PackVersion& version,
|
||||
QVector<ATLauncher::VersionMod> mods)
|
||||
: QAbstractListModel(parent), m_version(version), m_mods(mods)
|
||||
{
|
||||
// fill mod index
|
||||
@ -233,7 +235,7 @@ void AtlOptionalModListModel::clearAll()
|
||||
emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn), AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
|
||||
}
|
||||
|
||||
void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index)
|
||||
void AtlOptionalModListModel::toggleMod(const ATLauncher::VersionMod& mod, int index)
|
||||
{
|
||||
auto enable = !m_selection[mod.name];
|
||||
|
||||
@ -251,7 +253,7 @@ void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index)
|
||||
setMod(mod, index, enable);
|
||||
}
|
||||
|
||||
void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit)
|
||||
void AtlOptionalModListModel::setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit)
|
||||
{
|
||||
if (m_selection[mod.name] == enable)
|
||||
return;
|
||||
@ -313,7 +315,7 @@ void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool
|
||||
}
|
||||
}
|
||||
|
||||
AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods)
|
||||
AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector<ATLauncher::VersionMod> mods)
|
||||
: QDialog(parent), ui(new Ui::AtlOptionalModDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
@ -55,7 +55,7 @@ class AtlOptionalModListModel : public QAbstractListModel {
|
||||
DescriptionColumn,
|
||||
};
|
||||
|
||||
AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
|
||||
AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector<ATLauncher::VersionMod> mods);
|
||||
|
||||
QVector<QString> getResult();
|
||||
|
||||
@ -78,8 +78,8 @@ class AtlOptionalModListModel : public QAbstractListModel {
|
||||
void clearAll();
|
||||
|
||||
private:
|
||||
void toggleMod(ATLauncher::VersionMod mod, int index);
|
||||
void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
|
||||
void toggleMod(const ATLauncher::VersionMod& mod, int index);
|
||||
void setMod(const ATLauncher::VersionMod& mod, int index, bool enable, bool shouldEmit = true);
|
||||
|
||||
private:
|
||||
NetJob::Ptr m_jobPtr;
|
||||
@ -97,7 +97,7 @@ class AtlOptionalModDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AtlOptionalModDialog(QWidget* parent, ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods);
|
||||
AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector<ATLauncher::VersionMod> mods);
|
||||
~AtlOptionalModDialog() override;
|
||||
|
||||
QVector<QString> getResult() { return listModel->getResult(); }
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {}
|
||||
|
||||
std::optional<QVector<QString>> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version,
|
||||
std::optional<QVector<QString>> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version,
|
||||
QVector<ATLauncher::VersionMod> mods)
|
||||
{
|
||||
AtlOptionalModDialog optionalModDialog(m_parent, version, mods);
|
||||
|
@ -48,7 +48,8 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt
|
||||
|
||||
private:
|
||||
QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override;
|
||||
std::optional<QVector<QString>> chooseOptionalMods(ATLauncher::PackVersion version, QVector<ATLauncher::VersionMod> mods) override;
|
||||
std::optional<QVector<QString>> chooseOptionalMods(const ATLauncher::PackVersion& version,
|
||||
QVector<ATLauncher::VersionMod> mods) override;
|
||||
void displayMessage(QString message) override;
|
||||
|
||||
private:
|
||||
|
@ -32,7 +32,7 @@ void FlameModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject&
|
||||
|
||||
void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
FlameMod::loadIndexedPackVersions(m, arr);
|
||||
}
|
||||
|
||||
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
|
||||
@ -65,7 +65,7 @@ void FlameResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJso
|
||||
|
||||
void FlameResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
FlameMod::loadIndexedPackVersions(m, arr);
|
||||
}
|
||||
|
||||
bool FlameResourcePackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
@ -93,7 +93,7 @@ void FlameTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJson
|
||||
|
||||
void FlameTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
FlameMod::loadIndexedPackVersions(m, arr);
|
||||
|
||||
QVector<ModPlatform::IndexedVersion> filtered_versions(m.versions.size());
|
||||
|
||||
@ -157,7 +157,7 @@ void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonO
|
||||
|
||||
void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
|
||||
{
|
||||
FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
|
||||
FlameMod::loadIndexedPackVersions(m, arr);
|
||||
}
|
||||
|
||||
bool FlameShaderPackModel::optedOut(const ModPlatform::IndexedVersion& ver) const
|
||||
|
@ -106,9 +106,6 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
}
|
||||
|
||||
auto pack = m_modpacks.at(pos);
|
||||
if (role == Qt::ToolTipRole) {
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole:
|
||||
return tr("Minecraft %1").arg(pack.mcVersion);
|
||||
|
@ -213,7 +213,7 @@ void ListModel::fill(ModpackList modpacks_)
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ListModel::addPack(Modpack modpack)
|
||||
void ListModel::addPack(const Modpack& modpack)
|
||||
{
|
||||
beginResetModel();
|
||||
this->modpacks.append(modpack);
|
||||
|
@ -62,7 +62,7 @@ class ListModel : public QAbstractListModel {
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
void fill(ModpackList modpacks);
|
||||
void addPack(Modpack modpack);
|
||||
void addPack(const Modpack& modpack);
|
||||
void clear();
|
||||
void remove(int row);
|
||||
|
||||
|
@ -213,7 +213,7 @@ void Page::ftbPackDataDownloadAborted()
|
||||
CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show();
|
||||
}
|
||||
|
||||
void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
|
||||
void Page::ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack)
|
||||
{
|
||||
privateListModel->addPack(pack);
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class Page : public QWidget, public ModpackProviderBasePage {
|
||||
void ftbPackDataDownloadFailed(QString reason);
|
||||
void ftbPackDataDownloadAborted();
|
||||
|
||||
void ftbPrivatePackDataDownloadSuccessfully(Modpack pack);
|
||||
void ftbPrivatePackDataDownloadSuccessfully(const Modpack& pack);
|
||||
void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode);
|
||||
|
||||
void onSortingSelectionChanged(QString data);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user