diff --git a/.github/workflows/blocked-prs.yml b/.github/workflows/blocked-prs.yml new file mode 100644 index 000000000..bd49b7230 --- /dev/null +++ b/.github/workflows/blocked-prs.yml @@ -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 '

PR Dependencies :pushpin:

' > "$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<> "$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 + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b71d642a..11ef262ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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' diff --git a/.github/workflows/merge-blocking-pr.yml b/.github/workflows/merge-blocking-pr.yml new file mode 100644 index 000000000..d6410f997 --- /dev/null +++ b/.github/workflows/merge-blocking-pr.yml @@ -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:` 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") + diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 6c2b13de7..fea0df6ce 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -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" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 034a8548b..8a7da812e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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 diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 134281b2c..411a5bbeb 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -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 diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index 48a8418f5..cdd360589 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -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: diff --git a/.gitignore b/.gitignore index b5523f685..b563afbc7 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.gitmodules b/.gitmodules index 0f437d277..7ad40becb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index bc2e77d4a..10153c3ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 ############################### diff --git a/COPYING.md b/COPYING.md index 0ea3437d3..f1f0b3a70 100644 --- a/COPYING.md +++ b/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 - - 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 and others diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 2124d02ae..6bebcb80e 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -34,8 +34,8 @@ */ #include -#include "BuildConfig.h" #include +#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); } - diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 099d9b5ca..b59adcb57 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -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 diff --git a/default.nix b/default.nix index 6466507b7..5ecef5590 100644 --- a/default.nix +++ b/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 diff --git a/flake.lock b/flake.lock index 0d1bd2df0..2d79b8335 100644 --- a/flake.lock +++ b/flake.lock @@ -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" } } diff --git a/flake.nix b/flake.nix index 54add656d..fd3003bc4 100644 --- a/flake.nix +++ b/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; }; diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bf9fa32ed..e131c57e8 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -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 diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index ccfd0b847..eab91a5eb 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -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(); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 9827a08b4..2a2b4dc4a 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -126,6 +126,9 @@ class BaseInstance : public QObject, public std::enable_shared_from_this #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // 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() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -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"; diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index c5beef7bd..bf91c603c 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -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; diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index d335b11c4..fb5963532 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -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()); diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h index 0f7f1020d..3aba13e5c 100644 --- a/launcher/InstanceCopyTask.h +++ b/launcher/InstanceCopyTask.h @@ -28,7 +28,7 @@ class InstanceCopyTask : public InstanceTask { InstancePtr m_origInstance; QFuture m_copyFuture; QFutureWatcher m_copyFutureWatcher; - std::unique_ptr m_matcher; + IPathMatcher::Ptr m_matcher; bool m_keepPlaytime; bool m_useLinks = false; bool m_useHardLinks = false; diff --git a/launcher/InstanceDirUpdate.cpp b/launcher/InstanceDirUpdate.cpp new file mode 100644 index 000000000..8be0dccac --- /dev/null +++ b/launcher/InstanceDirUpdate.cpp @@ -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 . + * + * 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 + +#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.
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:
" + " - Old instance root: %1
" + " - New instance root: %2
" + "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; +} diff --git a/launcher/InstanceDirUpdate.h b/launcher/InstanceDirUpdate.h new file mode 100644 index 000000000..b92a59c4c --- /dev/null +++ b/launcher/InstanceDirUpdate.h @@ -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 . + * + * 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); diff --git a/launcher/JavaCommon.h b/launcher/JavaCommon.h index a21b5a494..0e4aa2b0a 100644 --- a/launcher/JavaCommon.h +++ b/launcher/JavaCommon.h @@ -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(); diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp index 7538ce08c..3d9d95eb6 100644 --- a/launcher/VersionProxyModel.cpp +++ b/launcher/VersionProxyModel.cpp @@ -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)) { diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp index b641b41d5..a082b4b5b 100644 --- a/launcher/filelink/FileLink.cpp +++ b/launcher/filelink/FileLink.cpp @@ -40,24 +40,8 @@ #include "WindowsConsole.h" #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // 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() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this)) { diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index d6534b910..4deee9712 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -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) { diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp index 812b13c71..cf27be676 100644 --- a/launcher/minecraft/WorldList.cpp +++ b/launcher/minecraft/WorldList.cpp @@ -208,13 +208,9 @@ QVariant WorldList::data(const QModelIndex& index, int role) const } case Qt::UserRole: - switch (column) { - case SizeColumn: - return QVariant::fromValue(world.bytes()); - - default: - return data(index, Qt::DisplayRole); - } + if (column == SizeColumn) + return QVariant::fromValue(world.bytes()); + return data(index, Qt::DisplayRole); case Qt::ToolTipRole: { if (column == InfoColumn) { diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index d276d4c41..3cbbb2a74 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -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) diff --git a/launcher/minecraft/auth/AuthFlow.cpp b/launcher/minecraft/auth/AuthFlow.cpp index 19fbe15dd..287831b2f 100644 --- a/launcher/minecraft/auth/AuthFlow.cpp +++ b/launcher/minecraft/auth/AuthFlow.cpp @@ -1,5 +1,4 @@ #include -#include #include #include diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h index 54e7d69e0..cbe604805 100644 --- a/launcher/minecraft/auth/AuthSession.h +++ b/launcher/minecraft/auth/AuthSession.h @@ -1,12 +1,9 @@ #pragma once -#include #include #include -#include "QObjectPtr.h" class MinecraftAccount; -class QNetworkAccessManager; struct AuthSession { bool MakeOffline(QString offline_playername); diff --git a/launcher/minecraft/gameoptions/GameOptions.cpp b/launcher/minecraft/gameoptions/GameOptions.cpp index 4f4fb99a7..25f7074ec 100644 --- a/launcher/minecraft/gameoptions/GameOptions.cpp +++ b/launcher/minecraft/gameoptions/GameOptions.cpp @@ -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 diff --git a/launcher/minecraft/mod/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp index 4a9e77a70..580d5c714 100644 --- a/launcher/minecraft/mod/DataPack.cpp +++ b/launcher/minecraft/mod/DataPack.cpp @@ -105,19 +105,16 @@ std::pair DataPack::compatibleVersions() const int DataPack::compare(const Resource& other, SortType type) const { auto const& cast_other = static_cast(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; } diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h index 4855b0203..266c2061b 100644 --- a/launcher/minecraft/mod/DataPack.h +++ b/launcher/minecraft/mod/DataPack.h @@ -35,8 +35,6 @@ class Version; class DataPack : public Resource { Q_OBJECT public: - using Ptr = shared_qobject_ptr; - 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; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 50fb45d77..99fc39ce0 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -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; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 8a352c66c..deb1859de 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -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; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 027f3d4ca..43888ae27 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -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(); } diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 42463fe8f..16d8c2a89 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -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; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 70555fa35..d4900616b 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -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 {}; } diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 81fb91485..4ed3c67e3 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -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 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(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; -} diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 2254fc5c4..bb5aeecb5 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -1,6 +1,7 @@ #pragma once #include "Resource.h" +#include "minecraft/mod/DataPack.h" #include #include @@ -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; + 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 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), diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index e106a2be9..d9f27a043 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -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(resource)); + return new LocalDataPackParseTask(m_next_resolution_ticket, dynamic_cast(&resource)); } diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp index 19b15709d..530ce44cc 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp @@ -23,6 +23,8 @@ #include "FileSystem.h" #include "Json.h" +#include "minecraft/mod/ResourcePack.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" #include #include @@ -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(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(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(); } diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h index 12fd8c82c..7355783df 100644 --- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.h @@ -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; }; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 0b80db82d..db4b2e55c 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -20,6 +20,7 @@ #include "FileSystem.h" #include "Json.h" +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include #include @@ -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(); -} diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 97bf7b2ba..6b4378aa6 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -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; -}; diff --git a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp index 74d8d8d60..d45f537fa 100644 --- a/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalWorldSaveParseTask.cpp @@ -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(); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index ffc358fbb..ee5960e30 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -62,7 +62,7 @@ class UserInteractionSupport { /** * Requests a user interaction to select which optional mods should be installed. */ - virtual std::optional> chooseOptionalMods(PackVersion version, QVector mods) = 0; + virtual std::optional> chooseOptionalMods(const PackVersion& version, QVector mods) = 0; /** * Requests a user interaction to select a component version from a given version list diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index 63b7d2b50..5f812d219 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -20,7 +20,6 @@ #include #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& network, Flame::Manifest& toProcess) - : m_network(network), m_manifest(toProcess) -{} +Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess) : m_manifest(toProcess) {} bool Flame::FileResolvingTask::abort() { diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index edd9fce9a..3fe8dfb1a 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -17,8 +17,6 @@ */ #pragma once -#include - #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& 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 m_network; Flame::Manifest m_manifest; std::shared_ptr m_result; Task::Ptr m_task; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 699eb792a..a06793de0 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -102,57 +102,6 @@ QString FlameAPI::getModDescription(int modId) return description; } -QList 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(QString("Flame::GetLatestVersion(%1)").arg(args.pack.name), APPLICATION->network()); - auto response = std::make_shared(); - QList 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 response) const { auto netJob = makeShared(QString("Flame::GetProjects"), APPLICATION->network()); @@ -266,7 +215,7 @@ QList FlameAPI::loadModCategories(std::shared_ptr FlameAPI::getLatestVersion(QList versions, +std::optional FlameAPI::getLatestVersion(QVector versions, QList instanceLoaders, ModPlatform::ModLoaderTypes modLoaders) { diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 3ca0d5448..509e1abcd 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -15,8 +15,7 @@ class FlameAPI : public NetworkResourceAPI { QString getModFileChangelog(int modId, int fileId); QString getModDescription(int modId); - QList getLatestVersions(VersionSearchArgs&& args); - std::optional getLatestVersion(QList versions, + std::optional getLatestVersion(QVector versions, QList 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 getInfoURL(QString const& id) const override - { - return QString("https://api.curseforge.com/v1/mods/%1").arg(id); - } - [[nodiscard]] std::optional 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 getInfoURL(QString const& id) const override + { + return QString("https://api.curseforge.com/v1/mods/%1").arg(id); + } [[nodiscard]] std::optional getDependencyURL(DependencySearchArgs const& args) const override { auto addonId = args.dependency.addonId.toString(); diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp index 2b469276d..047813675 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.cpp +++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp @@ -3,115 +3,31 @@ #include "FlameAPI.h" #include "FlameModIndex.h" -#include +#include #include #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(); - 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(); - 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(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(); + 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(); - 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(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(pack, latest_ver.value())); +void FlameCheckUpdate::getLatestVersionCallback(Resource* resource, std::shared_ptr 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(); + 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(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(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(pack, latest_ver.value())); } + +void FlameCheckUpdate::collectBlockedMods() +{ + QStringList addonIds; + QHash 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(); + 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(); +} \ No newline at end of file diff --git a/launcher/modplatform/flame/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h index 6543a0e04..eb80ce47c 100644 --- a/launcher/modplatform/flame/FlameCheckUpdate.h +++ b/launcher/modplatform/flame/FlameCheckUpdate.h @@ -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 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 m_blocked; }; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 16d47f64a..22c9e603b 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -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(); diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index eb5b3cc67..ff9d2d9ce 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -76,10 +76,7 @@ static QString enumToString(int hash_algorithm) } } -void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, - QJsonArray& arr, - [[maybe_unused]] const shared_qobject_ptr& network, - const BaseInstance* inst) +void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr) { QVector unsortedVersions; for (auto versionIter : arr) { diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index 1bcaa44ba..f6b4b22be 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -6,7 +6,6 @@ #include "modplatform/ModIndex.h" -#include #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& 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 \ No newline at end of file diff --git a/launcher/modplatform/helpers/ExportToModList.cpp b/launcher/modplatform/helpers/ExportToModList.cpp index bddc7e320..678f89917 100644 --- a/launcher/modplatform/helpers/ExportToModList.cpp +++ b/launcher/modplatform/helpers/ExportToModList.cpp @@ -203,6 +203,7 @@ QString exportToModList(QList 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 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) diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index e37d949d5..4c7a8f6aa 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -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); }; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index d6252663f..c04c0b2f3 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -52,7 +52,7 @@ namespace LegacyFTB { -PackInstallTask::PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version) +PackInstallTask::PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version) { m_pack = pack; m_version = version; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index 30ff48597..42808a1a2 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -18,7 +18,7 @@ class PackInstallTask : public InstanceTask { Q_OBJECT public: - explicit PackInstallTask(shared_qobject_ptr network, Modpack pack, QString version); + explicit PackInstallTask(shared_qobject_ptr network, const Modpack& pack, QString version); virtual ~PackInstallTask() {} bool canAbort() const override { return true; } diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index 72550937c..16b300b02 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -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 unsortedVersions; for (auto versionIter : arr) { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index 93f91eec2..16f3d262c 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -19,14 +19,13 @@ #include "modplatform/ModIndex.h" -#include #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; diff --git a/launcher/net/FileSink.cpp b/launcher/net/FileSink.cpp index 95c1a8f44..3a58a4667 100644 --- a/launcher/net/FileSink.cpp +++ b/launcher/net/FileSink.cpp @@ -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)) diff --git a/launcher/net/FileSink.h b/launcher/net/FileSink.h index 272f8ddc3..67c25361c 100644 --- a/launcher/net/FileSink.h +++ b/launcher/net/FileSink.h @@ -58,7 +58,7 @@ class FileSink : public Sink { protected: QString m_filename; - bool wroteAnyData = false; + bool m_wroteAnyData = false; std::unique_ptr m_output_file; }; } // namespace Net diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index 889588a11..432c0c84b 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -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()); } diff --git a/launcher/net/NetRequest.cpp b/launcher/net/NetRequest.cpp index cf2e72858..310653508 100644 --- a/launcher/net/NetRequest.cpp +++ b/launcher/net/NetRequest.cpp @@ -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: diff --git a/launcher/pathmatcher/MultiMatcher.h b/launcher/pathmatcher/MultiMatcher.h index 3e2bdb95d..ccd5a9163 100644 --- a/launcher/pathmatcher/MultiMatcher.h +++ b/launcher/pathmatcher/MultiMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include "IPathMatcher.h" diff --git a/launcher/pathmatcher/RegexpMatcher.h b/launcher/pathmatcher/RegexpMatcher.h index a6a3e616d..18c42f887 100644 --- a/launcher/pathmatcher/RegexpMatcher.h +++ b/launcher/pathmatcher/RegexpMatcher.h @@ -1,3 +1,5 @@ +#pragma once + #include #include "IPathMatcher.h" diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 8b41f838c..f29d5ce89 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -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()); diff --git a/launcher/ui/dialogs/CustomMessageBox.cpp b/launcher/ui/dialogs/CustomMessageBox.cpp index 1af47a449..ca0fe99e0 100644 --- a/launcher/ui/dialogs/CustomMessageBox.cpp +++ b/launcher/ui/dialogs/CustomMessageBox.cpp @@ -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; } diff --git a/launcher/ui/dialogs/CustomMessageBox.h b/launcher/ui/dialogs/CustomMessageBox.h index a9bc6a24a..1ee29903e 100644 --- a/launcher/ui/dialogs/CustomMessageBox.h +++ b/launcher/ui/dialogs/CustomMessageBox.h @@ -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); } diff --git a/launcher/ui/dialogs/ExportToModListDialog.ui b/launcher/ui/dialogs/ExportToModListDialog.ui index 3afda2fa8..ec049d7e7 100644 --- a/launcher/ui/dialogs/ExportToModListDialog.ui +++ b/launcher/ui/dialogs/ExportToModListDialog.ui @@ -79,6 +79,9 @@ 0 + + This text supports the following placeholders: {name} - Mod name {mod_id} - Mod ID {url} - Mod URL {version} - Mod version {authors} - Mod authors + diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 9897687e3..aa2f67bdb 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -92,7 +92,7 @@ ProgressDialog::~ProgressDialog() { for (auto conn : this->m_taskConnections) { disconnect(conn); - } + } delete ui; } diff --git a/launcher/ui/dialogs/ProgressDialog.h b/launcher/ui/dialogs/ProgressDialog.h index 4a696a49d..50e4418da 100644 --- a/launcher/ui/dialogs/ProgressDialog.h +++ b/launcher/ui/dialogs/ProgressDialog.h @@ -93,7 +93,7 @@ class ProgressDialog : public QDialog { Ui::ProgressDialog* ui; Task* m_task; - + QList m_taskConnections; bool m_is_multi_step = false; diff --git a/launcher/ui/dialogs/ResourceUpdateDialog.cpp b/launcher/ui/dialogs/ResourceUpdateDialog.cpp index 7e29e1192..aa4bbd294 100644 --- a/launcher/ui/dialogs/ResourceUpdateDialog.cpp +++ b/launcher/ui/dialogs/ResourceUpdateDialog.cpp @@ -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(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(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)); diff --git a/launcher/ui/dialogs/skins/SkinManageDialog.cpp b/launcher/ui/dialogs/skins/SkinManageDialog.cpp index 2127fc9cc..3bc0bc2d9 100644 --- a/launcher/ui/dialogs/skins/SkinManageDialog.cpp +++ b/launcher/ui/dialogs/skins/SkinManageDialog.cpp @@ -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(); } } diff --git a/launcher/ui/instanceview/InstanceDelegate.cpp b/launcher/ui/instanceview/InstanceDelegate.cpp index d947163bc..c7115801e 100644 --- a/launcher/ui/instanceview/InstanceDelegate.cpp +++ b/launcher/ui/instanceview/InstanceDelegate.cpp @@ -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); } } diff --git a/launcher/ui/instanceview/InstanceDelegate.h b/launcher/ui/instanceview/InstanceDelegate.h index 69dd32ba7..98ff9a2fc 100644 --- a/launcher/ui/instanceview/InstanceDelegate.h +++ b/launcher/ui/instanceview/InstanceDelegate.h @@ -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(); }; diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h index 4f02b7df5..7bd5101c0 100644 --- a/launcher/ui/pages/global/AccountListPage.h +++ b/launcher/ui/pages/global/AccountListPage.h @@ -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: diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 36d404660..132a2c320 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -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()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 3821c09c4..2d04093d2 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -43,7 +43,7 @@ 0 0 575 - 1294 + 1368 @@ -99,6 +99,51 @@ + + + + When renaming instances... + + + + + + + + 0 + 0 + + + + + Ask what to do with the folder + + + + + Always rename the folder + + + + + Never rename the folder - only the displayed name + + + + + + + + Qt::Vertical + + + + 20 + 6 + + + + @@ -185,29 +230,16 @@ Folders - - + + - &Java + &Icons - javaDirTextBox + iconsDirTextBox - - - - I&nstances - - - instDirTextBox - - - - - - @@ -218,56 +250,13 @@ - - + + - &Icons + &Java - iconsDirTextBox - - - - - - - - - - Browse - - - - - - - - - - - - - Browse - - - - - - - - - - &Mods - - - modsDirTextBox - - - - - - - Browse + javaDirTextBox @@ -281,19 +270,18 @@ - - + + - Browse + I&nstances + + + instDirTextBox - - - - Browse - - + + @@ -302,9 +290,66 @@ + + + + + + + Browse + + + + + + + + + + + + + Browse + + + + + + + Browse + + + + + + + Browse + + + + + + + Browse + + + + + + + &Mods + + + modsDirTextBox + + + + + + @@ -620,7 +665,6 @@ downloadsDirBrowseBtn metadataEnableBtn dependenciesEnableBtn - sortLastLaunchedBtn sortByNameBtn diff --git a/launcher/ui/pages/instance/ManagedPackPage.cpp b/launcher/ui/pages/instance/ManagedPackPage.cpp index a909d10d1..0fccd1d33 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.cpp +++ b/launcher/ui/pages/instance/ManagedPackPage.cpp @@ -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" diff --git a/launcher/ui/pages/instance/ManagedPackPage.h b/launcher/ui/pages/instance/ManagedPackPage.h index c44f77070..e8d304c6b 100644 --- a/launcher/ui/pages/instance/ManagedPackPage.h +++ b/launcher/ui/pages/instance/ManagedPackPage.h @@ -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. */ diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index 4bc2e6998..136fb47c7 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -36,9 +36,9 @@ */ #include "ServersPage.h" +#include "ServerPingTask.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui_ServersPage.h" -#include "ServerPingTask.h" #include #include @@ -49,10 +49,10 @@ #include #include +#include #include #include #include -#include 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 m_currentPlayers; // nullopt if not calculated/calculating + QString m_motd; // https://mctools.org/motd-creator + std::optional 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*)&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*)&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(); } diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 4ed5f1f73..dd7486a6c 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -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); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp index 6fb867733..d84737bf5 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp @@ -45,7 +45,9 @@ #include "net/ApiDownload.h" -AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods) +AtlOptionalModListModel::AtlOptionalModListModel(QWidget* parent, + const ATLauncher::PackVersion& version, + QVector 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 mods) +AtlOptionalModDialog::AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods) : QDialog(parent), ui(new Ui::AtlOptionalModDialog) { ui->setupUi(this); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h index 767d277d9..0636715cc 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h @@ -55,7 +55,7 @@ class AtlOptionalModListModel : public QAbstractListModel { DescriptionColumn, }; - AtlOptionalModListModel(QWidget* parent, ATLauncher::PackVersion version, QVector mods); + AtlOptionalModListModel(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); QVector 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 mods); + AtlOptionalModDialog(QWidget* parent, const ATLauncher::PackVersion& version, QVector mods); ~AtlOptionalModDialog() override; QVector getResult() { return listModel->getResult(); } diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp index 0c7257859..7550ff758 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -41,7 +41,7 @@ AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget* parent) : m_parent(parent) {} -std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, +std::optional> AtlUserInteractionSupportImpl::chooseOptionalMods(const ATLauncher::PackVersion& version, QVector mods) { AtlOptionalModDialog optionalModDialog(m_parent, version, mods); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h index 52ced2615..7ff021105 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -48,7 +48,8 @@ class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInt private: QString chooseVersion(Meta::VersionList::Ptr vlist, QString minecraftVersion) override; - std::optional> chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; + std::optional> chooseOptionalMods(const ATLauncher::PackVersion& version, + QVector mods) override; void displayMessage(QString message) override; private: diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp index ae4562be4..fea1fc27a 100644 --- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp @@ -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 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 diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp index f3c737977..0fb83b6cb 100644 --- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp @@ -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); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp index 98922123c..bdb7c64d7 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp @@ -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); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h index f35012078..e4477c929 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h @@ -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); diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 226a30ee3..5752b6c61 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -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); } diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index a2dee24e9..818000c05 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -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); diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp index 856018294..213d6e39e 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp @@ -39,7 +39,7 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion @@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray @@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { - ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance); + ::Modrinth::loadIndexedPackVersions(m, arr); } auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray diff --git a/launcher/ui/themes/SystemTheme.cpp b/launcher/ui/themes/SystemTheme.cpp index a1674455a..7fba08026 100644 --- a/launcher/ui/themes/SystemTheme.cpp +++ b/launcher/ui/themes/SystemTheme.cpp @@ -40,18 +40,29 @@ #include "HintOverrideProxyStyle.h" #include "ThemeManager.h" -SystemTheme::SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme) +// See https://github.com/MultiMC/Launcher/issues/1790 +// or https://github.com/PrismLauncher/PrismLauncher/issues/490 +static const QStringList S_NATIVE_STYLES{ "windows11", "windowsvista", "macos", "system", "windows" }; + +SystemTheme::SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme) { - themeName = isDefaultTheme ? "system" : styleName; - widgetTheme = styleName; - colorPalette = palette; + m_themeName = isDefaultTheme ? "system" : styleName; + m_widgetTheme = styleName; + // NOTE: SystemTheme is reconstructed on page refresh. We can't accurately determine the system palette here + // See also S_NATIVE_STYLES comment + if (S_NATIVE_STYLES.contains(m_themeName)) { + m_colorPalette = defaultPalette; + } else { + auto style = QStyleFactory::create(styleName); + m_colorPalette = style->standardPalette(); + delete style; + } } void SystemTheme::apply(bool initial) { - // See https://github.com/MultiMC/Launcher/issues/1790 - // or https://github.com/PrismLauncher/PrismLauncher/issues/490 - if (initial) { + // See S_NATIVE_STYLES comment + if (initial && S_NATIVE_STYLES.contains(m_themeName)) { QApplication::setStyle(new HintOverrideProxyStyle(QStyleFactory::create(qtTheme()))); return; } @@ -61,35 +72,35 @@ void SystemTheme::apply(bool initial) QString SystemTheme::id() { - return themeName; + return m_themeName; } QString SystemTheme::name() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Windows Vista"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("Windows 11"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("System"); } else { - return themeName; + return m_themeName; } } QString SystemTheme::tooltip() { - if (themeName.toLower() == "windowsvista") { + if (m_themeName.toLower() == "windowsvista") { return QObject::tr("Widget style trying to look like your win32 theme"); - } else if (themeName.toLower() == "windows") { + } else if (m_themeName.toLower() == "windows") { return QObject::tr("Windows 9x inspired widget style"); - } else if (themeName.toLower() == "windows11") { + } else if (m_themeName.toLower() == "windows11") { return QObject::tr("WinUI 3 inspired Qt widget style"); - } else if (themeName.toLower() == "fusion") { + } else if (m_themeName.toLower() == "fusion") { return QObject::tr("The default Qt widget style"); - } else if (themeName.toLower() == "system") { + } else if (m_themeName.toLower() == "system") { return QObject::tr("Your current system theme"); } else { return ""; @@ -98,12 +109,12 @@ QString SystemTheme::tooltip() QString SystemTheme::qtTheme() { - return widgetTheme; + return m_widgetTheme; } QPalette SystemTheme::colorScheme() { - return colorPalette; + return m_colorPalette; } QString SystemTheme::appStyleSheet() diff --git a/launcher/ui/themes/SystemTheme.h b/launcher/ui/themes/SystemTheme.h index 7c260fdc4..7ae24c3db 100644 --- a/launcher/ui/themes/SystemTheme.h +++ b/launcher/ui/themes/SystemTheme.h @@ -38,7 +38,7 @@ class SystemTheme : public ITheme { public: - SystemTheme(const QString& styleName, const QPalette& palette, bool isDefaultTheme); + SystemTheme(const QString& styleName, const QPalette& defaultPalette, bool isDefaultTheme); virtual ~SystemTheme() {} void apply(bool initial) override; @@ -53,7 +53,7 @@ class SystemTheme : public ITheme { QColor fadeColor() override; private: - QPalette colorPalette; - QString widgetTheme; - QString themeName; + QPalette m_colorPalette; + QString m_widgetTheme; + QString m_themeName; }; diff --git a/launcher/ui/themes/ThemeManager.h b/launcher/ui/themes/ThemeManager.h index a9036107c..8de7562d1 100644 --- a/launcher/ui/themes/ThemeManager.h +++ b/launcher/ui/themes/ThemeManager.h @@ -68,8 +68,8 @@ class ThemeManager { QDir m_applicationThemeFolder{ "themes" }; QDir m_catPacksFolder{ "catpacks" }; std::map> m_catPacks; - QString m_defaultStyle; QPalette m_defaultPalette; + QString m_defaultStyle; LogColors m_logColors; void initializeThemes(); diff --git a/launcher/ui/widgets/FocusLineEdit.h b/launcher/ui/widgets/FocusLineEdit.h index f5ea6602e..797969406 100644 --- a/launcher/ui/widgets/FocusLineEdit.h +++ b/launcher/ui/widgets/FocusLineEdit.h @@ -1,3 +1,5 @@ +#pragma once + #include class FocusLineEdit : public QLineEdit { diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 37211693f..0fda7933e 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -148,11 +148,10 @@ ModFilterWidget::ModFilterWidget(MinecraftInstance* instance, bool extended, QWi connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - - connect(ui->neoForge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->forge, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->fabric, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); - connect(ui->quilt, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + if (extended) + connect(ui->liteLoader, &QCheckBox::stateChanged, this, &ModFilterWidget::onLoadersFilterChanged); + else + ui->liteLoader->setVisible(false); if (extended) { connect(ui->clientSide, &QCheckBox::stateChanged, this, &ModFilterWidget::onSideFilterChanged); @@ -224,6 +223,7 @@ void ModFilterWidget::prepareBasicFilter() ui->forge->setChecked(loaders & ModPlatform::Forge); ui->fabric->setChecked(loaders & ModPlatform::Fabric); ui->quilt->setChecked(loaders & ModPlatform::Quilt); + ui->liteLoader->setChecked(loaders & ModPlatform::LiteLoader); m_filter->loaders = loaders; auto def = m_instance->getPackProfile()->getComponentVersion("net.minecraft"); m_filter->versions.emplace_front(def); @@ -269,6 +269,8 @@ void ModFilterWidget::onLoadersFilterChanged() loaders |= ModPlatform::Fabric; if (ui->quilt->isChecked()) loaders |= ModPlatform::Quilt; + if (ui->liteLoader->isChecked()) + loaders |= ModPlatform::LiteLoader; m_filter_changed = loaders != m_filter->loaders; m_filter->loaders = loaders; if (m_filter_changed) diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui index 807a0019a..788202714 100644 --- a/launcher/ui/widgets/ModFilterWidget.ui +++ b/launcher/ui/widgets/ModFilterWidget.ui @@ -121,6 +121,13 @@ + + + + LiteLoader + + + diff --git a/launcher/updater/prismupdater/PrismUpdater.cpp b/launcher/updater/prismupdater/PrismUpdater.cpp index 8bf8cb473..f9ffeb658 100644 --- a/launcher/updater/prismupdater/PrismUpdater.cpp +++ b/launcher/updater/prismupdater/PrismUpdater.cpp @@ -53,24 +53,8 @@ #include #endif -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // 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() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include "DesktopServices.h" @@ -298,6 +282,10 @@ PrismUpdaterApp::PrismUpdaterApp(int& argc, char** argv) : QApplication(argc, ar auto version_parts = version.split('.'); m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; } m_allowPreRelease = parser.isSet("pre-release"); @@ -556,6 +544,7 @@ void PrismUpdaterApp::run() m_prismVersion = BuildConfig.printableVersionString(); m_prismVersionMajor = BuildConfig.VERSION_MAJOR; m_prismVersionMinor = BuildConfig.VERSION_MINOR; + m_prismVersionPatch = BuildConfig.VERSION_PATCH; m_prsimVersionChannel = BuildConfig.VERSION_CHANNEL; m_prismGitCommit = BuildConfig.GIT_COMMIT; } @@ -564,6 +553,7 @@ void PrismUpdaterApp::run() qDebug() << "Executable reports as:" << m_prismBinaryName << "version:" << m_prismVersion; qDebug() << "Version major:" << m_prismVersionMajor; qDebug() << "Version minor:" << m_prismVersionMinor; + qDebug() << "Version minor:" << m_prismVersionPatch; qDebug() << "Version channel:" << m_prsimVersionChannel; qDebug() << "Git Commit:" << m_prismGitCommit; @@ -1277,6 +1267,10 @@ bool PrismUpdaterApp::loadPrismVersionFromExe(const QString& exe_path) return false; m_prismVersionMajor = version_parts.takeFirst().toInt(); m_prismVersionMinor = version_parts.takeFirst().toInt(); + if (!version_parts.isEmpty()) + m_prismVersionPatch = version_parts.takeFirst().toInt(); + else + m_prismVersionPatch = 0; m_prismGitCommit = lines.takeFirst().simplified(); return true; } @@ -1400,7 +1394,7 @@ GitHubRelease PrismUpdaterApp::getLatestRelease() bool PrismUpdaterApp::needUpdate(const GitHubRelease& release) { - auto current_ver = Version(QString("%1.%2").arg(QString::number(m_prismVersionMajor)).arg(QString::number(m_prismVersionMinor))); + auto current_ver = Version(QString("%1.%2.%3").arg(m_prismVersionMajor).arg(m_prismVersionMinor).arg(m_prismVersionPatch)); return current_ver < release.version; } diff --git a/launcher/updater/prismupdater/PrismUpdater.h b/launcher/updater/prismupdater/PrismUpdater.h index f3dd6e062..a904cbb6f 100644 --- a/launcher/updater/prismupdater/PrismUpdater.h +++ b/launcher/updater/prismupdater/PrismUpdater.h @@ -121,6 +121,7 @@ class PrismUpdaterApp : public QApplication { QString m_prismVersion; int m_prismVersionMajor = -1; int m_prismVersionMinor = -1; + int m_prismVersionPatch = -1; QString m_prsimVersionChannel; QString m_prismGitCommit; diff --git a/libraries/LocalPeer/src/LocalPeer.cpp b/libraries/LocalPeer/src/LocalPeer.cpp index bd407042f..c1875bf98 100644 --- a/libraries/LocalPeer/src/LocalPeer.cpp +++ b/libraries/LocalPeer/src/LocalPeer.cpp @@ -76,7 +76,7 @@ ApplicationId ApplicationId::fromTraditionalApp() prefix.truncate(6); QByteArray idc = protoId.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); - auto socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); + auto socketName = QLatin1String("pl") + prefix + QLatin1Char('-') + QString::number(idNum, 16).left(12); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); @@ -98,12 +98,12 @@ ApplicationId ApplicationId::fromPathAndVersion(const QString& dataPath, const Q QCryptographicHash shasum(QCryptographicHash::Algorithm::Sha1); QString result = dataPath + QLatin1Char('-') + version; shasum.addData(result.toUtf8()); - return ApplicationId(QLatin1String("qtsingleapp-") + QString::fromLatin1(shasum.result().toHex())); + return ApplicationId(QLatin1String("pl") + QString::fromLatin1(shasum.result().toHex()).left(12)); } ApplicationId ApplicationId::fromCustomId(const QString& id) { - return ApplicationId(QLatin1String("qtsingleapp-") + id); + return ApplicationId(QLatin1String("pl") + id); } ApplicationId ApplicationId::fromRawString(const QString& id) @@ -139,7 +139,7 @@ bool LocalPeer::isClient() #if defined(Q_OS_UNIX) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath()) + QLatin1Char('/') + socketName); + QLocalServer::removeServer(socketName); res = server->listen(socketName); } #endif diff --git a/libraries/README.md b/libraries/README.md index 67d78dade..3c5f5a4ab 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,14 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## filesystem - -Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD. - -See [github repo](https://github.com/gulrak/filesystem). - -MIT licensed. - ## gamemode A performance optimization daemon. diff --git a/nix/README.md b/nix/README.md index 7c43658f9..21714d64a 100644 --- a/nix/README.md +++ b/nix/README.md @@ -44,9 +44,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; @@ -92,9 +89,6 @@ Example: # Note that this may break the reproducibility mentioned above, and you might not be able to access the binary cache # # inputs.nixpkgs.follows = "nixpkgs"; - - # This is not required for Flakes - inputs.flake-compat.follows = ""; }; }; diff --git a/nix/checks.nix b/nix/checks.nix deleted file mode 100644 index ec219d6f8..000000000 --- a/nix/checks.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - runCommand, - deadnix, - llvmPackages_18, - markdownlint-cli, - nixfmt-rfc-style, - statix, - self, -}: -{ - formatting = - runCommand "check-formatting" - { - nativeBuildInputs = [ - deadnix - llvmPackages_18.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..." - nixfmt --check . - - echo "Running statix" - statix check . - - touch $out - ''; -} diff --git a/nix/unwrapped.nix b/nix/unwrapped.nix index 7ba20b68b..93cda8e1a 100644 --- a/nix/unwrapped.nix +++ b/nix/unwrapped.nix @@ -6,12 +6,10 @@ apple-sdk_11, extra-cmake-modules, gamemode, - ghc_filesystem, jdk17, kdePackages, libnbtplusplus, ninja, - nix-filter, self, stripJavaArchivesHook, tomlplusplus, @@ -25,21 +23,41 @@ assert lib.assertMsg ( gamemodeSupport -> stdenv.hostPlatform.isLinux ) "gamemodeSupport is only available on Linux."; +let + date = + let + # YYYYMMDD + date' = lib.substring 0 8 self.lastModifiedDate; + year = lib.substring 0 4 date'; + month = lib.substring 4 2 date'; + date = lib.substring 6 2 date'; + in + if (self ? "lastModifiedDate") then + lib.concatStringsSep "-" [ + year + month + date + ] + else + "unknown"; +in + stdenv.mkDerivation { pname = "prismlauncher-unwrapped"; - version = self.shortRev or self.dirtyShortRev or "unknown"; + version = "10.0-unstable-${date}"; - src = nix-filter.lib { - root = self; - include = [ - "buildconfig" - "cmake" - "launcher" - "libraries" - "program_info" - "tests" - ../COPYING.md + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ ../CMakeLists.txt + ../COPYING.md + + ../buildconfig + ../cmake + ../launcher + ../libraries + ../program_info + ../tests ]; }; @@ -59,7 +77,6 @@ stdenv.mkDerivation { buildInputs = [ cmark - ghc_filesystem kdePackages.qtbase kdePackages.qtnetworkauth kdePackages.quazip diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 24f6ee4e8..dfdce2e1c 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -398,6 +398,7 @@ Section "@Launcher_DisplayName@" WriteRegStr HKCU Software\Classes\prismlauncher\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"' ; Write the uninstall keys for Windows + ; https://learn.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key ${GetParameters} $R0 ${GetOptions} $R0 "/NoUninstaller" $R1 ${If} ${Errors} diff --git a/tests/DataPackParse_test.cpp b/tests/DataPackParse_test.cpp index cd6ae8e8f..14f80858f 100644 --- a/tests/DataPackParse_test.cpp +++ b/tests/DataPackParse_test.cpp @@ -38,7 +38,7 @@ class DataPackParseTest : public QObject { QString zip_dp = FS::PathCombine(source, "test_data_pack_boogaloo.zip"); DataPack pack{ QFileInfo(zip_dp) }; - bool valid = DataPackUtils::processZIP(pack); + bool valid = DataPackUtils::processZIP(&pack); QVERIFY(pack.packFormat() == 4); QVERIFY(pack.description() == "Some data pack 2 boobgaloo"); @@ -52,7 +52,7 @@ class DataPackParseTest : public QObject { QString folder_dp = FS::PathCombine(source, "test_folder"); DataPack pack{ QFileInfo(folder_dp) }; - bool valid = DataPackUtils::processFolder(pack); + bool valid = DataPackUtils::processFolder(&pack); QVERIFY(pack.packFormat() == 10); QVERIFY(pack.description() == "Some data pack, maybe"); @@ -66,7 +66,7 @@ class DataPackParseTest : public QObject { QString folder_dp = FS::PathCombine(source, "another_test_folder"); DataPack pack{ QFileInfo(folder_dp) }; - bool valid = DataPackUtils::process(pack); + bool valid = DataPackUtils::process(&pack); QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "Some data pack three, leaves on the tree"); diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index ca0313bb4..995867e46 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,30 +2,15 @@ #include #include #include +#include #include #include #include -// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header - -#ifdef __APPLE__ -#include // 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() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) -#define GHC_USE_STD_FS #include namespace fs = std::filesystem; -#endif // MacOS min version check -#endif // Other OSes version check - -#ifndef GHC_USE_STD_FS -#include -namespace fs = ghc::filesystem; -#endif #include @@ -42,7 +27,7 @@ class LinkTask : public Task { ~LinkTask() { delete m_lnk; } - void matcher(const IPathMatcher* filter) { m_lnk->matcher(filter); } + void matcher(IPathMatcher::Ptr filter) { m_lnk->matcher(filter); } void linkRecursively(bool recursive) { @@ -203,8 +188,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c(); for (auto entry : target_dir.entryList()) { @@ -236,8 +221,8 @@ class FileSystemTest : public QObject { qDebug() << tempDir.path(); qDebug() << target_dir.path(); FS::copy c(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - c.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + c.matcher(re); c.whitelist(true); c(); @@ -429,8 +414,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { QVERIFY2(lnk_tsk.wasSuccessful(), "Task finished but was not successful when it should have been."); @@ -476,8 +461,8 @@ class FileSystemTest : public QObject { qDebug() << target_dir.path(); LinkTask lnk_tsk(folder, target_dir.path()); - RegexpMatcher re("[.]?mcmeta"); - lnk_tsk.matcher(&re); + RegexpMatcher::Ptr re = std::make_shared("[.]?mcmeta"); + lnk_tsk.matcher(re); lnk_tsk.linkRecursively(true); lnk_tsk.whitelist(true); QObject::connect(&lnk_tsk, &Task::finished, [&lnk_tsk] { diff --git a/tests/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp index e1092167d..17c0482fc 100644 --- a/tests/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -18,6 +18,7 @@ #include #include +#include "minecraft/mod/tasks/LocalDataPackParseTask.h" #include @@ -35,7 +36,7 @@ class ResourcePackParseTest : public QObject { QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip"); ResourcePack pack{ QFileInfo(zip_rp) }; - bool valid = ResourcePackUtils::processZIP(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::processZIP(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 3); QVERIFY(pack.description() == @@ -51,7 +52,7 @@ class ResourcePackParseTest : public QObject { QString folder_rp = FS::PathCombine(source, "test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; - bool valid = ResourcePackUtils::processFolder(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::processFolder(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 1); QVERIFY(pack.description() == "Some resource pack maybe"); @@ -65,7 +66,7 @@ class ResourcePackParseTest : public QObject { QString folder_rp = FS::PathCombine(source, "another_test_folder"); ResourcePack pack{ QFileInfo(folder_rp) }; - bool valid = ResourcePackUtils::process(pack, ResourcePackUtils::ProcessingLevel::BasicInfoOnly); + bool valid = DataPackUtils::process(&pack, DataPackUtils::ProcessingLevel::BasicInfoOnly); QVERIFY(pack.packFormat() == 6); QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional");