Compare commits
384 commits
release-9.
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6799b772de | ||
|
|
3b87452699 | ||
|
|
0af021fef2 | ||
|
|
d24066c965 | ||
|
|
46385e671d | ||
|
|
73bb740bbb | ||
|
|
50ab1b2c40 | ||
|
|
d472b966dc | ||
|
|
beee93e199 | ||
|
|
8f1750df51 | ||
|
|
bdf87fa02f | ||
|
|
3295b0717a | ||
|
|
7b97863685 | ||
|
|
3e4fc2ff3b | ||
|
|
d22a20ffa6 | ||
|
|
de86e63611 | ||
|
|
e41a60e57e | ||
|
|
174cddcf42 | ||
|
|
9e954548dd | ||
|
|
14b653e08d | ||
|
|
79180db663 | ||
|
|
22b59e760c | ||
|
|
1d1a4f1b30 | ||
|
|
bf1084d7fa | ||
|
|
ef825a7bc5 | ||
|
|
8d143c7b95 | ||
|
|
9ea33e0651 | ||
|
|
f1209c1279 | ||
|
|
17e95c0d07 | ||
|
|
cb83f8e52d | ||
|
|
7d202e348d | ||
|
|
364b5be856 | ||
|
|
e1896d5b6b | ||
|
|
6e3dd04a1f | ||
|
|
f7a401ab22 | ||
|
|
9ed6801b15 | ||
|
|
06b50dfd33 | ||
|
|
5a76d9b7e1 | ||
|
|
34438824c1 | ||
|
|
ac598740b3 | ||
|
|
85c2710e33 | ||
|
|
afe6d4836d | ||
|
|
9c26ba5932 | ||
|
|
2a987b5a4a | ||
|
|
1a375dc243 | ||
|
|
ab3792b080 | ||
|
|
f9dcc31843 | ||
|
|
989e6c6cb6 | ||
|
|
138684a84f | ||
|
|
faf7e6b06e | ||
|
|
f1671e3ded | ||
|
|
6404d81ada | ||
|
|
d6eb96df5c | ||
|
|
c5efe081b4 | ||
|
|
a639bc019e | ||
|
|
588e0d6305 | ||
|
|
d7eddedf41 | ||
|
|
6ae9fa4df4 | ||
|
|
86cc6d3c5e | ||
|
|
61d7f08883 | ||
|
|
0deb5e20ae | ||
|
|
fcb3dd5e9d | ||
|
|
169f5728b1 | ||
|
|
8816be1668 | ||
|
|
bd752c025c | ||
|
|
49d734f314 | ||
|
|
417688089d | ||
|
|
de21b77ae1 | ||
|
|
68bf409a98 | ||
|
|
5d179671bf | ||
|
|
dc6f536843 | ||
|
|
cc504f4a6c | ||
|
|
a92c68adbd | ||
|
|
7aaa9fa93c | ||
|
|
81769b9047 | ||
|
|
147b315c14 | ||
|
|
9d0918a064 | ||
|
|
98ab686348 | ||
|
|
39ae2fbd4a | ||
|
|
36a35d7925 | ||
|
|
1b4b36df33 | ||
|
|
3d9e073721 | ||
|
|
fdb4159f2a | ||
|
|
98be91c9d5 | ||
|
|
705375c0df | ||
|
|
8d3684c484 | ||
|
|
0d4eeb9965 | ||
|
|
caccaefb39 | ||
|
|
532fec7748 | ||
|
|
4ba3fd559d | ||
|
|
2a403b3bfe | ||
|
|
f8a504e254 | ||
|
|
e66f447ce5 | ||
|
|
67b218b1f3 | ||
|
|
db766574a4 | ||
|
|
18da01c9a6 | ||
|
|
c79ad73311 | ||
|
|
fb3926a85d | ||
|
|
8d4f279539 | ||
|
|
1b5d3c2bf9 | ||
|
|
be3eca8c97 | ||
|
|
66f0397087 | ||
|
|
8e8538b506 | ||
|
|
dc00c47f2e | ||
|
|
7d3238d234 | ||
|
|
f17599c8b8 | ||
|
|
8e5a7c6e33 | ||
|
|
1d8bf1d5a7 | ||
|
|
c2f4389e5f | ||
|
|
b7d117cc77 | ||
|
|
1cce582d56 | ||
|
|
867aaf881e | ||
|
|
14b348ffe7 | ||
|
|
181610a8c1 | ||
|
|
b7f2175718 | ||
|
|
7a1dc0e9c9 | ||
|
|
9317a675ed | ||
|
|
7cb8deeb90 | ||
|
|
bca517b8d3 | ||
|
|
e4ad4051c8 | ||
|
|
3ee73916ca | ||
|
|
03834e1455 | ||
|
|
54da41c625 | ||
|
|
f4f1d5f0d7 | ||
|
|
fbd886e9db | ||
|
|
0fb99d837f | ||
|
|
cb8320ca41 | ||
|
|
33981b9870 | ||
|
|
07a0a15457 | ||
|
|
0f9ea5c949 | ||
|
|
d40c8b704c | ||
|
|
82a500f4e8 | ||
|
|
6ebfcb91cf | ||
|
|
e01df73deb | ||
|
|
7dde35fef2 | ||
|
|
0b6ce5525d | ||
|
|
f17faadf8a | ||
|
|
c7b6977692 | ||
|
|
423d72a6e8 | ||
|
|
2a2ed9f8bd | ||
|
|
0412ed3f78 | ||
|
|
f4d93a08ba | ||
|
|
8d53242952 | ||
|
|
706e263050 | ||
|
|
507faf9f00 | ||
|
|
c0803365ef | ||
|
|
02e527c15b | ||
|
|
5659dad23d | ||
|
|
647bb04a0d | ||
|
|
b6cd46ad27 | ||
|
|
c3712ba648 | ||
|
|
c3e44554ab | ||
|
|
a1c1c0b769 | ||
|
|
15e58589d5 | ||
|
|
eb8c375ec5 | ||
|
|
1dd0728a58 | ||
|
|
cf2dcbd431 | ||
|
|
dedb7a2343 | ||
|
|
614574f15c | ||
|
|
28a471777a | ||
|
|
93526637cb | ||
|
|
94c893bd86 | ||
|
|
c99876f3d7 | ||
|
|
a3b2f1a931 | ||
|
|
488dc7815c | ||
|
|
dbee4bee14 | ||
|
|
2cfd116947 | ||
|
|
3f67ef968b | ||
|
|
fd9c80db62 | ||
|
|
7e76638f2c | ||
|
|
884f15da1e | ||
|
|
616aa002e1 | ||
|
|
e0faee7f26 | ||
|
|
f6770a847a | ||
|
|
4a50e94967 | ||
|
|
514ec99a03 | ||
|
|
6c062c48b4 | ||
|
|
b40a1973bf | ||
|
|
a09af619ce | ||
|
|
835944387b | ||
|
|
d927c539b5 | ||
|
|
14454faac8 | ||
|
|
2351531e84 | ||
|
|
33ff3b4f36 | ||
|
|
11e7b218d4 | ||
|
|
fbbd64ddb7 | ||
|
|
41db87c8cf | ||
|
|
79dd912126 | ||
|
|
00fa65c18b | ||
|
|
59db80cb0e | ||
|
|
e5a4dfdf16 | ||
|
|
6ca18c6266 | ||
|
|
e74592fa09 | ||
|
|
abbebff400 | ||
|
|
3c88eccf17 | ||
|
|
48235f5b71 | ||
|
|
736ecf266d | ||
|
|
b2a37452be | ||
|
|
54f2e75b9d | ||
|
|
f7620a43aa | ||
|
|
bcbe5da87f | ||
|
|
e735ea7e63 | ||
|
|
ce61ed2f86 | ||
|
|
172e43cd3f | ||
|
|
9f0b2bafd8 | ||
|
|
03b34e39f8 | ||
|
|
885fc66c14 | ||
|
|
f41380d91c | ||
|
|
66883db645 | ||
|
|
df45e5518f | ||
|
|
4c8c6e543e | ||
|
|
1d9508c268 | ||
|
|
7d03cb114d | ||
|
|
2fcf03ead3 | ||
|
|
012bbca197 | ||
|
|
8132828ad7 | ||
|
|
9601fbf2d5 | ||
|
|
6e55239cec | ||
|
|
900235642c | ||
|
|
f667f56fb7 | ||
|
|
e06b3e7a7a | ||
|
|
223b6b3b2b | ||
|
|
71e55f8829 | ||
|
|
e6d4e5d78d | ||
|
|
8ecc21c093 | ||
|
|
0a80da28fa | ||
|
|
b67d400f60 | ||
|
|
1ab10bdd84 | ||
|
|
ba6743d134 | ||
|
|
d63a336411 | ||
|
|
4a92573235 | ||
|
|
db3d796275 | ||
|
|
6f2f3c2d3b | ||
|
|
f77f0207f7 | ||
|
|
99cfb62370 | ||
|
|
2b590f24ed | ||
|
|
670c932f32 | ||
|
|
ba07a136dc | ||
|
|
9ae16921bc | ||
|
|
887e4a964a | ||
|
|
8faff9ac6a | ||
|
|
0e80aae1b8 | ||
|
|
c142d38f08 | ||
|
|
82bffabb69 | ||
|
|
14c95b9d37 | ||
|
|
b7598c6a41 | ||
|
|
ced2f9d1a0 | ||
|
|
5661444414 | ||
|
|
afa1e0599a | ||
|
|
43e4481b70 | ||
|
|
65b0fdf08d | ||
|
|
f6511c601e | ||
|
|
fd109c4740 | ||
|
|
2368521a9a | ||
|
|
d49e6187cd | ||
|
|
c1c198b84b | ||
|
|
e7ff3146fe | ||
|
|
8e7d84d05b | ||
|
|
d157159106 | ||
|
|
bd8b30c606 | ||
|
|
f56a8feade | ||
|
|
f3cae427f1 | ||
|
|
51b7abec24 | ||
|
|
d90ed89a30 | ||
|
|
ad1d5ddcdf | ||
|
|
2dee936246 | ||
|
|
a0a74612e1 | ||
|
|
fbd3aa36c4 | ||
|
|
94f65f8727 | ||
|
|
2b22a1aebe | ||
|
|
354a0ed46e | ||
|
|
9cdda4377b | ||
|
|
49044e23de | ||
|
|
f57ca1e79e | ||
|
|
0f42f3cc5c | ||
|
|
81c5866fa9 | ||
|
|
1be29076ea | ||
|
|
3c17f606ed | ||
|
|
09a118e85e | ||
|
|
c12beb43a0 | ||
|
|
08f5148a9a | ||
|
|
14ad720c58 | ||
|
|
0ee5665c95 | ||
|
|
bd82737efb | ||
|
|
f75e049675 | ||
|
|
1f8ce9eb92 | ||
|
|
d4bf61da50 | ||
|
|
33754db9b9 | ||
|
|
1809858fc7 | ||
|
|
0cafac84ac | ||
|
|
9eb1ce3ad7 | ||
|
|
6a6fe60a5b | ||
|
|
6338e2b362 | ||
|
|
b1113f4293 | ||
|
|
c4cd7cf6c1 | ||
|
|
11d4d948aa | ||
|
|
61448a61ea | ||
|
|
f373a5cea0 | ||
|
|
935b2e8fb2 | ||
|
|
b1c8ab9350 | ||
|
|
b593ff09e9 | ||
|
|
44894a29b1 | ||
|
|
685d3c79ac | ||
|
|
b66d6b2812 | ||
|
|
5b8ea89092 | ||
|
|
13e13ea8fc | ||
|
|
63b10738b2 | ||
|
|
a5c554cf6e | ||
|
|
6a2ed4a631 | ||
|
|
b7152e7010 | ||
|
|
d766de5fbf | ||
|
|
8daa1219a5 | ||
|
|
3bc40120cd | ||
|
|
4ce025c0a2 | ||
|
|
d8702e1357 | ||
|
|
9fe81ff564 | ||
|
|
5eff9b0934 | ||
|
|
8e586c883c | ||
|
|
7bd04ae928 | ||
|
|
4a7d624e9b | ||
|
|
92a906e0d4 | ||
|
|
24cecf7b63 | ||
|
|
89a327d363 | ||
|
|
e7cd8fe078 | ||
|
|
3d82bc37af | ||
|
|
f13e875f0d | ||
|
|
79439a0217 | ||
|
|
210d0d8aa2 | ||
|
|
836aebc0d9 | ||
|
|
73d33f93b3 | ||
|
|
562c301326 | ||
|
|
a43ebefc97 | ||
|
|
69028969f1 | ||
|
|
671d5270eb | ||
|
|
9a5b773e69 | ||
|
|
f3f4c44620 | ||
|
|
b049e92697 | ||
|
|
ea703ba26e | ||
|
|
78e24962f9 | ||
|
|
fcadbbb739 | ||
|
|
a8c3a49cb9 | ||
|
|
0586d38e03 | ||
|
|
0beaa94311 | ||
|
|
b39098dbc5 | ||
|
|
5b6d551650 | ||
|
|
4b66320f63 | ||
|
|
a76f37760c | ||
|
|
538815e033 | ||
|
|
3fae659b30 | ||
|
|
f641f3acda | ||
|
|
b675406b1a | ||
|
|
a58e81d744 | ||
|
|
e35faa5522 | ||
|
|
7b49fd6508 | ||
|
|
f7f7c4b43e | ||
|
|
506ec64273 | ||
|
|
0a576a0f67 | ||
|
|
40c3866f63 | ||
|
|
6b0007291b | ||
|
|
6a12c43c78 | ||
|
|
43fc9ba932 | ||
|
|
762d24bd02 | ||
|
|
a910337e9d | ||
|
|
6d017b5f0b | ||
|
|
65f8526152 | ||
|
|
99bd4a8937 | ||
|
|
cf914526bf | ||
|
|
858f6aa9b8 | ||
|
|
e6f30c0ebe | ||
|
|
014fc14291 | ||
|
|
60bd8104c4 | ||
|
|
9ce10231ed | ||
|
|
803e26a401 | ||
|
|
27780cc7ae | ||
|
|
82d0f204e2 | ||
|
|
97ee0a19b5 | ||
|
|
2c18d0f1a5 | ||
|
|
35b5c8097c | ||
|
|
f4940e26c6 | ||
|
|
179abfa03e | ||
|
|
ad16d61208 | ||
|
|
6aecbfc38f | ||
|
|
ee48766996 | ||
|
|
93876e27f8 |
20
.clang-tidy
|
|
@ -1,5 +1,23 @@
|
|||
Checks:
|
||||
- modernize-use-using
|
||||
- readability-avoid-const-params-in-decls
|
||||
- misc-unused-parameters,
|
||||
- readability-identifier-naming
|
||||
|
||||
SystemHeaders: false
|
||||
# ^ Without unused-parameters the readability-identifier-naming check doesn't cause any warnings.
|
||||
|
||||
CheckOptions:
|
||||
- { key: readability-identifier-naming.ClassCase, value: PascalCase }
|
||||
- { key: readability-identifier-naming.EnumCase, value: PascalCase }
|
||||
- { key: readability-identifier-naming.FunctionCase, value: camelCase }
|
||||
- { key: readability-identifier-naming.GlobalVariableCase, value: camelCase }
|
||||
- { key: readability-identifier-naming.GlobalFunctionCase, value: camelCase }
|
||||
- { key: readability-identifier-naming.GlobalConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||
- { key: readability-identifier-naming.MacroDefinitionCase, value: SCREAMING_SNAKE_CASE }
|
||||
- { key: readability-identifier-naming.ClassMemberCase, value: camelCase }
|
||||
- { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
|
||||
- { key: readability-identifier-naming.ProtectedMemberPrefix, value: m_ }
|
||||
- { key: readability-identifier-naming.PrivateStaticMemberPrefix, value: s_ }
|
||||
- { key: readability-identifier-naming.ProtectedStaticMemberPrefix, value: s_ }
|
||||
- { key: readability-identifier-naming.PublicStaticConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||
- { key: readability-identifier-naming.EnumConstantCase, value: SCREAMING_SNAKE_CASE }
|
||||
8
.github/workflows/build.yml
vendored
|
|
@ -164,13 +164,13 @@ jobs:
|
|||
|
||||
- name: Setup ccache
|
||||
if: (runner.os != 'Windows' || matrix.msystem == '') && inputs.build_type == 'Debug'
|
||||
uses: hendrikmuhs/ccache-action@v1.2.14
|
||||
uses: hendrikmuhs/ccache-action@v1.2.17
|
||||
with:
|
||||
key: ${{ matrix.os }}-qt${{ matrix.qt_ver }}-${{ matrix.architecture }}
|
||||
|
||||
- name: Retrieve ccache cache (Windows MinGW-w64)
|
||||
if: runner.os == 'Windows' && matrix.msystem != '' && inputs.build_type == 'Debug'
|
||||
uses: actions/cache@v4.2.0
|
||||
uses: actions/cache@v4.2.1
|
||||
with:
|
||||
path: '${{ github.workspace }}\.ccache'
|
||||
key: ${{ matrix.os }}-mingw-w64-ccache-${{ github.run_id }}
|
||||
|
|
@ -377,11 +377,13 @@ jobs:
|
|||
|
||||
if [ -n '${{ secrets.APPLE_CODESIGN_ID }}' ]; then
|
||||
APPLE_CODESIGN_ID='${{ secrets.APPLE_CODESIGN_ID }}'
|
||||
ENTITLEMENTS_FILE='../program_info/App.entitlements'
|
||||
else
|
||||
APPLE_CODESIGN_ID='-'
|
||||
ENTITLEMENTS_FILE='../program_info/AdhocSignedApp.entitlements'
|
||||
fi
|
||||
|
||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "../program_info/App.entitlements" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
sudo codesign --sign "$APPLE_CODESIGN_ID" --deep --force --entitlements "$ENTITLEMENTS_FILE" --options runtime "PrismLauncher.app/Contents/MacOS/prismlauncher"
|
||||
mv "PrismLauncher.app" "Prism Launcher.app"
|
||||
|
||||
- name: Notarize (macOS)
|
||||
|
|
|
|||
10
.github/workflows/nix.yml
vendored
|
|
@ -8,8 +8,6 @@ on:
|
|||
- ".github/ISSUE_TEMPLATE/**"
|
||||
- ".markdownlint**"
|
||||
- "flatpak/**"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request_target:
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
|
|
@ -80,9 +78,13 @@ jobs:
|
|||
- name: Build debug package
|
||||
if: ${{ env.DEBUG }}
|
||||
run: |
|
||||
nix build --print-build-logs .#prismlauncher-debug
|
||||
nix build \
|
||||
--no-link --print-build-logs --print-out-paths \
|
||||
.#prismlauncher-debug >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Build release package
|
||||
if: ${{ !env.DEBUG }}
|
||||
run: |
|
||||
nix build --print-build-logs .#prismlauncher
|
||||
nix build \
|
||||
--no-link --print-build-logs --print-out-paths \
|
||||
.#prismlauncher >> "$GITHUB_STEP_SUMMARY"
|
||||
|
|
|
|||
29
.github/workflows/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Stale
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# run weekly on sunday
|
||||
- cron: "0 0 * * 0"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Label issues and PRs
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-stale: 60
|
||||
days-before-close: -1 # Don't close anything
|
||||
exempt-issue-labels: rfc,nostale,help wanted
|
||||
exempt-all-milestones: true
|
||||
exempt-all-assignees: true
|
||||
operations-per-run: 1000
|
||||
stale-issue-label: inactive
|
||||
stale-pr-label: inactive
|
||||
|
|
@ -187,8 +187,8 @@ set(Launcher_LOGIN_CALLBACK_URL "https://prismlauncher.org/successful-login" CAC
|
|||
set(Launcher_FMLLIBS_BASE_URL "https://files.prismlauncher.org/fmllibs/" CACHE STRING "URL for FML Libraries.")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(Launcher_VERSION_MAJOR 9)
|
||||
set(Launcher_VERSION_MINOR 3)
|
||||
set(Launcher_VERSION_MAJOR 10)
|
||||
set(Launcher_VERSION_MINOR 0)
|
||||
|
||||
set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}")
|
||||
set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0")
|
||||
|
|
@ -306,6 +306,8 @@ include(QtVersionlessBackport)
|
|||
if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml NetworkAuth)
|
||||
find_package(Qt5 COMPONENTS DBus)
|
||||
list(APPEND Launcher_QT_DBUS Qt5::DBus)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
find_package(QuaZip-Qt5 1.3 QUIET)
|
||||
|
|
@ -320,6 +322,8 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5)
|
|||
elseif(Launcher_QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core CoreTools Widgets Concurrent Network Test Xml Core5Compat NetworkAuth)
|
||||
find_package(Qt6 COMPONENTS DBus)
|
||||
list(APPEND Launcher_QT_DBUS Qt6::DBus)
|
||||
list(APPEND Launcher_QT_LIBS Qt6::Core5Compat)
|
||||
|
||||
if(NOT Launcher_FORCE_BUNDLED_LIBS)
|
||||
|
|
|
|||
|
|
@ -2,16 +2,59 @@
|
|||
|
||||
## Code formatting
|
||||
|
||||
Try to follow the existing formatting.
|
||||
If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration.
|
||||
All files are formatted with `clang-format` using the configuration in `.clang-format`. Ensure it is run on changed files before committing!
|
||||
|
||||
In general, in order of importance:
|
||||
Please also follow the project's conventions for C++:
|
||||
|
||||
- Make sure your IDE is not messing up line endings or whitespace and avoid using linters.
|
||||
- Prefer readability over dogma.
|
||||
- Keep to the existing formatting.
|
||||
- Indent with 4 space unless it's in a submodule.
|
||||
- Keep lists (of arguments, parameters, initializers...) as lists, not paragraphs. It should either read from top to bottom, or left to right. Not both.
|
||||
- Class and type names should be formatted as `PascalCase`: `MyClass`.
|
||||
- Private or protected class data members should be formatted as `camelCase` prefixed with `m_`: `m_myCounter`.
|
||||
- Private or protected `static` class data members should be formatted as `camelCase` prefixed with `s_`: `s_instance`.
|
||||
- Public class data members should be formatted as `camelCase` without the prefix: `dateOfBirth`.
|
||||
- Public, private or protected `static const` class data members should be formatted as `SCREAMING_SNAKE_CASE`: `MAX_VALUE`.
|
||||
- Class function members should be formatted as `camelCase` without a prefix: `incrementCounter`.
|
||||
- Global functions and non-`const` global variables should be formatted as `camelCase` without a prefix: `globalData`.
|
||||
- `const` global variables, macros, and enum constants should be formatted as `SCREAMING_SNAKE_CASE`: `LIGHT_GRAY`.
|
||||
- Avoid inventing acronyms or abbreviations especially for a name of multiple words - like `tp` for `texturePack`.
|
||||
|
||||
Most of these rules are included in the `.clang-tidy` file, so you can run `clang-tidy` to check for any violations.
|
||||
|
||||
Here is what these conventions with the formatting configuration look like:
|
||||
|
||||
```c++
|
||||
#define AWESOMENESS 10
|
||||
|
||||
constexpr double PI = 3.14159;
|
||||
|
||||
enum class PizzaToppings { HAM_AND_PINEAPPLE, OREO_AND_KETCHUP };
|
||||
|
||||
struct Person {
|
||||
QString name;
|
||||
QDateTime dateOfBirth;
|
||||
|
||||
long daysOld() const { return dateOfBirth.daysTo(QDateTime::currentDateTime()); }
|
||||
};
|
||||
|
||||
class ImportantClass {
|
||||
public:
|
||||
void incrementCounter()
|
||||
{
|
||||
if (m_counter + 1 > MAX_COUNTER_VALUE)
|
||||
throw std::runtime_error("Counter has reached limit!");
|
||||
|
||||
++m_counter;
|
||||
}
|
||||
|
||||
int counter() const { return m_counter; }
|
||||
|
||||
private:
|
||||
static constexpr int MAX_COUNTER_VALUE = 100;
|
||||
int m_counter;
|
||||
};
|
||||
|
||||
ImportantClass importantClassInstance;
|
||||
```
|
||||
|
||||
If you see any names which do not follow these conventions, it is preferred that you leave them be - renames increase the number of changes therefore make reviewing harder and make your PR more prone to conflicts. However, if you're refactoring a whole class anyway, it's fine.
|
||||
|
||||
## Signing your work
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
<p align="center"><b><i>This is a fork of PrismLauncher, unlocked to be able to use offline accounts without owning the game.</b></i></p>
|
||||
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="/program_info/org.prismlauncher.PrismLauncher.logo-darkmode.svg">
|
||||
|
|
|
|||
18
flake.lock
generated
|
|
@ -3,11 +3,11 @@
|
|||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"lastModified": 1733328505,
|
||||
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -34,11 +34,11 @@
|
|||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1710156097,
|
||||
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
|
||||
"lastModified": 1731533336,
|
||||
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
|
||||
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -49,11 +49,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1729256560,
|
||||
"narHash": "sha256-/uilDXvCIEs3C9l73JTACm4quuHUsIHcns1c+cHUJwA=",
|
||||
"lastModified": 1739446958,
|
||||
"narHash": "sha256-+/bYK3DbPxMIvSL4zArkMX0LQvS7rzBKXnDXLfKyRVc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4c2fcb090b1f3e5b47eaa7bd33913b574a11e0a0",
|
||||
"rev": "2ff53fe64443980e139eaa286017f53f88336dd0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@
|
|||
#include "ui/pages/BasePageProvider.h"
|
||||
#include "ui/pages/global/APIPage.h"
|
||||
#include "ui/pages/global/AccountListPage.h"
|
||||
#include "ui/pages/global/CustomCommandsPage.h"
|
||||
#include "ui/pages/global/EnvironmentVariablesPage.h"
|
||||
#include "ui/pages/global/ExternalToolsPage.h"
|
||||
#include "ui/pages/global/JavaPage.h"
|
||||
#include "ui/pages/global/LanguagePage.h"
|
||||
|
|
@ -243,6 +241,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
|
||||
{ { "w", "world" }, "Join the specified world on launch (only valid in combination with --launch)", "world" },
|
||||
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
|
||||
{ { "o", "offline" }, "Launch offline, with given player name (only valid in combination with --launch)", "offline" },
|
||||
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
|
||||
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
|
||||
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
|
||||
|
|
@ -258,6 +257,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_serverToJoin = parser.value("server");
|
||||
m_worldToJoin = parser.value("world");
|
||||
m_profileToUse = parser.value("profile");
|
||||
if (parser.isSet("offline")) {
|
||||
m_offline = true;
|
||||
m_offlineName = parser.value("offline");
|
||||
}
|
||||
m_liveCheck = parser.isSet("alive");
|
||||
|
||||
m_instanceIdToShowWindowOf = parser.value("show");
|
||||
|
|
@ -272,8 +275,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
}
|
||||
|
||||
// error if --launch is missing with --server or --profile
|
||||
if (((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty()) || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) {
|
||||
std::cerr << "--server and --profile can only be used in combination with --launch!" << std::endl;
|
||||
if ((!m_serverToJoin.isEmpty() || !m_worldToJoin.isEmpty() || !m_profileToUse.isEmpty() || m_offline) &&
|
||||
m_instanceIdToLaunch.isEmpty()) {
|
||||
std::cerr << "--server, --profile and --offline can only be used in combination with --launch!" << std::endl;
|
||||
m_status = Application::Failed;
|
||||
return;
|
||||
}
|
||||
|
|
@ -398,6 +402,10 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
if (!m_profileToUse.isEmpty()) {
|
||||
launch.args["profile"] = m_profileToUse;
|
||||
}
|
||||
if (m_offline) {
|
||||
launch.args["offline_enabled"] = "true";
|
||||
launch.args["offline_name"] = m_offlineName;
|
||||
}
|
||||
m_peerInstance->sendMessage(launch.serialize(), timeout);
|
||||
}
|
||||
m_status = Application::Succeeded;
|
||||
|
|
@ -606,6 +614,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_settings->registerSetting("IconsDir", "icons");
|
||||
m_settings->registerSetting("DownloadsDir", QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
||||
m_settings->registerSetting("DownloadsDirWatchRecursive", false);
|
||||
m_settings->registerSetting("MoveModsFromDownloadsDir", false);
|
||||
m_settings->registerSetting("SkinsDir", "skins");
|
||||
m_settings->registerSetting("JavaDir", "java");
|
||||
|
||||
|
|
@ -791,8 +800,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
m_globalSettingsProvider->addPage<MinecraftPage>();
|
||||
m_globalSettingsProvider->addPage<JavaPage>();
|
||||
m_globalSettingsProvider->addPage<LanguagePage>();
|
||||
m_globalSettingsProvider->addPage<CustomCommandsPage>();
|
||||
m_globalSettingsProvider->addPage<EnvironmentVariablesPage>();
|
||||
m_globalSettingsProvider->addPage<ProxyPage>();
|
||||
m_globalSettingsProvider->addPage<ExternalToolsPage>();
|
||||
m_globalSettingsProvider->addPage<AccountListPage>();
|
||||
|
|
@ -836,7 +843,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
":/icons/multimc/128x128/instances/", ":/icons/multimc/scalable/instances/" };
|
||||
m_icons.reset(new IconList(instFolders, setting->get().toString()));
|
||||
connect(setting.get(), &Setting::SettingChanged,
|
||||
[&](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
||||
[this](const Setting&, QVariant value) { m_icons->directoryChanged(value.toString()); });
|
||||
qDebug() << "<> Instance icons initialized.";
|
||||
}
|
||||
|
||||
|
|
@ -1071,11 +1078,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
|
|||
|
||||
bool Application::createSetupWizard()
|
||||
{
|
||||
bool javaRequired = [&]() {
|
||||
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_settings->get("AutomaticJavaDownload").toBool()) {
|
||||
bool javaRequired = [this]() {
|
||||
if (BuildConfig.JAVA_DOWNLOADER_ENABLED && settings()->get("AutomaticJavaDownload").toBool()) {
|
||||
return false;
|
||||
}
|
||||
bool ignoreJavaWizard = m_settings->get("IgnoreJavaWizard").toBool();
|
||||
bool ignoreJavaWizard = settings()->get("IgnoreJavaWizard").toBool();
|
||||
if (ignoreJavaWizard) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1089,8 +1096,8 @@ bool Application::createSetupWizard()
|
|||
QString actualPath = FS::ResolveExecutable(currentJavaPath);
|
||||
return actualPath.isNull();
|
||||
}();
|
||||
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() &&
|
||||
!m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
||||
bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !settings()->get("AutomaticJavaDownload").toBool() &&
|
||||
!settings()->get("AutomaticJavaSwitch").toBool() && !settings()->get("UserAskedAboutAutomaticJavaDownload").toBool();
|
||||
bool languageRequired = settings()->get("Language").toString().isEmpty();
|
||||
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
|
||||
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
|
||||
|
|
@ -1210,7 +1217,7 @@ void Application::performMainStartupAction()
|
|||
qDebug() << " Launching with account" << m_profileToUse;
|
||||
}
|
||||
|
||||
launch(inst, true, false, targetToJoin, accountToUse);
|
||||
launch(inst, !m_offline, false, targetToJoin, accountToUse, m_offlineName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -1309,6 +1316,8 @@ void Application::messageReceived(const QByteArray& message)
|
|||
QString server = received.args["server"];
|
||||
QString world = received.args["world"];
|
||||
QString profile = received.args["profile"];
|
||||
bool offline = received.args["offline_enabled"] == "true";
|
||||
QString offlineName = received.args["offline_name"];
|
||||
|
||||
InstancePtr instance;
|
||||
if (!id.isEmpty()) {
|
||||
|
|
@ -1338,7 +1347,7 @@ void Application::messageReceived(const QByteArray& message)
|
|||
}
|
||||
}
|
||||
|
||||
launch(instance, true, false, serverObject, accountObject);
|
||||
launch(instance, !offline, false, serverObject, accountObject, offlineName);
|
||||
} else {
|
||||
qWarning() << "Received invalid message" << message;
|
||||
}
|
||||
|
|
@ -1376,7 +1385,12 @@ bool Application::openJsonEditor(const QString& filename)
|
|||
}
|
||||
}
|
||||
|
||||
bool Application::launch(InstancePtr instance, bool online, bool demo, MinecraftTarget::Ptr targetToJoin, MinecraftAccountPtr accountToUse)
|
||||
bool Application::launch(InstancePtr instance,
|
||||
bool online,
|
||||
bool demo,
|
||||
MinecraftTarget::Ptr targetToJoin,
|
||||
MinecraftAccountPtr accountToUse,
|
||||
const QString& offlineName)
|
||||
{
|
||||
if (m_updateRunning) {
|
||||
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
|
||||
|
|
@ -1397,6 +1411,7 @@ bool Application::launch(InstancePtr instance, bool online, bool demo, Minecraft
|
|||
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
|
||||
controller->setTargetToJoin(targetToJoin);
|
||||
controller->setAccountToUse(accountToUse);
|
||||
controller->setOfflineName(offlineName);
|
||||
if (window) {
|
||||
controller->setParentWidget(window);
|
||||
} else if (m_mainWindow) {
|
||||
|
|
|
|||
|
|
@ -211,7 +211,8 @@ class Application : public QApplication {
|
|||
bool online = true,
|
||||
bool demo = false,
|
||||
MinecraftTarget::Ptr targetToJoin = nullptr,
|
||||
MinecraftAccountPtr accountToUse = nullptr);
|
||||
MinecraftAccountPtr accountToUse = nullptr,
|
||||
const QString& offlineName = QString());
|
||||
bool kill(InstancePtr instance);
|
||||
void closeCurrentWindow();
|
||||
|
||||
|
|
@ -301,6 +302,8 @@ class Application : public QApplication {
|
|||
QString m_serverToJoin;
|
||||
QString m_worldToJoin;
|
||||
QString m_profileToUse;
|
||||
bool m_offline = false;
|
||||
QString m_offlineName;
|
||||
bool m_liveCheck = false;
|
||||
QList<QUrl> m_urlsToImport;
|
||||
QString m_instanceIdToShowWindowOf;
|
||||
|
|
|
|||
|
|
@ -411,3 +411,8 @@ void BaseInstance::updateRuntimeContext()
|
|||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
bool BaseInstance::isLegacy()
|
||||
{
|
||||
return traits().contains("legacyLaunch") || traits().contains("alphaLaunch");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,6 +269,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
|
|||
bool removeLinkedInstanceId(const QString& id);
|
||||
bool isLinkedToInstanceId(const QString& id) const;
|
||||
|
||||
bool isLegacy();
|
||||
|
||||
protected:
|
||||
void changeStatus(Status newStatus);
|
||||
|
||||
|
|
|
|||
|
|
@ -345,13 +345,12 @@ set(MINECRAFT_SOURCES
|
|||
minecraft/mod/TexturePackFolderModel.h
|
||||
minecraft/mod/TexturePackFolderModel.cpp
|
||||
minecraft/mod/ShaderPackFolderModel.h
|
||||
minecraft/mod/tasks/BasicFolderLoadTask.h
|
||||
minecraft/mod/tasks/ModFolderLoadTask.h
|
||||
minecraft/mod/tasks/ModFolderLoadTask.cpp
|
||||
minecraft/mod/tasks/ResourceFolderLoadTask.h
|
||||
minecraft/mod/tasks/ResourceFolderLoadTask.cpp
|
||||
minecraft/mod/tasks/LocalModParseTask.h
|
||||
minecraft/mod/tasks/LocalModParseTask.cpp
|
||||
minecraft/mod/tasks/LocalModUpdateTask.h
|
||||
minecraft/mod/tasks/LocalModUpdateTask.cpp
|
||||
minecraft/mod/tasks/LocalResourceUpdateTask.h
|
||||
minecraft/mod/tasks/LocalResourceUpdateTask.cpp
|
||||
minecraft/mod/tasks/LocalDataPackParseTask.h
|
||||
minecraft/mod/tasks/LocalDataPackParseTask.cpp
|
||||
minecraft/mod/tasks/LocalResourcePackParseTask.h
|
||||
|
|
@ -909,7 +908,6 @@ SET(LAUNCHER_SOURCES
|
|||
ui/pages/instance/NotesPage.h
|
||||
ui/pages/instance/LogPage.cpp
|
||||
ui/pages/instance/LogPage.h
|
||||
ui/pages/instance/InstanceSettingsPage.cpp
|
||||
ui/pages/instance/InstanceSettingsPage.h
|
||||
ui/pages/instance/ScreenshotsPage.cpp
|
||||
ui/pages/instance/ScreenshotsPage.h
|
||||
|
|
@ -923,17 +921,12 @@ SET(LAUNCHER_SOURCES
|
|||
# GUI - global settings pages
|
||||
ui/pages/global/AccountListPage.cpp
|
||||
ui/pages/global/AccountListPage.h
|
||||
ui/pages/global/CustomCommandsPage.cpp
|
||||
ui/pages/global/CustomCommandsPage.h
|
||||
ui/pages/global/EnvironmentVariablesPage.cpp
|
||||
ui/pages/global/EnvironmentVariablesPage.h
|
||||
ui/pages/global/ExternalToolsPage.cpp
|
||||
ui/pages/global/ExternalToolsPage.h
|
||||
ui/pages/global/JavaPage.cpp
|
||||
ui/pages/global/JavaPage.h
|
||||
ui/pages/global/LanguagePage.cpp
|
||||
ui/pages/global/LanguagePage.h
|
||||
ui/pages/global/MinecraftPage.cpp
|
||||
ui/pages/global/MinecraftPage.h
|
||||
ui/pages/global/LauncherPage.cpp
|
||||
ui/pages/global/LauncherPage.h
|
||||
|
|
@ -1067,8 +1060,8 @@ SET(LAUNCHER_SOURCES
|
|||
ui/dialogs/BlockedModsDialog.h
|
||||
ui/dialogs/ChooseProviderDialog.h
|
||||
ui/dialogs/ChooseProviderDialog.cpp
|
||||
ui/dialogs/ModUpdateDialog.cpp
|
||||
ui/dialogs/ModUpdateDialog.h
|
||||
ui/dialogs/ResourceUpdateDialog.cpp
|
||||
ui/dialogs/ResourceUpdateDialog.h
|
||||
ui/dialogs/InstallLoaderDialog.cpp
|
||||
ui/dialogs/InstallLoaderDialog.h
|
||||
|
||||
|
|
@ -1084,14 +1077,12 @@ SET(LAUNCHER_SOURCES
|
|||
ui/widgets/CustomCommands.h
|
||||
ui/widgets/EnvironmentVariables.cpp
|
||||
ui/widgets/EnvironmentVariables.h
|
||||
ui/widgets/DropLabel.cpp
|
||||
ui/widgets/DropLabel.h
|
||||
ui/widgets/FocusLineEdit.cpp
|
||||
ui/widgets/FocusLineEdit.h
|
||||
ui/widgets/IconLabel.cpp
|
||||
ui/widgets/IconLabel.h
|
||||
ui/widgets/JavaSettingsWidget.cpp
|
||||
ui/widgets/JavaSettingsWidget.h
|
||||
ui/widgets/JavaWizardWidget.cpp
|
||||
ui/widgets/JavaWizardWidget.h
|
||||
ui/widgets/LabeledToolButton.cpp
|
||||
ui/widgets/LabeledToolButton.h
|
||||
ui/widgets/LanguageSelectionWidget.cpp
|
||||
|
|
@ -1127,6 +1118,10 @@ SET(LAUNCHER_SOURCES
|
|||
ui/widgets/WideBar.cpp
|
||||
ui/widgets/ThemeCustomizationWidget.h
|
||||
ui/widgets/ThemeCustomizationWidget.cpp
|
||||
ui/widgets/MinecraftSettingsWidget.h
|
||||
ui/widgets/MinecraftSettingsWidget.cpp
|
||||
ui/widgets/JavaSettingsWidget.h
|
||||
ui/widgets/JavaSettingsWidget.cpp
|
||||
|
||||
# GUI - instance group view
|
||||
ui/instanceview/InstanceProxyModel.cpp
|
||||
|
|
@ -1170,7 +1165,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/pages/global/LauncherPage.ui
|
||||
ui/pages/global/APIPage.ui
|
||||
ui/pages/global/ProxyPage.ui
|
||||
ui/pages/global/MinecraftPage.ui
|
||||
ui/pages/global/ExternalToolsPage.ui
|
||||
ui/pages/instance/ExternalResourcesPage.ui
|
||||
ui/pages/instance/NotesPage.ui
|
||||
|
|
@ -1178,7 +1172,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/pages/instance/ServersPage.ui
|
||||
ui/pages/instance/GameOptionsPage.ui
|
||||
ui/pages/instance/OtherLogsPage.ui
|
||||
ui/pages/instance/InstanceSettingsPage.ui
|
||||
ui/pages/instance/VersionPage.ui
|
||||
ui/pages/instance/ManagedPackPage.ui
|
||||
ui/pages/instance/WorldListPage.ui
|
||||
|
|
@ -1201,6 +1194,8 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/widgets/ModFilterWidget.ui
|
||||
ui/widgets/SubTaskProgressBar.ui
|
||||
ui/widgets/ThemeCustomizationWidget.ui
|
||||
ui/widgets/MinecraftSettingsWidget.ui
|
||||
ui/widgets/JavaSettingsWidget.ui
|
||||
ui/dialogs/CopyInstanceDialog.ui
|
||||
ui/dialogs/ProfileSetupDialog.ui
|
||||
ui/dialogs/ProgressDialog.ui
|
||||
|
|
@ -1220,7 +1215,6 @@ qt_wrap_ui(LAUNCHER_UI
|
|||
ui/dialogs/ScrollMessageBox.ui
|
||||
ui/dialogs/BlockedModsDialog.ui
|
||||
ui/dialogs/ChooseProviderDialog.ui
|
||||
|
||||
ui/dialogs/skins/SkinManageDialog.ui
|
||||
)
|
||||
|
||||
|
|
@ -1295,6 +1289,7 @@ target_link_libraries(Launcher_logic
|
|||
Qt${QT_VERSION_MAJOR}::Gui
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::NetworkAuth
|
||||
${Launcher_QT_DBUS}
|
||||
${Launcher_QT_LIBS}
|
||||
)
|
||||
target_link_libraries(Launcher_logic
|
||||
|
|
@ -1303,6 +1298,10 @@ target_link_libraries(Launcher_logic
|
|||
LocalPeer
|
||||
Launcher_rainbow
|
||||
)
|
||||
if (TARGET ${Launcher_QT_DBUS})
|
||||
add_compile_definitions(WITH_QTDBUS)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_MACOSX_RPATH 1)
|
||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks/")
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ void DataMigrationTask::executeTask()
|
|||
|
||||
// 1. Scan
|
||||
// Check how many files we gotta copy
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||
return m_copy(true); // dry run to collect amount of files
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
|
||||
|
|
@ -57,7 +57,7 @@ void DataMigrationTask::dryRunFinished()
|
|||
setProgress(m_copy.totalCopied(), m_toCopy);
|
||||
setStatus(tr("Copying %1…").arg(shortenedName));
|
||||
});
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
|
||||
return m_copy(false); // actually copy now
|
||||
});
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ bool copy::operator()(const QString& offset, bool dryRun)
|
|||
opt |= copy_opts::overwrite_existing;
|
||||
|
||||
// Function that'll do the actual copying
|
||||
auto copy_file = [&](QString src_path, QString relative_dst_path) {
|
||||
auto copy_file = [this, dryRun, src, dst, opt, &err](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||
return;
|
||||
|
||||
|
|
@ -428,7 +428,7 @@ void create_link::make_link_list(const QString& offset)
|
|||
m_recursive = true;
|
||||
|
||||
// Function that'll do the actual linking
|
||||
auto link_file = [&](QString src_path, QString relative_dst_path) {
|
||||
auto link_file = [this, dst](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist)) {
|
||||
qDebug() << "path" << relative_dst_path << "in black list or not in whitelist";
|
||||
return;
|
||||
|
|
@ -523,7 +523,7 @@ void create_link::runPrivileged(const QString& offset)
|
|||
|
||||
QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric();
|
||||
|
||||
connect(&m_linkServer, &QLocalServer::newConnection, this, [&]() {
|
||||
connect(&m_linkServer, &QLocalServer::newConnection, this, [this, &gotResults]() {
|
||||
qDebug() << "Client connected, sending out pairs";
|
||||
// construct block of data to send
|
||||
QByteArray block;
|
||||
|
|
@ -605,7 +605,7 @@ void create_link::runPrivileged(const QString& offset)
|
|||
}
|
||||
|
||||
ExternalLinkFileProcess* linkFileProcess = new ExternalLinkFileProcess(serverName, m_useHardLinks, this);
|
||||
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&]() { emit finishedPrivileged(gotResults); });
|
||||
connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [this, gotResults]() { emit finishedPrivileged(gotResults); });
|
||||
connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
|
||||
|
||||
linkFileProcess->start();
|
||||
|
|
@ -1295,7 +1295,7 @@ bool clone::operator()(const QString& offset, bool dryRun)
|
|||
std::error_code err;
|
||||
|
||||
// Function that'll do the actual cloneing
|
||||
auto cloneFile = [&](QString src_path, QString relative_dst_path) {
|
||||
auto cloneFile = [this, dryRun, dst, &err](QString src_path, QString relative_dst_path) {
|
||||
if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ void InstanceCopyTask::executeTask()
|
|||
QEventLoop loop;
|
||||
bool got_priv_results = false;
|
||||
|
||||
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&](bool gotResults) {
|
||||
connect(&folderLink, &FS::create_link::finishedPrivileged, this, [&got_priv_results, &loop](bool gotResults) {
|
||||
if (!gotResults) {
|
||||
qDebug() << "Privileged run exited without results!";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,7 +487,7 @@ InstanceList::InstListError InstanceList::loadList()
|
|||
int front_bookmark = -1;
|
||||
int back_bookmark = -1;
|
||||
int currentItem = -1;
|
||||
auto removeNow = [&]() {
|
||||
auto removeNow = [this, &front_bookmark, &back_bookmark, ¤tItem]() {
|
||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||
endRemoveRows();
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class InstancePageProvider : protected QObject, public BasePageProvider {
|
|||
values.append(new ServersPage(onesix));
|
||||
// values.append(new GameOptionsPage(onesix.get()));
|
||||
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
|
||||
values.append(new InstanceSettingsPage(onesix.get()));
|
||||
values.append(new InstanceSettingsPage(onesix));
|
||||
auto logMatcher = inst->getLogFileMatcher();
|
||||
if (logMatcher) {
|
||||
values.append(new OtherLogsPage(inst->getLogFileRoot(), logMatcher));
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "ui/InstanceWindow.h"
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
#include "ui/dialogs/MSALoginDialog.h"
|
||||
#include "ui/dialogs/ProfileSelectDialog.h"
|
||||
#include "ui/dialogs/ProfileSetupDialog.h"
|
||||
#include "ui/dialogs/ProgressDialog.h"
|
||||
|
|
@ -234,10 +235,15 @@ void LaunchController::login()
|
|||
if (!m_session->wants_online) {
|
||||
// we ask the user for a player name
|
||||
bool ok = false;
|
||||
auto name = askOfflineName(m_session->player_name, m_session->demo, ok);
|
||||
if (!ok) {
|
||||
tryagain = false;
|
||||
break;
|
||||
QString name;
|
||||
if (m_offlineName.isEmpty()) {
|
||||
name = askOfflineName(m_session->player_name, m_session->demo, ok);
|
||||
if (!ok) {
|
||||
tryagain = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
name = m_offlineName;
|
||||
}
|
||||
m_session->MakeOffline(name);
|
||||
// offline flavored game from here :3
|
||||
|
|
@ -287,10 +293,8 @@ void LaunchController::login()
|
|||
continue;
|
||||
}
|
||||
case AccountState::Expired: {
|
||||
auto errorString = tr("The account has expired and needs to be logged into manually again.");
|
||||
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"), errorString, QMessageBox::StandardButton::Ok,
|
||||
QMessageBox::StandardButton::Ok);
|
||||
emitFailed(errorString);
|
||||
if (reauthenticateCurrentAccount())
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
case AccountState::Disabled: {
|
||||
|
|
@ -314,6 +318,33 @@ void LaunchController::login()
|
|||
emitFailed(tr("Failed to launch."));
|
||||
}
|
||||
|
||||
bool LaunchController::reauthenticateCurrentAccount()
|
||||
{
|
||||
auto button =
|
||||
QMessageBox::warning(m_parentWidget, tr("Account refresh failed"),
|
||||
tr("The account has expired and needs to be reauthenticated. Do you want to reauthenticate this account?"),
|
||||
QMessageBox::StandardButton::Yes | QMessageBox::StandardButton::No, QMessageBox::StandardButton::Yes);
|
||||
if (button == QMessageBox::StandardButton::Yes) {
|
||||
auto accounts = APPLICATION->accounts();
|
||||
bool isDefault = accounts->defaultAccount() == m_accountToUse;
|
||||
accounts->removeAccount(accounts->index(accounts->findAccountByProfileId(m_accountToUse->profileId())));
|
||||
if (m_accountToUse->accountType() == AccountType::MSA) {
|
||||
auto newAccount = MSALoginDialog::newAccount(m_parentWidget);
|
||||
accounts->addAccount(newAccount);
|
||||
if (isDefault) {
|
||||
accounts->setDefaultAccount(newAccount);
|
||||
}
|
||||
m_accountToUse = nullptr;
|
||||
decideAccount();
|
||||
return true;
|
||||
}
|
||||
emitFailed(tr("Account expired and re-login attempt failed"));
|
||||
} else {
|
||||
emitFailed(tr("The account has expired and needs to be reauthenticated"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LaunchController::launchInstance()
|
||||
{
|
||||
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ class LaunchController : public Task {
|
|||
|
||||
void setOnline(bool online) { m_online = online; }
|
||||
|
||||
void setOfflineName(const QString& offlineName) { m_offlineName = offlineName; }
|
||||
|
||||
void setDemo(bool demo) { m_demo = demo; }
|
||||
|
||||
void setProfiler(BaseProfilerFactory* profiler) { m_profiler = profiler; }
|
||||
|
|
@ -76,6 +78,7 @@ class LaunchController : public Task {
|
|||
void decideAccount();
|
||||
bool askPlayDemo();
|
||||
QString askOfflineName(QString playerName, bool demo, bool& ok);
|
||||
bool reauthenticateCurrentAccount();
|
||||
|
||||
private slots:
|
||||
void readyForLaunch();
|
||||
|
|
@ -87,6 +90,7 @@ class LaunchController : public Task {
|
|||
private:
|
||||
BaseProfilerFactory* m_profiler = nullptr;
|
||||
bool m_online = true;
|
||||
QString m_offlineName;
|
||||
bool m_demo = false;
|
||||
InstancePtr m_instance;
|
||||
QWidget* m_parentWidget = nullptr;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class shared_qobject_ptr : public QSharedPointer<T> {
|
|||
{}
|
||||
|
||||
void reset() { QSharedPointer<T>::reset(); }
|
||||
void reset(T*&& other)
|
||||
void reset(T* other)
|
||||
{
|
||||
shared_qobject_ptr<T> t(other);
|
||||
this->swap(t);
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
|
|||
QString custom_target_folder)
|
||||
: m_pack(std::move(pack)), m_pack_version(std::move(version)), m_pack_model(packs), m_custom_target_folder(custom_target_folder)
|
||||
{
|
||||
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model && is_indexed) {
|
||||
m_update_task.reset(new LocalModUpdateTask(model->indexDir(), *m_pack, m_pack_version));
|
||||
connect(m_update_task.get(), &LocalModUpdateTask::hasOldMod, this, &ResourceDownloadTask::hasOldResource);
|
||||
if (is_indexed) {
|
||||
m_update_task.reset(new LocalResourceUpdateTask(m_pack_model->indexDir(), *m_pack, m_pack_version));
|
||||
connect(m_update_task.get(), &LocalResourceUpdateTask::hasOldResource, this, &ResourceDownloadTask::hasOldResource);
|
||||
|
||||
addTask(m_update_task);
|
||||
}
|
||||
|
|
@ -91,12 +91,8 @@ void ResourceDownloadTask::downloadSucceeded()
|
|||
m_filesNetJob.reset();
|
||||
auto name = std::get<0>(to_delete);
|
||||
auto filename = std::get<1>(to_delete);
|
||||
if (!name.isEmpty() && filename != m_pack_version.fileName) {
|
||||
if (auto model = dynamic_cast<ModFolderModel*>(m_pack_model.get()); model)
|
||||
model->uninstallMod(filename, true);
|
||||
else
|
||||
m_pack_model->uninstallResource(filename);
|
||||
}
|
||||
if (!name.isEmpty() && filename != m_pack_version.fileName)
|
||||
m_pack_model->uninstallResource(filename, true);
|
||||
}
|
||||
|
||||
void ResourceDownloadTask::downloadFailed(QString reason)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
#include "net/NetJob.h"
|
||||
#include "tasks/SequentialTask.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class ResourceFolderModel;
|
||||
|
|
@ -50,7 +50,7 @@ class ResourceDownloadTask : public SequentialTask {
|
|||
QString m_custom_target_folder;
|
||||
|
||||
NetJob::Ptr m_filesNetJob;
|
||||
LocalModUpdateTask::Ptr m_update_task;
|
||||
LocalResourceUpdateTask::Ptr m_update_task;
|
||||
|
||||
void downloadProgressChanged(qint64 current, qint64 total);
|
||||
void downloadFailed(QString reason);
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ void Version::parse()
|
|||
if (m_string.isEmpty())
|
||||
return;
|
||||
|
||||
auto classChange = [&](QChar lastChar, QChar currentChar) {
|
||||
auto classChange = [¤tSection](QChar lastChar, QChar currentChar) {
|
||||
if (lastChar.isNull())
|
||||
return false;
|
||||
if (lastChar.isDigit() != currentChar.isDigit())
|
||||
|
|
|
|||
|
|
@ -104,11 +104,11 @@ void FileLinkApp::joinServer(QString server)
|
|||
|
||||
in.setDevice(&socket);
|
||||
|
||||
connect(&socket, &QLocalSocket::connected, this, [&]() { qDebug() << "connected to server"; });
|
||||
connect(&socket, &QLocalSocket::connected, this, []() { qDebug() << "connected to server"; });
|
||||
|
||||
connect(&socket, &QLocalSocket::readyRead, this, &FileLinkApp::readPathPairs);
|
||||
|
||||
connect(&socket, &QLocalSocket::errorOccurred, this, [&](QLocalSocket::LocalSocketError socketError) {
|
||||
connect(&socket, &QLocalSocket::errorOccurred, this, [this](QLocalSocket::LocalSocketError socketError) {
|
||||
m_status = Failed;
|
||||
switch (socketError) {
|
||||
case QLocalSocket::ServerNotFoundError:
|
||||
|
|
@ -132,7 +132,7 @@ void FileLinkApp::joinServer(QString server)
|
|||
}
|
||||
});
|
||||
|
||||
connect(&socket, &QLocalSocket::disconnected, this, [&]() {
|
||||
connect(&socket, &QLocalSocket::disconnected, this, [this]() {
|
||||
qDebug() << "disconnected from server, should exit";
|
||||
m_status = Succeeded;
|
||||
exit();
|
||||
|
|
|
|||
|
|
@ -47,24 +47,24 @@
|
|||
|
||||
#define MAX_SIZE 1024
|
||||
|
||||
IconList::IconList(const QStringList& builtinPaths, QString path, QObject* parent) : QAbstractListModel(parent)
|
||||
IconList::IconList(const QStringList& builtinPaths, const QString& path, QObject* parent) : QAbstractListModel(parent)
|
||||
{
|
||||
QSet<QString> builtinNames;
|
||||
|
||||
// add builtin icons
|
||||
for (auto& builtinPath : builtinPaths) {
|
||||
QDir instance_icons(builtinPath);
|
||||
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
|
||||
for (auto file_info : file_info_list) {
|
||||
builtinNames.insert(file_info.completeBaseName());
|
||||
for (const auto& builtinPath : builtinPaths) {
|
||||
QDir instanceIcons(builtinPath);
|
||||
auto fileInfoList = instanceIcons.entryInfoList(QDir::Files, QDir::Name);
|
||||
for (const auto& fileInfo : fileInfoList) {
|
||||
builtinNames.insert(fileInfo.baseName());
|
||||
}
|
||||
}
|
||||
for (auto& builtinName : builtinNames) {
|
||||
for (const auto& builtinName : builtinNames) {
|
||||
addThemeIcon(builtinName);
|
||||
}
|
||||
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
is_watching = false;
|
||||
m_isWatching = false;
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::directoryChanged, this, &IconList::directoryChanged);
|
||||
connect(m_watcher.get(), &QFileSystemWatcher::fileChanged, this, &IconList::fileChanged);
|
||||
|
||||
|
|
@ -77,91 +77,131 @@ IconList::IconList(const QStringList& builtinPaths, QString path, QObject* paren
|
|||
void IconList::sortIconList()
|
||||
{
|
||||
qDebug() << "Sorting icon list...";
|
||||
std::sort(icons.begin(), icons.end(), [](const MMCIcon& a, const MMCIcon& b) { return a.m_key.localeAwareCompare(b.m_key) < 0; });
|
||||
std::sort(m_icons.begin(), m_icons.end(), [](const MMCIcon& a, const MMCIcon& b) {
|
||||
bool aIsSubdir = a.m_key.contains(QDir::separator());
|
||||
bool bIsSubdir = b.m_key.contains(QDir::separator());
|
||||
if (aIsSubdir != bIsSubdir) {
|
||||
return !aIsSubdir; // root-level icons come first
|
||||
}
|
||||
return a.m_key.localeAwareCompare(b.m_key) < 0;
|
||||
});
|
||||
reindex();
|
||||
}
|
||||
|
||||
// Helper function to add directories recursively
|
||||
bool IconList::addPathRecursively(const QString& path)
|
||||
{
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
return false;
|
||||
|
||||
// Add the directory itself
|
||||
bool watching = m_watcher->addPath(path);
|
||||
|
||||
// Add all subdirectories
|
||||
QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
for (const QFileInfo& entry : entries) {
|
||||
if (addPathRecursively(entry.absoluteFilePath())) {
|
||||
watching = true;
|
||||
}
|
||||
}
|
||||
return watching;
|
||||
}
|
||||
|
||||
QStringList IconList::getIconFilePaths() const
|
||||
{
|
||||
QStringList iconFiles{};
|
||||
QStringList directories{ m_dir.absolutePath() };
|
||||
while (!directories.isEmpty()) {
|
||||
QString first = directories.takeFirst();
|
||||
QDir dir(first);
|
||||
for (QFileInfo& fileInfo : dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (fileInfo.isDir())
|
||||
directories.push_back(fileInfo.absoluteFilePath());
|
||||
else
|
||||
iconFiles.push_back(fileInfo.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
return iconFiles;
|
||||
}
|
||||
|
||||
QString formatName(const QDir& iconsDir, const QFileInfo& iconFile)
|
||||
{
|
||||
if (iconFile.dir() == iconsDir)
|
||||
return iconFile.baseName();
|
||||
|
||||
constexpr auto delimiter = " » ";
|
||||
QString relativePathWithoutExtension = iconsDir.relativeFilePath(iconFile.dir().path()) + QDir::separator() + iconFile.baseName();
|
||||
return relativePathWithoutExtension.replace(QDir::separator(), delimiter);
|
||||
}
|
||||
|
||||
/// Split into a separate function because the preprocessing impedes readability
|
||||
QSet<QString> toStringSet(const QList<QString>& list)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QSet<QString> set(list.begin(), list.end());
|
||||
#else
|
||||
QSet<QString> set = list.toSet();
|
||||
#endif
|
||||
return set;
|
||||
}
|
||||
|
||||
void IconList::directoryChanged(const QString& path)
|
||||
{
|
||||
QDir new_dir(path);
|
||||
if (m_dir.absolutePath() != new_dir.absolutePath()) {
|
||||
m_dir.setPath(path);
|
||||
QDir newDir(path);
|
||||
if (m_dir.absolutePath() != newDir.absolutePath()) {
|
||||
if (!path.startsWith(m_dir.absolutePath()))
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if (is_watching)
|
||||
if (m_isWatching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
if (!m_dir.exists())
|
||||
if (!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
return;
|
||||
if (!m_dir.exists() && !FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
return;
|
||||
m_dir.refresh();
|
||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
||||
for (auto it = new_list.begin(); it != new_list.end(); it++) {
|
||||
QString& foo = (*it);
|
||||
foo = m_dir.filePath(foo);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QSet<QString> new_set(new_list.begin(), new_list.end());
|
||||
#else
|
||||
auto new_set = new_list.toSet();
|
||||
#endif
|
||||
QList<QString> current_list;
|
||||
for (auto& it : icons) {
|
||||
const QStringList newFileNamesList = getIconFilePaths();
|
||||
const QSet<QString> newSet = toStringSet(newFileNamesList);
|
||||
QSet<QString> currentSet;
|
||||
for (const MMCIcon& it : m_icons) {
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||
currentSet.insert(it.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QSet<QString> current_set(current_list.begin(), current_list.end());
|
||||
#else
|
||||
QSet<QString> current_set = current_list.toSet();
|
||||
#endif
|
||||
QSet<QString> toRemove = currentSet - newSet;
|
||||
QSet<QString> toAdd = newSet - currentSet;
|
||||
|
||||
QSet<QString> to_remove = current_set;
|
||||
to_remove -= new_set;
|
||||
|
||||
QSet<QString> to_add = new_set;
|
||||
to_add -= current_set;
|
||||
|
||||
for (auto remove : to_remove) {
|
||||
qDebug() << "Removing " << remove;
|
||||
QFileInfo rmfile(remove);
|
||||
QString key = rmfile.completeBaseName();
|
||||
|
||||
QString suffix = rmfile.suffix();
|
||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||
if (!IconUtils::isIconSuffix(suffix))
|
||||
key = rmfile.fileName();
|
||||
for (const QString& removedPath : toRemove) {
|
||||
qDebug() << "Removing icon " << removedPath;
|
||||
QFileInfo removedFile(removedPath);
|
||||
QString key = m_dir.relativeFilePath(removedFile.absoluteFilePath());
|
||||
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(IconType::FileBased);
|
||||
if (icons[idx].type() == IconType::ToBeDeleted) {
|
||||
m_icons[idx].remove(FileBased);
|
||||
if (m_icons[idx].type() == ToBeDeleted) {
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
m_icons.remove(idx);
|
||||
reindex();
|
||||
endRemoveRows();
|
||||
} else {
|
||||
dataChanged(index(idx), index(idx));
|
||||
}
|
||||
m_watcher->removePath(remove);
|
||||
m_watcher->removePath(removedPath);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
for (auto add : to_add) {
|
||||
qDebug() << "Adding " << add;
|
||||
for (const QString& addedPath : toAdd) {
|
||||
qDebug() << "Adding icon " << addedPath;
|
||||
|
||||
QFileInfo addfile(add);
|
||||
QString key = addfile.completeBaseName();
|
||||
QFileInfo addfile(addedPath);
|
||||
QString relativePath = m_dir.relativeFilePath(addfile.absoluteFilePath());
|
||||
QString key = QFileInfo(relativePath).completeBaseName();
|
||||
QString name = formatName(m_dir, addfile);
|
||||
|
||||
QString suffix = addfile.suffix();
|
||||
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
|
||||
if (!IconUtils::isIconSuffix(suffix))
|
||||
key = addfile.fileName();
|
||||
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
|
||||
m_watcher->addPath(add);
|
||||
if (addIcon(key, name, addfile.filePath(), IconType::FileBased)) {
|
||||
m_watcher->addPath(addedPath);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,24 +211,24 @@ void IconList::directoryChanged(const QString& path)
|
|||
|
||||
void IconList::fileChanged(const QString& path)
|
||||
{
|
||||
qDebug() << "Checking " << path;
|
||||
qDebug() << "Checking icon " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
QString key = checkfile.completeBaseName();
|
||||
QString key = m_dir.relativeFilePath(checkfile.absoluteFilePath());
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
return;
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
if (icon.availableSizes().empty())
|
||||
return;
|
||||
|
||||
icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||
m_icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||
dataChanged(index(idx), index(idx));
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
void IconList::SettingChanged(const Setting& setting, QVariant value)
|
||||
void IconList::SettingChanged(const Setting& setting, const QVariant& value)
|
||||
{
|
||||
if (setting.id() != "IconsDir")
|
||||
return;
|
||||
|
|
@ -200,8 +240,8 @@ void IconList::startWatching()
|
|||
{
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching) {
|
||||
m_isWatching = addPathRecursively(abs_path);
|
||||
if (m_isWatching) {
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
} else {
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
|
|
@ -212,7 +252,7 @@ void IconList::stopWatching()
|
|||
{
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
is_watching = false;
|
||||
m_isWatching = false;
|
||||
}
|
||||
|
||||
QStringList IconList::mimeTypes() const
|
||||
|
|
@ -242,7 +282,7 @@ bool IconList::dropMimeData(const QMimeData* data,
|
|||
if (data->hasUrls()) {
|
||||
auto urls = data->urls();
|
||||
QStringList iconFiles;
|
||||
for (auto url : urls) {
|
||||
for (const auto& url : urls) {
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
|
|
@ -263,33 +303,33 @@ Qt::ItemFlags IconList::flags(const QModelIndex& index) const
|
|||
QVariant IconList::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
return {};
|
||||
|
||||
int row = index.row();
|
||||
|
||||
if (row < 0 || row >= icons.size())
|
||||
return QVariant();
|
||||
if (row < 0 || row >= m_icons.size())
|
||||
return {};
|
||||
|
||||
switch (role) {
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
return m_icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
return m_icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
return m_icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int IconList::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : icons.size();
|
||||
return parent.isValid() ? 0 : m_icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList& iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
for (const QString& file : iconFiles)
|
||||
installIcon(file, {});
|
||||
}
|
||||
|
||||
|
|
@ -312,12 +352,13 @@ bool IconList::iconFileExists(const QString& key) const
|
|||
return iconEntry && iconEntry->has(IconType::FileBased);
|
||||
}
|
||||
|
||||
/// Returns the icon with the given key or nullptr if it doesn't exist.
|
||||
const MMCIcon* IconList::icon(const QString& key) const
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return nullptr;
|
||||
return &icons[iconIdx];
|
||||
return &m_icons[iconIdx];
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(const QString& key)
|
||||
|
|
@ -332,22 +373,22 @@ bool IconList::trashIcon(const QString& key)
|
|||
|
||||
bool IconList::addThemeIcon(const QString& key)
|
||||
{
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end()) {
|
||||
auto& oldOne = icons[*iter];
|
||||
auto iter = m_nameIndex.find(key);
|
||||
if (iter != m_nameIndex.end()) {
|
||||
auto& oldOne = m_icons[*iter];
|
||||
oldOne.replace(Builtin, key);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
|
||||
{
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = key;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(Builtin, key);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
m_icons.push_back(mmc_icon);
|
||||
m_nameIndex[key] = m_icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
|
|
@ -359,22 +400,22 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
|
|||
QIcon icon(path);
|
||||
if (icon.isNull())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end()) {
|
||||
auto& oldOne = icons[*iter];
|
||||
auto iter = m_nameIndex.find(key);
|
||||
if (iter != m_nameIndex.end()) {
|
||||
auto& oldOne = m_icons[*iter];
|
||||
oldOne.replace(type, icon, path);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), icons.size());
|
||||
beginInsertRows(QModelIndex(), m_icons.size(), m_icons.size());
|
||||
{
|
||||
MMCIcon mmc_icon;
|
||||
mmc_icon.m_name = name;
|
||||
mmc_icon.m_key = key;
|
||||
mmc_icon.replace(type, icon, path);
|
||||
icons.push_back(mmc_icon);
|
||||
name_index[key] = icons.size() - 1;
|
||||
m_icons.push_back(mmc_icon);
|
||||
m_nameIndex[key] = m_icons.size() - 1;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
|
|
@ -389,33 +430,32 @@ void IconList::saveIcon(const QString& key, const QString& path, const char* for
|
|||
|
||||
void IconList::reindex()
|
||||
{
|
||||
name_index.clear();
|
||||
int i = 0;
|
||||
for (auto& iter : icons) {
|
||||
name_index[iter.m_key] = i;
|
||||
i++;
|
||||
m_nameIndex.clear();
|
||||
for (int i = 0; i < m_icons.size(); i++) {
|
||||
m_nameIndex[m_icons[i].m_key] = i;
|
||||
emit iconUpdated(m_icons[i].m_key); // prevents incorrect indices with proxy model
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconList::getIcon(const QString& key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
int iconIndex = getIconIndex(key);
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
if (iconIndex != -1)
|
||||
return m_icons[iconIndex].icon();
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex("grass");
|
||||
// Fallback for icons that don't exist.b
|
||||
iconIndex = getIconIndex("grass");
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
return QIcon();
|
||||
if (iconIndex != -1)
|
||||
return m_icons[iconIndex].icon();
|
||||
return {};
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(const QString& key) const
|
||||
{
|
||||
auto iter = name_index.find(key == "default" ? "grass" : key);
|
||||
if (iter != name_index.end())
|
||||
auto iter = m_nameIndex.find(key == "default" ? "grass" : key);
|
||||
if (iter != m_nameIndex.end())
|
||||
return *iter;
|
||||
|
||||
return -1;
|
||||
|
|
@ -425,3 +465,15 @@ QString IconList::getDirectory() const
|
|||
{
|
||||
return m_dir.absolutePath();
|
||||
}
|
||||
|
||||
/// Returns the directory of the icon with the given key or the default directory if it's a builtin icon.
|
||||
QString IconList::iconDirectory(const QString& key) const
|
||||
{
|
||||
for (const auto& mmcIcon : m_icons) {
|
||||
if (mmcIcon.m_key == key && mmcIcon.has(IconType::FileBased)) {
|
||||
QFileInfo iconFile(mmcIcon.getFilePath());
|
||||
return iconFile.dir().path();
|
||||
}
|
||||
}
|
||||
return getDirectory();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class QFileSystemWatcher;
|
|||
class IconList : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconList(const QStringList& builtinPaths, QString path, QObject* parent = 0);
|
||||
explicit IconList(const QStringList& builtinPaths, const QString& path, QObject* parent = 0);
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(const QString& key) const;
|
||||
|
|
@ -72,6 +72,7 @@ class IconList : public QAbstractListModel {
|
|||
bool deleteIcon(const QString& key);
|
||||
bool trashIcon(const QString& key);
|
||||
bool iconFileExists(const QString& key) const;
|
||||
QString iconDirectory(const QString& key) const;
|
||||
|
||||
void installIcons(const QStringList& iconFiles);
|
||||
void installIcon(const QString& file, const QString& name);
|
||||
|
|
@ -91,18 +92,20 @@ class IconList : public QAbstractListModel {
|
|||
IconList& operator=(const IconList&) = delete;
|
||||
void reindex();
|
||||
void sortIconList();
|
||||
bool addPathRecursively(const QString& path);
|
||||
QStringList getIconFilePaths() const;
|
||||
|
||||
public slots:
|
||||
void directoryChanged(const QString& path);
|
||||
|
||||
protected slots:
|
||||
void fileChanged(const QString& path);
|
||||
void SettingChanged(const Setting& setting, QVariant value);
|
||||
void SettingChanged(const Setting& setting, const QVariant& value);
|
||||
|
||||
private:
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
bool m_isWatching;
|
||||
QMap<QString, int> m_nameIndex;
|
||||
QVector<MMCIcon> m_icons;
|
||||
QDir m_dir;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
{
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
auto scanJavaDir = [&](
|
||||
auto scanJavaDir = [&javas](
|
||||
const QString& dirPath,
|
||||
const std::function<bool(const QFileInfo&)>& filter = [](const QFileInfo&) { return true; }) {
|
||||
QDir dir(dirPath);
|
||||
|
|
@ -424,7 +424,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
};
|
||||
// java installed in a snap is installed in the standard directory, but underneath $SNAP
|
||||
auto snap = qEnvironmentVariable("SNAP");
|
||||
auto scanJavaDirs = [&](const QString& dirPath) {
|
||||
auto scanJavaDirs = [scanJavaDir, snap](const QString& dirPath) {
|
||||
scanJavaDir(dirPath);
|
||||
if (!snap.isNull()) {
|
||||
scanJavaDir(snap + dirPath);
|
||||
|
|
@ -442,9 +442,15 @@ QList<QString> JavaUtils::FindJavaPaths()
|
|||
QString fileName = info.fileName();
|
||||
return fileName.startsWith("openjdk-") || fileName.startsWith("openj9-");
|
||||
};
|
||||
// AOSC OS's locations for openjdk
|
||||
auto aoscFilter = [](const QFileInfo& info) {
|
||||
QString fileName = info.fileName();
|
||||
return fileName == "java" || fileName.startsWith("java-");
|
||||
};
|
||||
scanJavaDir("/usr/lib64", gentooFilter);
|
||||
scanJavaDir("/usr/lib", gentooFilter);
|
||||
scanJavaDir("/opt", gentooFilter);
|
||||
scanJavaDir("/usr/lib", aoscFilter);
|
||||
// javas stored in Prism Launcher's folder
|
||||
scanJavaDirs("java");
|
||||
// manually installed JDKs in /opt
|
||||
|
|
@ -546,12 +552,12 @@ QStringList getPrismJavaBundle()
|
|||
{
|
||||
QList<QString> javas;
|
||||
|
||||
auto scanDir = [&](QString prefix) {
|
||||
auto scanDir = [&javas](QString prefix) {
|
||||
javas.append(FS::PathCombine(prefix, "jre", "bin", JavaUtils::javaExecutable));
|
||||
javas.append(FS::PathCombine(prefix, "bin", JavaUtils::javaExecutable));
|
||||
javas.append(FS::PathCombine(prefix, JavaUtils::javaExecutable));
|
||||
};
|
||||
auto scanJavaDir = [&](const QString& dirPath) {
|
||||
auto scanJavaDir = [scanDir](const QString& dirPath) {
|
||||
QDir dir(dirPath);
|
||||
if (!dir.exists())
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ void PostLaunchCommand::executeTask()
|
|||
|
||||
void PostLaunchCommand::on_state(LoggedProcess::State state)
|
||||
{
|
||||
auto getError = [&]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||
auto getError = [this]() { return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||
switch (state) {
|
||||
case LoggedProcess::Aborted:
|
||||
case LoggedProcess::Crashed:
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ void PreLaunchCommand::executeTask()
|
|||
|
||||
void PreLaunchCommand::on_state(LoggedProcess::State state)
|
||||
{
|
||||
auto getError = [&]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||
auto getError = [this]() { return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode()); };
|
||||
switch (state) {
|
||||
case LoggedProcess::Aborted:
|
||||
case LoggedProcess::Crashed:
|
||||
|
|
|
|||
|
|
@ -158,8 +158,8 @@ Version::Ptr VersionList::getVersion(const QString& version)
|
|||
|
||||
bool VersionList::hasVersion(QString version) const
|
||||
{
|
||||
auto ver =
|
||||
std::find_if(m_versions.constBegin(), m_versions.constEnd(), [&](Meta::Version::Ptr const& a) { return a->version() == version; });
|
||||
auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(),
|
||||
[version](Meta::Version::Ptr const& a) { return a->version() == version; });
|
||||
return (ver != m_versions.constEnd());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
|
|||
{
|
||||
bool local = isLocal();
|
||||
// Lambda function to get the absolute file path
|
||||
auto actualPath = [&](QString relPath) {
|
||||
auto actualPath = [this, local, overridePath](QString relPath) {
|
||||
relPath = FS::RemoveInvalidPathChars(relPath);
|
||||
QFileInfo out(FS::PathCombine(storagePrefix(), relPath));
|
||||
if (local && !overridePath.isEmpty()) {
|
||||
|
|
@ -115,7 +115,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
|||
bool local = isLocal();
|
||||
|
||||
// Lambda function to check if a local file exists
|
||||
auto check_local_file = [&](QString storage) {
|
||||
auto check_local_file = [overridePath, &failedLocalFiles](QString storage) {
|
||||
QFileInfo fileinfo(storage);
|
||||
QString fileName = fileinfo.fileName();
|
||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||
|
|
@ -128,7 +128,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
|||
};
|
||||
|
||||
// Lambda function to add a download request
|
||||
auto add_download = [&](QString storage, QString url, QString sha1) {
|
||||
auto add_download = [this, local, check_local_file, cache, stale, &out](QString storage, QString url, QString sha1) {
|
||||
if (local) {
|
||||
return check_local_file(storage);
|
||||
}
|
||||
|
|
@ -198,7 +198,7 @@ QList<Net::NetRequest::Ptr> Library::getDownloads(const RuntimeContext& runtimeC
|
|||
}
|
||||
}
|
||||
} else {
|
||||
auto raw_dl = [&]() {
|
||||
auto raw_dl = [this, raw_storage]() {
|
||||
if (!m_absoluteURL.isEmpty()) {
|
||||
return m_absoluteURL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,13 +91,56 @@
|
|||
#include "tools/BaseProfiler.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QMainWindow>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "MangoHud.h"
|
||||
#endif
|
||||
|
||||
#ifdef WITH_QTDBUS
|
||||
#include <QtDBus/QtDBus>
|
||||
#endif
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
static bool switcherooSetupGPU(QProcessEnvironment& env)
|
||||
{
|
||||
#ifdef WITH_QTDBUS
|
||||
if (!QDBusConnection::systemBus().isConnected())
|
||||
return false;
|
||||
|
||||
QDBusInterface switcheroo("net.hadess.SwitcherooControl", "/net/hadess/SwitcherooControl", "org.freedesktop.DBus.Properties",
|
||||
QDBusConnection::systemBus());
|
||||
|
||||
if (!switcheroo.isValid())
|
||||
return false;
|
||||
|
||||
QDBusReply<QDBusVariant> reply =
|
||||
switcheroo.call(QStringLiteral("Get"), QStringLiteral("net.hadess.SwitcherooControl"), QStringLiteral("GPUs"));
|
||||
if (!reply.isValid())
|
||||
return false;
|
||||
|
||||
QDBusArgument arg = qvariant_cast<QDBusArgument>(reply.value().variant());
|
||||
QList<QVariantMap> gpus;
|
||||
arg >> gpus;
|
||||
|
||||
for (const auto& gpu : gpus) {
|
||||
QString name = qvariant_cast<QString>(gpu[QStringLiteral("Name")]);
|
||||
bool defaultGpu = qvariant_cast<bool>(gpu[QStringLiteral("Default")]);
|
||||
if (!defaultGpu) {
|
||||
QStringList envList = qvariant_cast<QStringList>(gpu[QStringLiteral("Environment")]);
|
||||
for (int i = 0; i + 1 < envList.size(); i += 2) {
|
||||
env.insert(envList[i], envList[i + 1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// all of this because keeping things compatible with deprecated old settings
|
||||
// if either of the settings {a, b} is true, this also resolves to true
|
||||
class OrSetting : public Setting {
|
||||
|
|
@ -530,7 +573,7 @@ QStringList MinecraftInstance::javaArguments()
|
|||
QString MinecraftInstance::getLauncher()
|
||||
{
|
||||
// use legacy launcher if the traits are set
|
||||
if (traits().contains("legacyLaunch") || traits().contains("alphaLaunch"))
|
||||
if (isLegacy())
|
||||
return "legacy";
|
||||
|
||||
return "standard";
|
||||
|
|
@ -551,6 +594,13 @@ QMap<QString, QString> MinecraftInstance::getVariables()
|
|||
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
||||
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
||||
out.insert("NO_COLOR", "1");
|
||||
#ifdef Q_OS_MACOS
|
||||
// get library for Steam overlay support
|
||||
QString steamDyldInsertLibraries = qEnvironmentVariable("STEAM_DYLD_INSERT_LIBRARIES");
|
||||
if (!steamDyldInsertLibraries.isEmpty()) {
|
||||
out.insert("DYLD_INSERT_LIBRARIES", steamDyldInsertLibraries);
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -615,12 +665,14 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment()
|
|||
}
|
||||
|
||||
if (settings()->get("UseDiscreteGpu").toBool()) {
|
||||
// Open Source Drivers
|
||||
env.insert("DRI_PRIME", "1");
|
||||
// Proprietary Nvidia Drivers
|
||||
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||
if (!switcherooSetupGPU(env)) {
|
||||
// Open Source Drivers
|
||||
env.insert("DRI_PRIME", "1");
|
||||
// Proprietary Nvidia Drivers
|
||||
env.insert("__NV_PRIME_RENDER_OFFLOAD", "1");
|
||||
env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only");
|
||||
env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings()->get("UseZink").toBool()) {
|
||||
|
|
@ -753,11 +805,34 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftT
|
|||
// window size, title and state, legacy
|
||||
{
|
||||
QString windowParams;
|
||||
if (settings()->get("LaunchMaximized").toBool())
|
||||
windowParams = "maximized";
|
||||
else
|
||||
if (settings()->get("LaunchMaximized").toBool()) {
|
||||
// FIXME doesn't support maximisation
|
||||
if (!isLegacy()) {
|
||||
auto screen = QGuiApplication::primaryScreen();
|
||||
auto screenGeometry = screen->availableSize();
|
||||
|
||||
// small hack to get the widow decorations
|
||||
for (auto w : QApplication::topLevelWidgets()) {
|
||||
auto mainWindow = qobject_cast<QMainWindow*>(w);
|
||||
if (mainWindow) {
|
||||
auto m = mainWindow->windowHandle()->frameMargins();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
screenGeometry = screenGeometry.shrunkBy(m);
|
||||
#else
|
||||
screenGeometry = { screenGeometry.width() - m.left() - m.right(), screenGeometry.height() - m.top() - m.bottom() };
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
windowParams = QString("%1x%2").arg(screenGeometry.width()).arg(screenGeometry.height());
|
||||
} else {
|
||||
windowParams = "maximized";
|
||||
}
|
||||
} else {
|
||||
windowParams =
|
||||
QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
|
||||
}
|
||||
launchScript += "windowTitle " + windowTitle() + "\n";
|
||||
launchScript += "windowParams " + windowParams + "\n";
|
||||
}
|
||||
|
|
@ -829,7 +904,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||
out << "Libraries:";
|
||||
QStringList jars, nativeJars;
|
||||
profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
|
||||
auto printLibFile = [&](const QString& path) {
|
||||
auto printLibFile = [&out](const QString& path) {
|
||||
QFileInfo info(path);
|
||||
if (info.exists()) {
|
||||
out << " " + path;
|
||||
|
|
@ -849,7 +924,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
|
|||
}
|
||||
|
||||
// mods and core mods
|
||||
auto printModList = [&](const QString& label, ModFolderModel& model) {
|
||||
auto printModList = [&out](const QString& label, ModFolderModel& model) {
|
||||
if (model.size()) {
|
||||
out << QString("%1:").arg(label);
|
||||
auto modList = model.allMods();
|
||||
|
|
@ -1178,7 +1253,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList()
|
|||
{
|
||||
if (!m_loader_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed));
|
||||
m_loader_mod_list.reset(new ModFolderModel(modsRoot(), this, is_indexed, true));
|
||||
}
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
|
@ -1187,7 +1262,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList()
|
|||
{
|
||||
if (!m_core_mod_list) {
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed));
|
||||
m_core_mod_list.reset(new ModFolderModel(coreModsDir(), this, is_indexed, true));
|
||||
}
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
|
@ -1204,7 +1279,8 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::nilModList()
|
|||
std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
||||
{
|
||||
if (!m_resource_pack_list) {
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this));
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir(), this, is_indexed, true));
|
||||
}
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
|
@ -1212,7 +1288,8 @@ std::shared_ptr<ResourcePackFolderModel> MinecraftInstance::resourcePackList()
|
|||
std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
||||
{
|
||||
if (!m_texture_pack_list) {
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this));
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir(), this, is_indexed, true));
|
||||
}
|
||||
return m_texture_pack_list;
|
||||
}
|
||||
|
|
@ -1220,11 +1297,17 @@ std::shared_ptr<TexturePackFolderModel> MinecraftInstance::texturePackList()
|
|||
std::shared_ptr<ShaderPackFolderModel> MinecraftInstance::shaderPackList()
|
||||
{
|
||||
if (!m_shader_pack_list) {
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this));
|
||||
bool is_indexed = !APPLICATION->settings()->get("ModMetadataDisabled").toBool();
|
||||
m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir(), this, is_indexed, true));
|
||||
}
|
||||
return m_shader_pack_list;
|
||||
}
|
||||
|
||||
QList<std::shared_ptr<ResourceFolderModel>> MinecraftInstance::resourceLists()
|
||||
{
|
||||
return { loaderModList(), coreModList(), nilModList(), resourcePackList(), texturePackList(), shaderPackList() };
|
||||
}
|
||||
|
||||
std::shared_ptr<WorldList> MinecraftInstance::worldList()
|
||||
{
|
||||
if (!m_world_list) {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ class MinecraftInstance : public BaseInstance {
|
|||
std::shared_ptr<ResourcePackFolderModel> resourcePackList();
|
||||
std::shared_ptr<TexturePackFolderModel> texturePackList();
|
||||
std::shared_ptr<ShaderPackFolderModel> shaderPackList();
|
||||
QList<std::shared_ptr<ResourceFolderModel>> resourceLists();
|
||||
std::shared_ptr<WorldList> worldList();
|
||||
std::shared_ptr<GameOptions> gameOptionsModel();
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument& doc
|
|||
}
|
||||
}
|
||||
|
||||
auto readLibs = [&](const char* which, QList<LibraryPtr>& outList) {
|
||||
auto readLibs = [&root, &out, &filename](const char* which, QList<LibraryPtr>& outList) {
|
||||
for (auto libVal : requireArray(root.value(which))) {
|
||||
QJsonObject libObj = requireObject(libVal);
|
||||
// parse the library
|
||||
|
|
|
|||
|
|
@ -752,7 +752,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch)
|
|||
}
|
||||
|
||||
// FIXME: we need a generic way of removing local resources, not just jar mods...
|
||||
auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool {
|
||||
auto preRemoveJarMod = [this](LibraryPtr jarMod) -> bool {
|
||||
if (!jarMod->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class MinecraftAccount : public QObject, public Usable {
|
|||
|
||||
[[nodiscard]] AccountType accountType() const noexcept { return data.type; }
|
||||
|
||||
bool ownsMinecraft() const { return data.type != AccountType::Offline && data.minecraftEntitlement.ownsMinecraft; }
|
||||
bool ownsMinecraft() const { return true; }
|
||||
|
||||
bool hasProfile() const { return data.profileId().size() != 0; }
|
||||
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent)
|
|||
{
|
||||
if (parent->instance()->settings()->get("CloseAfterLaunch").toBool()) {
|
||||
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
|
||||
*connection =
|
||||
connect(&m_process, &LoggedProcess::log, this, [=](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
|
||||
*connection = connect(
|
||||
&m_process, &LoggedProcess::log, this, [connection](const QStringList& lines, [[maybe_unused]] MessageLevel::Enum level) {
|
||||
qDebug() << lines;
|
||||
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
|
||||
APPLICATION->closeAllWindows();
|
||||
|
|
|
|||
|
|
@ -26,33 +26,48 @@
|
|||
// launcher/minecraft/mod/Mod.h
|
||||
class Mod;
|
||||
|
||||
/* Abstraction file for easily changing the way metadata is stored / handled
|
||||
* Needs to be a class because of -Wunused-function and no C++17 [[maybe_unused]]
|
||||
* */
|
||||
class Metadata {
|
||||
public:
|
||||
using ModStruct = Packwiz::V1::Mod;
|
||||
using ModSide = Packwiz::V1::Side;
|
||||
namespace Metadata {
|
||||
using ModStruct = Packwiz::V1::Mod;
|
||||
using ModSide = Packwiz::V1::Side;
|
||||
|
||||
static auto create(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
|
||||
}
|
||||
inline auto create(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::createModFormat(index_dir, mod_pack, mod_version);
|
||||
}
|
||||
|
||||
static auto create(QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::createModFormat(index_dir, internal_mod, mod_slug);
|
||||
}
|
||||
inline auto create(const QDir& index_dir, Mod& internal_mod, QString mod_slug) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::createModFormat(index_dir, internal_mod, std::move(mod_slug));
|
||||
}
|
||||
|
||||
static void update(QDir& index_dir, ModStruct& mod) { Packwiz::V1::updateModIndex(index_dir, mod); }
|
||||
inline void update(const QDir& index_dir, ModStruct& mod)
|
||||
{
|
||||
Packwiz::V1::updateModIndex(index_dir, mod);
|
||||
}
|
||||
|
||||
static void remove(QDir& index_dir, QString mod_slug) { Packwiz::V1::deleteModIndex(index_dir, mod_slug); }
|
||||
inline void remove(const QDir& index_dir, QString mod_slug)
|
||||
{
|
||||
Packwiz::V1::deleteModIndex(index_dir, mod_slug);
|
||||
}
|
||||
|
||||
static void remove(QDir& index_dir, QVariant& mod_id) { Packwiz::V1::deleteModIndex(index_dir, mod_id); }
|
||||
inline void remove(const QDir& index_dir, QVariant& mod_id)
|
||||
{
|
||||
Packwiz::V1::deleteModIndex(index_dir, mod_id);
|
||||
}
|
||||
|
||||
static auto get(QDir& index_dir, QString mod_slug) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_slug); }
|
||||
inline auto get(const QDir& index_dir, QString mod_slug) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::getIndexForMod(index_dir, std::move(mod_slug));
|
||||
}
|
||||
|
||||
static auto get(QDir& index_dir, QVariant& mod_id) -> ModStruct { return Packwiz::V1::getIndexForMod(index_dir, mod_id); }
|
||||
inline auto get(const QDir& index_dir, QVariant& mod_id) -> ModStruct
|
||||
{
|
||||
return Packwiz::V1::getIndexForMod(index_dir, mod_id);
|
||||
}
|
||||
|
||||
static auto modSideToString(ModSide side) -> QString { return Packwiz::V1::sideToString(side); }
|
||||
};
|
||||
inline auto modSideToString(ModSide side) -> QString
|
||||
{
|
||||
return Packwiz::V1::sideToString(side);
|
||||
}
|
||||
|
||||
}; // namespace Metadata
|
||||
|
|
|
|||
|
|
@ -38,16 +38,15 @@
|
|||
#include "Mod.h"
|
||||
#include <qpixmap.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#include "MTPixmapCache.h"
|
||||
#include "MetadataHandler.h"
|
||||
#include "Resource.h"
|
||||
#include "Version.h"
|
||||
#include "minecraft/mod/ModDetails.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
|
|
@ -56,24 +55,6 @@ Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details()
|
|||
m_enabled = (file.suffix() != "disabled");
|
||||
}
|
||||
|
||||
Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename))
|
||||
{
|
||||
m_name = metadata.name;
|
||||
m_local_details.metadata = std::make_shared<Metadata::ModStruct>(std::move(metadata));
|
||||
}
|
||||
|
||||
void Mod::setStatus(ModStatus status)
|
||||
{
|
||||
m_local_details.status = status;
|
||||
}
|
||||
void Mod::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
|
||||
{
|
||||
if (status() == ModStatus::NoMetadata)
|
||||
setStatus(ModStatus::Installed);
|
||||
|
||||
m_local_details.metadata = metadata;
|
||||
}
|
||||
|
||||
void Mod::setDetails(const ModDetails& details)
|
||||
{
|
||||
m_local_details = details;
|
||||
|
|
@ -101,33 +82,28 @@ int Mod::compare(const Resource& other, SortType type) const
|
|||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
return QString::compare(provider().value_or("Unknown"), cast_other->provider().value_or("Unknown"), Qt::CaseInsensitive);
|
||||
}
|
||||
case SortType::SIDE: {
|
||||
if (side() > cast_other->side())
|
||||
return 1;
|
||||
else if (side() < cast_other->side())
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::LOADERS: {
|
||||
if (loaders() > cast_other->loaders())
|
||||
return 1;
|
||||
else if (loaders() < cast_other->loaders())
|
||||
return -1;
|
||||
auto compare_result = QString::compare(side(), cast_other->side(), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
case SortType::MC_VERSIONS: {
|
||||
auto thisVersion = mcVersions().join(",");
|
||||
auto otherVersion = cast_other->mcVersions().join(",");
|
||||
return QString::compare(thisVersion, otherVersion, Qt::CaseInsensitive);
|
||||
auto compare_result = QString::compare(mcVersions(), cast_other->mcVersions(), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
case SortType::LOADERS: {
|
||||
auto compare_result = QString::compare(loaders(), cast_other->loaders(), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
case SortType::RELEASE_TYPE: {
|
||||
if (releaseType() > cast_other->releaseType())
|
||||
return 1;
|
||||
else if (releaseType() < cast_other->releaseType())
|
||||
return -1;
|
||||
auto compare_result = QString::compare(releaseType(), cast_other->releaseType(), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -148,28 +124,6 @@ bool Mod::applyFilter(QRegularExpression filter) const
|
|||
return Resource::applyFilter(filter);
|
||||
}
|
||||
|
||||
auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
||||
{
|
||||
if (!preserve_metadata) {
|
||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||
|
||||
destroyMetadata(index_dir);
|
||||
}
|
||||
|
||||
return Resource::destroy(attempt_trash);
|
||||
}
|
||||
|
||||
void Mod::destroyMetadata(QDir& index_dir)
|
||||
{
|
||||
if (metadata()) {
|
||||
Metadata::remove(index_dir, metadata()->slug);
|
||||
} else {
|
||||
auto n = name();
|
||||
Metadata::remove(index_dir, n);
|
||||
}
|
||||
m_local_details.metadata = nullptr;
|
||||
}
|
||||
|
||||
auto Mod::details() const -> const ModDetails&
|
||||
{
|
||||
return m_local_details;
|
||||
|
|
@ -181,10 +135,7 @@ auto Mod::name() const -> QString
|
|||
if (!d_name.isEmpty())
|
||||
return d_name;
|
||||
|
||||
if (metadata())
|
||||
return metadata()->name;
|
||||
|
||||
return m_name;
|
||||
return Resource::name();
|
||||
}
|
||||
|
||||
auto Mod::version() const -> QString
|
||||
|
|
@ -192,16 +143,52 @@ auto Mod::version() const -> QString
|
|||
return details().version;
|
||||
}
|
||||
|
||||
auto Mod::homeurl() const -> QString
|
||||
auto Mod::homepage() const -> QString
|
||||
{
|
||||
return details().homeurl;
|
||||
QString metaUrl = Resource::homepage();
|
||||
|
||||
if (metaUrl.isEmpty())
|
||||
return details().homeurl;
|
||||
else
|
||||
return metaUrl;
|
||||
}
|
||||
|
||||
auto Mod::metaurl() const -> QString
|
||||
auto Mod::loaders() const -> QString
|
||||
{
|
||||
if (metadata() == nullptr)
|
||||
return homeurl();
|
||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||
if (metadata()) {
|
||||
QStringList loaders;
|
||||
auto modLoaders = metadata()->loaders;
|
||||
for (auto loader : ModPlatform::modLoaderTypesToList(modLoaders)) {
|
||||
loaders << getModLoaderAsString(loader);
|
||||
}
|
||||
return loaders.join(", ");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::side() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return Metadata::modSideToString(metadata()->side);
|
||||
|
||||
return Metadata::modSideToString(Metadata::ModSide::UniversalSide);
|
||||
}
|
||||
|
||||
auto Mod::mcVersions() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->mcVersions.join(", ");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::releaseType() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->releaseType.toString();
|
||||
|
||||
return ModPlatform::IndexedVersionType().toString();
|
||||
}
|
||||
|
||||
auto Mod::description() const -> QString
|
||||
|
|
@ -214,73 +201,17 @@ auto Mod::authors() const -> QStringList
|
|||
return details().authors;
|
||||
}
|
||||
|
||||
auto Mod::status() const -> ModStatus
|
||||
{
|
||||
return details().status;
|
||||
}
|
||||
|
||||
auto Mod::metadata() -> std::shared_ptr<Metadata::ModStruct>
|
||||
{
|
||||
return m_local_details.metadata;
|
||||
}
|
||||
|
||||
auto Mod::metadata() const -> const std::shared_ptr<Metadata::ModStruct>
|
||||
{
|
||||
return m_local_details.metadata;
|
||||
}
|
||||
|
||||
void Mod::finishResolvingWithDetails(ModDetails&& details)
|
||||
{
|
||||
m_is_resolving = false;
|
||||
m_is_resolved = true;
|
||||
|
||||
std::shared_ptr<Metadata::ModStruct> metadata = details.metadata;
|
||||
if (details.status == ModStatus::Unknown)
|
||||
details.status = m_local_details.status;
|
||||
|
||||
m_local_details = std::move(details);
|
||||
if (metadata)
|
||||
setMetadata(std::move(metadata));
|
||||
if (!iconPath().isEmpty()) {
|
||||
m_packImageCacheKey.wasReadAttempt = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto Mod::provider() const -> std::optional<QString>
|
||||
{
|
||||
if (metadata())
|
||||
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::side() const -> Metadata::ModSide
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->side;
|
||||
return Metadata::ModSide::UniversalSide;
|
||||
}
|
||||
|
||||
auto Mod::releaseType() const -> ModPlatform::IndexedVersionType
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->releaseType;
|
||||
return ModPlatform::IndexedVersionType::VersionType::Unknown;
|
||||
}
|
||||
|
||||
auto Mod::loaders() const -> ModPlatform::ModLoaderTypes
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->loaders;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::mcVersions() const -> QStringList
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->mcVersions;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Mod::licenses() const -> const QList<ModLicense>&
|
||||
{
|
||||
return details().licenses;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@
|
|||
|
||||
#include "ModDetails.h"
|
||||
#include "Resource.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class Mod : public Resource {
|
||||
Q_OBJECT
|
||||
|
|
@ -58,24 +57,20 @@ class Mod : public Resource {
|
|||
|
||||
Mod() = default;
|
||||
Mod(const QFileInfo& file);
|
||||
Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata);
|
||||
Mod(QString file_path) : Mod(QFileInfo(file_path)) {}
|
||||
|
||||
auto details() const -> const ModDetails&;
|
||||
auto name() const -> QString override;
|
||||
auto version() const -> QString;
|
||||
auto homeurl() const -> QString;
|
||||
auto homepage() const -> QString override;
|
||||
auto description() const -> QString;
|
||||
auto authors() const -> QStringList;
|
||||
auto status() const -> ModStatus;
|
||||
auto provider() const -> std::optional<QString>;
|
||||
auto licenses() const -> const QList<ModLicense>&;
|
||||
auto issueTracker() const -> QString;
|
||||
auto metaurl() const -> QString;
|
||||
auto side() const -> Metadata::ModSide;
|
||||
auto loaders() const -> ModPlatform::ModLoaderTypes;
|
||||
auto mcVersions() const -> QStringList;
|
||||
auto releaseType() const -> ModPlatform::IndexedVersionType;
|
||||
auto side() const -> QString;
|
||||
auto loaders() const -> QString;
|
||||
auto mcVersions() const -> QString;
|
||||
auto releaseType() const -> QString;
|
||||
|
||||
/** Get the intneral path to the mod's icon file*/
|
||||
QString iconPath() const { return m_local_details.icon_file; }
|
||||
|
|
@ -84,17 +79,11 @@ class Mod : public Resource {
|
|||
/** Thread-safe. */
|
||||
QPixmap setIcon(QImage new_image) const;
|
||||
|
||||
auto metadata() -> std::shared_ptr<Metadata::ModStruct>;
|
||||
auto metadata() const -> const std::shared_ptr<Metadata::ModStruct>;
|
||||
|
||||
void setStatus(ModStatus status);
|
||||
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
||||
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
||||
void setDetails(const ModDetails& details);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
[[nodiscard]] int compare(Resource const& other, SortType type) const override;
|
||||
[[nodiscard]] int compare(const Resource & other, SortType type) const override;
|
||||
[[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
|
||||
|
||||
// Delete all the files of this mod
|
||||
|
|
|
|||
|
|
@ -43,13 +43,6 @@
|
|||
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
||||
enum class ModStatus {
|
||||
Installed, // Both JAR and Metadata are present
|
||||
NotInstalled, // Only the Metadata is present
|
||||
NoMetadata, // Only the JAR is present
|
||||
Unknown, // Default status
|
||||
};
|
||||
|
||||
struct ModLicense {
|
||||
QString name = {};
|
||||
QString id = {};
|
||||
|
|
@ -149,12 +142,6 @@ struct ModDetails {
|
|||
/* Path of mod logo */
|
||||
QString icon_file = {};
|
||||
|
||||
/* Installation status of the mod */
|
||||
ModStatus status = ModStatus::Unknown;
|
||||
|
||||
/* Metadata information, if any */
|
||||
std::shared_ptr<Metadata::ModStruct> metadata = nullptr;
|
||||
|
||||
ModDetails() = default;
|
||||
|
||||
/** Metadata should be handled manually to properly set the mod status. */
|
||||
|
|
@ -169,40 +156,9 @@ struct ModDetails {
|
|||
, issue_tracker(other.issue_tracker)
|
||||
, licenses(other.licenses)
|
||||
, icon_file(other.icon_file)
|
||||
, status(other.status)
|
||||
{}
|
||||
|
||||
ModDetails& operator=(const ModDetails& other)
|
||||
{
|
||||
this->mod_id = other.mod_id;
|
||||
this->name = other.name;
|
||||
this->version = other.version;
|
||||
this->mcversion = other.mcversion;
|
||||
this->homeurl = other.homeurl;
|
||||
this->description = other.description;
|
||||
this->authors = other.authors;
|
||||
this->issue_tracker = other.issue_tracker;
|
||||
this->licenses = other.licenses;
|
||||
this->icon_file = other.icon_file;
|
||||
this->status = other.status;
|
||||
ModDetails& operator=(const ModDetails& other) = default;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
ModDetails& operator=(const ModDetails&& other)
|
||||
{
|
||||
this->mod_id = other.mod_id;
|
||||
this->name = other.name;
|
||||
this->version = other.version;
|
||||
this->mcversion = other.mcversion;
|
||||
this->homeurl = other.homeurl;
|
||||
this->description = other.description;
|
||||
this->authors = other.authors;
|
||||
this->issue_tracker = other.issue_tracker;
|
||||
this->licenses = other.licenses;
|
||||
this->icon_file = other.icon_file;
|
||||
this->status = other.status;
|
||||
|
||||
return *this;
|
||||
}
|
||||
ModDetails& operator=(ModDetails&& other) = default;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,18 +51,10 @@
|
|||
|
||||
#include "Application.h"
|
||||
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
||||
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
|
||||
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
|
||||
ModFolderModel::ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
|
||||
{
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Version", "Last Modified", "Provider", "Size", "Side", "Loaders",
|
||||
"Minecraft Versions", "Release Type" });
|
||||
|
|
@ -92,7 +84,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
|||
case NameColumn:
|
||||
return m_resources[row]->name();
|
||||
case VersionColumn: {
|
||||
switch (m_resources[row]->type()) {
|
||||
switch (at(row).type()) {
|
||||
case ResourceType::FOLDER:
|
||||
return tr("Folder");
|
||||
case ResourceType::SINGLEFILE:
|
||||
|
|
@ -100,64 +92,50 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
|||
default:
|
||||
break;
|
||||
}
|
||||
return at(row)->version();
|
||||
return at(row).version();
|
||||
}
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
return at(row).dateTimeChanged();
|
||||
case ProviderColumn: {
|
||||
auto provider = at(row)->provider();
|
||||
if (!provider.has_value()) {
|
||||
//: Unknown mod provider (i.e. not Modrinth, CurseForge, etc...)
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
return provider.value();
|
||||
return at(row).provider();
|
||||
}
|
||||
case SideColumn: {
|
||||
return Metadata::modSideToString(at(row)->side());
|
||||
return at(row).side();
|
||||
}
|
||||
case LoadersColumn: {
|
||||
QStringList loaders;
|
||||
auto modLoaders = at(row)->loaders();
|
||||
for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Cauldron, ModPlatform::LiteLoader,
|
||||
ModPlatform::Fabric, ModPlatform::Quilt }) {
|
||||
if (modLoaders & loader) {
|
||||
loaders << getModLoaderAsString(loader);
|
||||
}
|
||||
}
|
||||
return loaders.join(", ");
|
||||
return at(row).loaders();
|
||||
}
|
||||
case McVersionsColumn: {
|
||||
return at(row)->mcVersions().join(", ");
|
||||
return at(row).mcVersions();
|
||||
}
|
||||
case ReleaseTypeColumn: {
|
||||
return at(row)->releaseType().toString();
|
||||
return at(row).releaseType();
|
||||
}
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
return at(row).sizeStr();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
}
|
||||
if (at(row)->isMoreThanOneHardLink()) {
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
}
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
return at(row).icon({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
@ -169,7 +147,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
|
|||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
|
@ -210,7 +188,7 @@ QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientatio
|
|||
case DateColumn:
|
||||
return tr("The date and time this mod was last changed (or added).");
|
||||
case ProviderColumn:
|
||||
return tr("Where the mod was downloaded from.");
|
||||
return tr("The source provider of the mod.");
|
||||
case SideColumn:
|
||||
return tr("On what environment the mod is running.");
|
||||
case LoadersColumn:
|
||||
|
|
@ -235,133 +213,16 @@ int ModFolderModel::columnCount(const QModelIndex& parent) const
|
|||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ModFolderModel::createUpdateTask()
|
||||
{
|
||||
auto index_dir = indexDir();
|
||||
auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load);
|
||||
m_first_folder_load = false;
|
||||
return task;
|
||||
}
|
||||
|
||||
Task* ModFolderModel::createParseTask(Resource& resource)
|
||||
{
|
||||
return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo());
|
||||
}
|
||||
|
||||
bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata)
|
||||
{
|
||||
for (auto mod : allMods()) {
|
||||
if (mod->getOriginalFileName() == filename) {
|
||||
auto index_dir = indexDir();
|
||||
mod->destroy(index_dir, preserve_metadata, false);
|
||||
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
auto m = at(i.row());
|
||||
auto index_dir = indexDir();
|
||||
m->destroy(index_dir);
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return true;
|
||||
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
continue;
|
||||
}
|
||||
auto m = at(i.row());
|
||||
auto index_dir = indexDir();
|
||||
m->destroyMetadata(index_dir);
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModFolderModel::isValid()
|
||||
{
|
||||
return m_dir.exists() && m_dir.isReadable();
|
||||
}
|
||||
|
||||
bool ModFolderModel::startWatching()
|
||||
{
|
||||
// Remove orphaned metadata next time
|
||||
m_first_folder_load = true;
|
||||
return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
||||
}
|
||||
|
||||
bool ModFolderModel::stopWatching()
|
||||
{
|
||||
return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() });
|
||||
}
|
||||
|
||||
auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList<Mod*>
|
||||
{
|
||||
QList<Mod*> selected_resources;
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0)
|
||||
continue;
|
||||
|
||||
selected_resources.push_back(at(i.row()));
|
||||
}
|
||||
return selected_resources;
|
||||
}
|
||||
|
||||
auto ModFolderModel::allMods() -> QList<Mod*>
|
||||
{
|
||||
QList<Mod*> mods;
|
||||
|
||||
for (auto& res : qAsConst(m_resources)) {
|
||||
mods.append(static_cast<Mod*>(res.get()));
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
|
||||
void ModFolderModel::onUpdateSucceeded()
|
||||
{
|
||||
auto update_results = static_cast<ModFolderLoadTask*>(m_current_update_task.get())->result();
|
||||
|
||||
auto& new_mods = update_results->mods;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
auto current_list = m_resources_index.keys();
|
||||
QSet<QString> current_set(current_list.begin(), current_list.end());
|
||||
|
||||
auto new_list = new_mods.keys();
|
||||
QSet<QString> new_set(new_list.begin(), new_list.end());
|
||||
#else
|
||||
QSet<QString> current_set(m_resources_index.keys().toSet());
|
||||
QSet<QString> new_set(new_mods.keys().toSet());
|
||||
#endif
|
||||
|
||||
applyUpdates(current_set, new_set, new_mods);
|
||||
}
|
||||
|
||||
void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
||||
{
|
||||
auto iter = m_active_parse_tasks.constFind(ticket);
|
||||
|
|
@ -379,51 +240,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
|
|||
|
||||
auto result = cast_task->result();
|
||||
if (result && resource)
|
||||
resource->finishResolvingWithDetails(std::move(result->details));
|
||||
static_cast<Mod*>(resource.get())->finishResolvingWithDetails(std::move(result->details));
|
||||
|
||||
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
|
||||
static const FlameAPI flameAPI;
|
||||
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
|
||||
{
|
||||
if (vers.addonId.isValid()) {
|
||||
ModPlatform::IndexedPack pack{
|
||||
vers.addonId,
|
||||
ModPlatform::ResourceProvider::FLAME,
|
||||
};
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto job = flameAPI.getProject(vers.addonId.toString(), response);
|
||||
|
||||
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
||||
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
||||
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qDebug() << *response;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
||||
FlameMod::loadIndexedPack(pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
|
||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||
update_metadata.start();
|
||||
});
|
||||
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
return ResourceFolderModel::installResource(file_path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,6 @@
|
|||
#include "Mod.h"
|
||||
#include "ResourceFolderModel.h"
|
||||
|
||||
#include "minecraft/mod/tasks/LocalModParseTask.h"
|
||||
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
class LegacyInstance;
|
||||
class BaseInstance;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
|
|
@ -76,8 +71,7 @@ class ModFolderModel : public ResourceFolderModel {
|
|||
ReleaseTypeColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
enum ModStatusAction { Disable, Enable, Toggle };
|
||||
ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed = false, bool create_dir = true);
|
||||
ModFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
|
||||
virtual QString id() const override { return "mods"; }
|
||||
|
||||
|
|
@ -86,34 +80,13 @@ class ModFolderModel : public ResourceFolderModel {
|
|||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
int columnCount(const QModelIndex& parent) const override;
|
||||
|
||||
[[nodiscard]] Task* createUpdateTask() override;
|
||||
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new Mod(file); }
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
|
||||
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
|
||||
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
|
||||
|
||||
/// Deletes all the selected mods
|
||||
bool deleteMods(const QModelIndexList& indexes);
|
||||
bool deleteModsMetadata(const QModelIndexList& indexes);
|
||||
|
||||
bool isValid();
|
||||
|
||||
bool startWatching() override;
|
||||
bool stopWatching() override;
|
||||
|
||||
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
|
||||
|
||||
auto selectedMods(QModelIndexList& indexes) -> QList<Mod*>;
|
||||
auto allMods() -> QList<Mod*>;
|
||||
|
||||
RESOURCE_HELPERS(Mod)
|
||||
|
||||
private slots:
|
||||
void onUpdateSucceeded() override;
|
||||
void onParseSucceeded(int ticket, QString resource_id) override;
|
||||
|
||||
protected:
|
||||
bool m_is_indexed;
|
||||
bool m_first_folder_load = true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ void Resource::setFile(QFileInfo file_info)
|
|||
parseFile();
|
||||
}
|
||||
|
||||
std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
|
||||
static std::tuple<QString, qint64> calculateFileSize(const QFileInfo& file)
|
||||
{
|
||||
if (file.isDir()) {
|
||||
auto dir = QDir(file.absoluteFilePath());
|
||||
|
|
@ -72,6 +72,14 @@ void Resource::parseFile()
|
|||
m_changed_date_time = m_file_info.lastModified();
|
||||
}
|
||||
|
||||
auto Resource::name() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return metadata()->name;
|
||||
|
||||
return m_name;
|
||||
}
|
||||
|
||||
static void removeThePrefix(QString& string)
|
||||
{
|
||||
QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption);
|
||||
|
|
@ -79,6 +87,30 @@ static void removeThePrefix(QString& string)
|
|||
string = string.trimmed();
|
||||
}
|
||||
|
||||
auto Resource::provider() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return ModPlatform::ProviderCapabilities::readableName(metadata()->provider);
|
||||
|
||||
return tr("Unknown");
|
||||
}
|
||||
|
||||
auto Resource::homepage() const -> QString
|
||||
{
|
||||
if (metadata())
|
||||
return ModPlatform::getMetaURL(metadata()->provider, metadata()->project_id);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void Resource::setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata)
|
||||
{
|
||||
if (status() == ResourceStatus::NO_METADATA)
|
||||
setStatus(ResourceStatus::INSTALLED);
|
||||
|
||||
m_metadata = metadata;
|
||||
}
|
||||
|
||||
int Resource::compare(const Resource& other, SortType type) const
|
||||
{
|
||||
switch (type) {
|
||||
|
|
@ -93,6 +125,7 @@ int Resource::compare(const Resource& other, SortType type) const
|
|||
QString this_name{ name() };
|
||||
QString other_name{ other.name() };
|
||||
|
||||
// TODO do we need this? it could result in 0 being returned
|
||||
removeThePrefix(this_name);
|
||||
removeThePrefix(other_name);
|
||||
|
||||
|
|
@ -118,6 +151,12 @@ int Resource::compare(const Resource& other, SortType type) const
|
|||
return -1;
|
||||
break;
|
||||
}
|
||||
case SortType::PROVIDER: {
|
||||
auto compare_result = QString::compare(provider(), other.provider(), Qt::CaseInsensitive);
|
||||
if (compare_result != 0)
|
||||
return compare_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -174,10 +213,27 @@ bool Resource::enable(EnableAction action)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Resource::destroy(bool attemptTrash)
|
||||
auto Resource::destroy(const QDir& index_dir, bool preserve_metadata, bool attempt_trash) -> bool
|
||||
{
|
||||
m_type = ResourceType::UNKNOWN;
|
||||
return (attemptTrash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
||||
|
||||
if (!preserve_metadata) {
|
||||
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
|
||||
destroyMetadata(index_dir);
|
||||
}
|
||||
|
||||
return (attempt_trash && FS::trash(m_file_info.filePath())) || FS::deletePath(m_file_info.filePath());
|
||||
}
|
||||
|
||||
auto Resource::destroyMetadata(const QDir& index_dir) -> void
|
||||
{
|
||||
if (metadata()) {
|
||||
Metadata::remove(index_dir, metadata()->slug);
|
||||
} else {
|
||||
auto n = name();
|
||||
Metadata::remove(index_dir, n);
|
||||
}
|
||||
m_metadata = nullptr;
|
||||
}
|
||||
|
||||
bool Resource::isSymLinkUnder(const QString& instPath) const
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "MetadataHandler.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
enum class ResourceType {
|
||||
|
|
@ -50,7 +51,14 @@ enum class ResourceType {
|
|||
LITEMOD, //!< The resource is a litemod
|
||||
};
|
||||
|
||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, LOADERS, MC_VERSIONS, RELEASE_TYPE };
|
||||
enum class ResourceStatus {
|
||||
INSTALLED, // Both JAR and Metadata are present
|
||||
NOT_INSTALLED, // Only the Metadata is present
|
||||
NO_METADATA, // Only the JAR is present
|
||||
UNKNOWN, // Default status
|
||||
};
|
||||
|
||||
enum class SortType { NAME, DATE, VERSION, ENABLED, PACK_FORMAT, PROVIDER, SIZE, SIDE, MC_VERSIONS, LOADERS, RELEASE_TYPE };
|
||||
|
||||
enum class EnableAction { ENABLE, DISABLE, TOGGLE };
|
||||
|
||||
|
|
@ -84,9 +92,19 @@ class Resource : public QObject {
|
|||
[[nodiscard]] QString sizeStr() const { return m_size_str; }
|
||||
[[nodiscard]] qint64 sizeInfo() const { return m_size_info; }
|
||||
|
||||
[[nodiscard]] virtual auto name() const -> QString { return m_name; }
|
||||
[[nodiscard]] virtual auto name() const -> QString;
|
||||
[[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; }
|
||||
|
||||
[[nodiscard]] auto status() const -> ResourceStatus { return m_status; };
|
||||
[[nodiscard]] auto metadata() -> std::shared_ptr<Metadata::ModStruct> { return m_metadata; }
|
||||
[[nodiscard]] auto metadata() const -> std::shared_ptr<const Metadata::ModStruct> { return m_metadata; }
|
||||
[[nodiscard]] auto provider() const -> QString;
|
||||
[[nodiscard]] virtual auto homepage() const -> QString;
|
||||
|
||||
void setStatus(ResourceStatus status) { m_status = status; }
|
||||
void setMetadata(std::shared_ptr<Metadata::ModStruct>&& metadata);
|
||||
void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared<Metadata::ModStruct>(metadata)); }
|
||||
|
||||
/** Compares two Resources, for sorting purposes, considering a ascending order, returning:
|
||||
* > 0: 'this' comes after 'other'
|
||||
* = 0: 'this' is equal to 'other'
|
||||
|
|
@ -117,7 +135,9 @@ class Resource : public QObject {
|
|||
}
|
||||
|
||||
// Delete all files of this resource.
|
||||
bool destroy(bool attemptTrash = true);
|
||||
auto destroy(const QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
|
||||
// Delete the metadata only.
|
||||
auto destroyMetadata(const QDir& index_dir) -> void;
|
||||
|
||||
[[nodiscard]] auto isSymLink() const -> bool { return m_file_info.isSymLink(); }
|
||||
|
||||
|
|
@ -146,6 +166,11 @@ class Resource : public QObject {
|
|||
/* The type of file we're dealing with. */
|
||||
ResourceType m_type = ResourceType::UNKNOWN;
|
||||
|
||||
/* Installation status of the resource. */
|
||||
ResourceStatus m_status = ResourceStatus::UNKNOWN;
|
||||
|
||||
std::shared_ptr<Metadata::ModStruct> m_metadata = nullptr;
|
||||
|
||||
/* Whether the resource is enabled (e.g. shows up in the game) or not. */
|
||||
bool m_enabled = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,20 +11,23 @@
|
|||
#include <QStyle>
|
||||
#include <QThreadPool>
|
||||
#include <QUrl>
|
||||
#include <utility>
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include "QVariantUtils.h"
|
||||
#include "StringUtils.h"
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
|
||||
|
||||
#include "Json.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "ui/dialogs/CustomMessageBox.h"
|
||||
|
||||
ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObject* parent, bool create_dir)
|
||||
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this)
|
||||
ResourceFolderModel::ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||
: QAbstractListModel(parent), m_dir(dir), m_instance(instance), m_watcher(this), m_is_indexed(is_indexed)
|
||||
{
|
||||
if (create_dir) {
|
||||
FS::ensureFolderPathExists(m_dir.absolutePath());
|
||||
|
|
@ -48,6 +51,9 @@ ResourceFolderModel::~ResourceFolderModel()
|
|||
|
||||
bool ResourceFolderModel::startWatching(const QStringList& paths)
|
||||
{
|
||||
// Remove orphaned metadata next time
|
||||
m_first_folder_load = true;
|
||||
|
||||
if (m_is_watching)
|
||||
return false;
|
||||
|
||||
|
|
@ -158,11 +164,55 @@ bool ResourceFolderModel::installResource(QString original_path)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::uninstallResource(QString file_name)
|
||||
bool ResourceFolderModel::installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers)
|
||||
{
|
||||
if (vers.addonId.isValid()) {
|
||||
ModPlatform::IndexedPack pack{
|
||||
vers.addonId,
|
||||
ModPlatform::ResourceProvider::FLAME,
|
||||
};
|
||||
|
||||
QEventLoop loop;
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto job = FlameAPI().getProject(vers.addonId.toString(), response);
|
||||
|
||||
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
|
||||
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
|
||||
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
|
||||
<< " reason: " << parse_error.errorString();
|
||||
qDebug() << *response;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto obj = Json::requireObject(Json::requireObject(doc), "data");
|
||||
FlameMod::loadIndexedPack(pack, obj);
|
||||
} catch (const JSONValidationError& e) {
|
||||
qDebug() << doc;
|
||||
qWarning() << "Error while reading mod info: " << e.cause();
|
||||
}
|
||||
LocalResourceUpdateTask update_metadata(indexDir(), pack, vers);
|
||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||
update_metadata.start();
|
||||
});
|
||||
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
}
|
||||
|
||||
return installResource(std::move(path));
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::uninstallResource(QString file_name, bool preserve_metadata)
|
||||
{
|
||||
for (auto& resource : m_resources) {
|
||||
if (resource->fileinfo().fileName() == file_name) {
|
||||
auto res = resource->destroy(false);
|
||||
auto res = resource->destroy(indexDir(), preserve_metadata, false);
|
||||
|
||||
update();
|
||||
|
||||
|
|
@ -178,13 +228,11 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
|||
return true;
|
||||
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0) {
|
||||
if (i.column() != 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& resource = m_resources.at(i.row());
|
||||
|
||||
resource->destroy();
|
||||
resource->destroy(indexDir());
|
||||
}
|
||||
|
||||
update();
|
||||
|
|
@ -192,6 +240,22 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes)
|
|||
return true;
|
||||
}
|
||||
|
||||
void ResourceFolderModel::deleteMetadata(const QModelIndexList& indexes)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return;
|
||||
|
||||
for (auto i : indexes) {
|
||||
if (i.column() != 0)
|
||||
continue;
|
||||
|
||||
auto& resource = m_resources.at(i.row());
|
||||
resource->destroyMetadata(indexDir());
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::setResourceEnabled(const QModelIndexList& indexes, EnableAction action)
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
|
|
@ -245,7 +309,7 @@ bool ResourceFolderModel::update()
|
|||
connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection);
|
||||
connect(
|
||||
m_current_update_task.get(), &Task::finished, this,
|
||||
[=] {
|
||||
[this] {
|
||||
m_current_update_task.reset();
|
||||
if (m_scheduled_update) {
|
||||
m_scheduled_update = false;
|
||||
|
|
@ -299,7 +363,7 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res)
|
|||
|
||||
void ResourceFolderModel::onUpdateSucceeded()
|
||||
{
|
||||
auto update_results = static_cast<BasicFolderLoadTask*>(m_current_update_task.get())->result();
|
||||
auto update_results = static_cast<ResourceFolderLoadTask*>(m_current_update_task.get())->result();
|
||||
|
||||
auto& new_resources = update_results->resources;
|
||||
|
||||
|
|
@ -329,7 +393,11 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id)
|
|||
|
||||
Task* ResourceFolderModel::createUpdateTask()
|
||||
{
|
||||
return new BasicFolderLoadTask(m_dir);
|
||||
auto index_dir = indexDir();
|
||||
auto task = new ResourceFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load,
|
||||
[this](const QFileInfo& file) { return createResource(file); });
|
||||
m_first_folder_load = false;
|
||||
return task;
|
||||
}
|
||||
|
||||
bool ResourceFolderModel::hasPendingParseTasks() const
|
||||
|
|
@ -419,6 +487,8 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
|||
return m_resources[row]->name();
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case ProviderColumn:
|
||||
return m_resources[row]->provider();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
default:
|
||||
|
|
@ -490,22 +560,23 @@ QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orien
|
|||
case ActiveColumn:
|
||||
case NameColumn:
|
||||
case DateColumn:
|
||||
case ProviderColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::ToolTipRole: {
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("Is the resource enabled?");
|
||||
case NameColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The name of the resource.");
|
||||
case DateColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The date and time this resource was last changed (or added).");
|
||||
case ProviderColumn:
|
||||
return tr("The source provider of the resource.");
|
||||
case SizeColumn:
|
||||
return tr("The size of the resource.");
|
||||
default:
|
||||
|
|
@ -651,3 +722,126 @@ void ResourceFolderModel::onParseFailed(int ticket, QString resource_id)
|
|||
}
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources)
|
||||
{
|
||||
// see if the kept resources changed in some way
|
||||
{
|
||||
QSet<QString> kept_set = current_set;
|
||||
kept_set.intersect(new_set);
|
||||
|
||||
for (auto const& kept : kept_set) {
|
||||
auto row_it = m_resources_index.constFind(kept);
|
||||
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||
auto row = row_it.value();
|
||||
|
||||
auto& new_resource = new_resources[kept];
|
||||
auto const& current_resource = m_resources.at(row);
|
||||
|
||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||
// no significant change, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the resource is resolving, but something about it changed, we don't want to
|
||||
// continue the resolving.
|
||||
if (current_resource->isResolving()) {
|
||||
auto ticket = current_resource->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
m_resources[row].reset(new_resource);
|
||||
resolveResource(m_resources.at(row));
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// remove resources no longer present
|
||||
{
|
||||
QSet<QString> removed_set = current_set;
|
||||
removed_set.subtract(new_set);
|
||||
|
||||
QList<int> removed_rows;
|
||||
for (auto& removed : removed_set)
|
||||
removed_rows.append(m_resources_index[removed]);
|
||||
|
||||
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||
|
||||
for (auto& removed_index : removed_rows) {
|
||||
auto removed_it = m_resources.begin() + removed_index;
|
||||
|
||||
Q_ASSERT(removed_it != m_resources.end());
|
||||
|
||||
if ((*removed_it)->isResolving()) {
|
||||
auto ticket = (*removed_it)->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||
m_resources.erase(removed_it);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
// add new resources to the end
|
||||
{
|
||||
QSet<QString> added_set = new_set;
|
||||
added_set.subtract(current_set);
|
||||
|
||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||
if (added_set.size() > 0) {
|
||||
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||
|
||||
for (auto& added : added_set) {
|
||||
auto res = new_resources[added];
|
||||
m_resources.append(res);
|
||||
resolveResource(m_resources.last());
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
// update index
|
||||
{
|
||||
m_resources_index.clear();
|
||||
int idx = 0;
|
||||
for (auto const& mod : qAsConst(m_resources)) {
|
||||
m_resources_index[mod->internal_id()] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Resource::Ptr ResourceFolderModel::find(QString id)
|
||||
{
|
||||
auto iter =
|
||||
std::find_if(m_resources.constBegin(), m_resources.constEnd(), [&](Resource::Ptr const& r) { return r->internal_id() == id; });
|
||||
if (iter == m_resources.constEnd())
|
||||
return nullptr;
|
||||
return *iter;
|
||||
}
|
||||
QList<Resource*> ResourceFolderModel::allResources()
|
||||
{
|
||||
QList<Resource*> result;
|
||||
result.reserve(m_resources.size());
|
||||
for (const Resource ::Ptr& resource : m_resources)
|
||||
result.append((resource.get()));
|
||||
return result;
|
||||
}
|
||||
QList<Resource*> ResourceFolderModel::selectedResources(const QModelIndexList& indexes)
|
||||
{
|
||||
QList<Resource*> result;
|
||||
for (const QModelIndex& index : indexes) {
|
||||
if (index.column() != 0)
|
||||
continue;
|
||||
result.append(&at(index.row()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,38 @@
|
|||
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
||||
#define RESOURCE_HELPERS(T) \
|
||||
[[nodiscard]] T& at(int index) \
|
||||
{ \
|
||||
return *static_cast<T*>(m_resources[index].get()); \
|
||||
} \
|
||||
[[nodiscard]] const T& at(int index) const \
|
||||
{ \
|
||||
return *static_cast<const T*>(m_resources.at(index).get()); \
|
||||
} \
|
||||
QList<T*> selected##T##s(const QModelIndexList& indexes) \
|
||||
{ \
|
||||
QList<T*> result; \
|
||||
for (const QModelIndex& index : indexes) { \
|
||||
if (index.column() != 0) \
|
||||
continue; \
|
||||
\
|
||||
result.append(&at(index.row())); \
|
||||
} \
|
||||
return result; \
|
||||
} \
|
||||
QList<T*> all##T##s() \
|
||||
{ \
|
||||
QList<T*> result; \
|
||||
result.reserve(m_resources.size()); \
|
||||
\
|
||||
for (const Resource::Ptr& resource : m_resources) \
|
||||
result.append(static_cast<T*>(resource.get())); \
|
||||
\
|
||||
return result; \
|
||||
}
|
||||
|
||||
/** A basic model for external resources.
|
||||
*
|
||||
* This model manages a list of resources. As such, external users of such resources do not own them,
|
||||
|
|
@ -29,7 +61,7 @@ class QSortFilterProxyModel;
|
|||
class ResourceFolderModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResourceFolderModel(QDir, BaseInstance* instance, QObject* parent = nullptr, bool create_dir = true);
|
||||
ResourceFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
~ResourceFolderModel() override;
|
||||
|
||||
virtual QString id() const { return "resource"; }
|
||||
|
|
@ -49,8 +81,10 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
bool stopWatching(const QStringList& paths);
|
||||
|
||||
/* Helper methods for subclasses, using a predetermined list of paths. */
|
||||
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
|
||||
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
|
||||
virtual bool startWatching() { return startWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
|
||||
virtual bool stopWatching() { return stopWatching({ indexDir().absolutePath(), m_dir.absolutePath() }); }
|
||||
|
||||
QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; }
|
||||
|
||||
/** Given a path in the system, install that resource, moving it to its place in the
|
||||
* instance file hierarchy.
|
||||
|
|
@ -59,12 +93,15 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
*/
|
||||
virtual bool installResource(QString path);
|
||||
|
||||
virtual bool installResourceWithFlameMetadata(QString path, ModPlatform::IndexedVersion& vers);
|
||||
|
||||
/** Uninstall (i.e. remove all data about it) a resource, given its file name.
|
||||
*
|
||||
* Returns whether the removal was successful.
|
||||
*/
|
||||
virtual bool uninstallResource(QString file_name);
|
||||
virtual bool uninstallResource(QString file_name, bool preserve_metadata = false);
|
||||
virtual bool deleteResources(const QModelIndexList&);
|
||||
virtual void deleteMetadata(const QModelIndexList&);
|
||||
|
||||
/** Applies the given 'action' to the resources in 'indexes'.
|
||||
*
|
||||
|
|
@ -80,9 +117,13 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
|
||||
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
|
||||
[[nodiscard]] bool empty() const { return size() == 0; }
|
||||
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
|
||||
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
|
||||
[[nodiscard]] QList<Resource::Ptr> const& all() const { return m_resources; }
|
||||
|
||||
[[nodiscard]] Resource& at(int index) { return *m_resources[index].get(); }
|
||||
[[nodiscard]] const Resource& at(int index) const { return *m_resources.at(index).get(); }
|
||||
QList<Resource*> selectedResources(const QModelIndexList& indexes);
|
||||
QList<Resource*> allResources();
|
||||
|
||||
[[nodiscard]] Resource::Ptr find(QString id);
|
||||
|
||||
[[nodiscard]] QDir const& dir() const { return m_dir; }
|
||||
|
||||
|
|
@ -96,7 +137,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
/* Qt behavior */
|
||||
|
||||
/* Basic columns */
|
||||
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
|
||||
|
||||
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||
|
|
@ -153,7 +195,9 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
* This Task is normally executed when opening a page, so it shouldn't contain much heavy work.
|
||||
* If such work is needed, try using it in the Task create by createParseTask() instead!
|
||||
*/
|
||||
[[nodiscard]] virtual Task* createUpdateTask();
|
||||
[[nodiscard]] Task* createUpdateTask();
|
||||
|
||||
[[nodiscard]] virtual Resource* createResource(const QFileInfo& info) { return new Resource(info); }
|
||||
|
||||
/** This creates a new parse task to be executed by onUpdateSucceeded().
|
||||
*
|
||||
|
|
@ -167,10 +211,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
* It uses set operations to find differences between the current state and the updated state,
|
||||
* to act only on those disparities.
|
||||
*
|
||||
* The implementation is at the end of this header.
|
||||
*/
|
||||
template <typename T>
|
||||
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources);
|
||||
void applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, Resource::Ptr>& new_resources);
|
||||
|
||||
protected slots:
|
||||
void directoryChanged(QString);
|
||||
|
|
@ -195,19 +237,22 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
protected:
|
||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||
// As such, the order in with they appear is very important!
|
||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::SIZE };
|
||||
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Size" };
|
||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Size") };
|
||||
QList<SortType> m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||
QStringList m_column_names = { "Enable", "Name", "Last Modified", "Provider", "Size" };
|
||||
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") };
|
||||
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
QList<bool> m_columnsHideable = { false, false, true, true };
|
||||
QList<bool> m_columnsHiddenByDefault = { false, false, false, false };
|
||||
QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
QList<bool> m_columnsHideable = { false, false, true, true, true };
|
||||
QList<bool> m_columnsHiddenByDefault = { false, false, false, false, true };
|
||||
|
||||
QDir m_dir;
|
||||
BaseInstance* m_instance;
|
||||
QFileSystemWatcher m_watcher;
|
||||
bool m_is_watching = false;
|
||||
|
||||
bool m_is_indexed;
|
||||
bool m_first_folder_load = true;
|
||||
|
||||
Task::Ptr m_current_update_task = nullptr;
|
||||
bool m_scheduled_update = false;
|
||||
|
||||
|
|
@ -220,133 +265,3 @@ class ResourceFolderModel : public QAbstractListModel {
|
|||
QMap<int, Task::Ptr> m_active_parse_tasks;
|
||||
std::atomic<int> m_next_resolution_ticket = 0;
|
||||
};
|
||||
|
||||
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
|
||||
#define RESOURCE_HELPERS(T) \
|
||||
[[nodiscard]] T* operator[](int index) \
|
||||
{ \
|
||||
return static_cast<T*>(m_resources[index].get()); \
|
||||
} \
|
||||
[[nodiscard]] T* at(int index) \
|
||||
{ \
|
||||
return static_cast<T*>(m_resources[index].get()); \
|
||||
} \
|
||||
[[nodiscard]] const T* at(int index) const \
|
||||
{ \
|
||||
return static_cast<const T*>(m_resources.at(index).get()); \
|
||||
} \
|
||||
[[nodiscard]] T* first() \
|
||||
{ \
|
||||
return static_cast<T*>(m_resources.first().get()); \
|
||||
} \
|
||||
[[nodiscard]] T* last() \
|
||||
{ \
|
||||
return static_cast<T*>(m_resources.last().get()); \
|
||||
} \
|
||||
[[nodiscard]] T* find(QString id) \
|
||||
{ \
|
||||
auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \
|
||||
[&](Resource::Ptr const& r) { return r->internal_id() == id; }); \
|
||||
if (iter == m_resources.constEnd()) \
|
||||
return nullptr; \
|
||||
return static_cast<T*>((*iter).get()); \
|
||||
}
|
||||
|
||||
/* Template definition to avoid some code duplication */
|
||||
template <typename T>
|
||||
void ResourceFolderModel::applyUpdates(QSet<QString>& current_set, QSet<QString>& new_set, QMap<QString, T>& new_resources)
|
||||
{
|
||||
// see if the kept resources changed in some way
|
||||
{
|
||||
QSet<QString> kept_set = current_set;
|
||||
kept_set.intersect(new_set);
|
||||
|
||||
for (auto const& kept : kept_set) {
|
||||
auto row_it = m_resources_index.constFind(kept);
|
||||
Q_ASSERT(row_it != m_resources_index.constEnd());
|
||||
auto row = row_it.value();
|
||||
|
||||
auto& new_resource = new_resources[kept];
|
||||
auto const& current_resource = m_resources.at(row);
|
||||
|
||||
if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) {
|
||||
// no significant change, ignore...
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the resource is resolving, but something about it changed, we don't want to
|
||||
// continue the resolving.
|
||||
if (current_resource->isResolving()) {
|
||||
auto ticket = current_resource->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
m_resources[row].reset(new_resource);
|
||||
resolveResource(m_resources.at(row));
|
||||
emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// remove resources no longer present
|
||||
{
|
||||
QSet<QString> removed_set = current_set;
|
||||
removed_set.subtract(new_set);
|
||||
|
||||
QList<int> removed_rows;
|
||||
for (auto& removed : removed_set)
|
||||
removed_rows.append(m_resources_index[removed]);
|
||||
|
||||
std::sort(removed_rows.begin(), removed_rows.end(), std::greater<int>());
|
||||
|
||||
for (auto& removed_index : removed_rows) {
|
||||
auto removed_it = m_resources.begin() + removed_index;
|
||||
|
||||
Q_ASSERT(removed_it != m_resources.end());
|
||||
|
||||
if ((*removed_it)->isResolving()) {
|
||||
auto ticket = (*removed_it)->resolutionTicket();
|
||||
if (m_active_parse_tasks.contains(ticket)) {
|
||||
auto task = (*m_active_parse_tasks.find(ticket)).get();
|
||||
task->abort();
|
||||
}
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), removed_index, removed_index);
|
||||
m_resources.erase(removed_it);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
// add new resources to the end
|
||||
{
|
||||
QSet<QString> added_set = new_set;
|
||||
added_set.subtract(current_set);
|
||||
|
||||
// When you have a Qt build with assertions turned on, proceeding here will abort the application
|
||||
if (added_set.size() > 0) {
|
||||
beginInsertRows(QModelIndex(), static_cast<int>(m_resources.size()),
|
||||
static_cast<int>(m_resources.size() + added_set.size() - 1));
|
||||
|
||||
for (auto& added : added_set) {
|
||||
auto res = new_resources[added];
|
||||
m_resources.append(res);
|
||||
resolveResource(m_resources.last());
|
||||
}
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
// update index
|
||||
{
|
||||
m_resources_index.clear();
|
||||
int idx = 0;
|
||||
for (auto const& mod : qAsConst(m_resources)) {
|
||||
m_resources_index[mod->internal_id()] = idx;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,19 +44,19 @@
|
|||
#include "Application.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
|
||||
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
|
||||
ResourcePackFolderModel::ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
|
||||
{
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Size" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Size") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE, SortType::SIZE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch,
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Pack Format", "Last Modified", "Provider", "Size" });
|
||||
m_column_names_translated =
|
||||
QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Pack Format"), tr("Last Modified"), tr("Provider"), tr("Size") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT,
|
||||
SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false, false };
|
||||
m_columnsHideable = { false, true, false, true, true, true, true };
|
||||
}
|
||||
|
||||
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
||||
|
|
@ -73,12 +73,12 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
case NameColumn:
|
||||
return m_resources[row]->name();
|
||||
case PackFormatColumn: {
|
||||
auto resource = at(row);
|
||||
auto pack_format = resource->packFormat();
|
||||
auto& resource = at(row);
|
||||
auto pack_format = resource.packFormat();
|
||||
if (pack_format == 0)
|
||||
return tr("Unrecognized");
|
||||
|
||||
auto version_bounds = resource->compatibleVersions();
|
||||
auto version_bounds = resource.compatibleVersions();
|
||||
if (version_bounds.first.toString().isEmpty())
|
||||
return QString::number(pack_format);
|
||||
|
||||
|
|
@ -87,17 +87,18 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
}
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case ProviderColumn:
|
||||
return m_resources[row]->provider();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
@ -107,14 +108,14 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||
}
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
;
|
||||
}
|
||||
if (at(row)->isMoreThanOneHardLink()) {
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
|
|
@ -129,7 +130,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
case Qt::CheckStateRole:
|
||||
switch (column) {
|
||||
case ActiveColumn:
|
||||
return at(row)->enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
return at(row).enabled() ? Qt::Checked : Qt::Unchecked;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
@ -148,6 +149,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
|||
case PackFormatColumn:
|
||||
case DateColumn:
|
||||
case ImageColumn:
|
||||
case ProviderColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
|
|
@ -157,7 +159,7 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
|||
case Qt::ToolTipRole:
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
return tr("Is the resource pack enabled? (Only valid for ZIPs)");
|
||||
return tr("Is the resource pack enabled?");
|
||||
case NameColumn:
|
||||
return tr("The name of the resource pack.");
|
||||
case PackFormatColumn:
|
||||
|
|
@ -165,6 +167,8 @@ QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::O
|
|||
return tr("The resource pack format ID, as well as the Minecraft versions it was designed for.");
|
||||
case DateColumn:
|
||||
return tr("The date and time this resource pack was last changed (or added).");
|
||||
case ProviderColumn:
|
||||
return tr("The source provider of the resource pack.");
|
||||
case SizeColumn:
|
||||
return tr("The size of the resource pack.");
|
||||
default:
|
||||
|
|
@ -185,11 +189,6 @@ int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
|||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ResourcePackFolderModel::createUpdateTask()
|
||||
{
|
||||
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ResourcePack>(entry); });
|
||||
}
|
||||
|
||||
Task* ResourcePackFolderModel::createParseTask(Resource& resource)
|
||||
{
|
||||
return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast<ResourcePack&>(resource));
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@
|
|||
class ResourcePackFolderModel : public ResourceFolderModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, PackFormatColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
|
||||
|
||||
explicit ResourcePackFolderModel(const QString& dir, BaseInstance* instance);
|
||||
explicit ResourcePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
|
||||
virtual QString id() const override { return "resourcepacks"; }
|
||||
QString id() const override { return "resourcepacks"; }
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
|
||||
|
||||
[[nodiscard]] Task* createUpdateTask() override;
|
||||
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new ResourcePack(file); }
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
RESOURCE_HELPERS(ResourcePack)
|
||||
|
|
|
|||
|
|
@ -2,24 +2,24 @@
|
|||
|
||||
#include "ResourceFolderModel.h"
|
||||
#include "minecraft/mod/ShaderPack.h"
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
|
||||
|
||||
class ShaderPackFolderModel : public ResourceFolderModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
|
||||
explicit ShaderPackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr)
|
||||
: ResourceFolderModel(dir, instance, is_indexed, create_dir, parent)
|
||||
{}
|
||||
|
||||
virtual QString id() const override { return "shaderpacks"; }
|
||||
|
||||
[[nodiscard]] Task* createUpdateTask() override
|
||||
{
|
||||
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
|
||||
}
|
||||
[[nodiscard]] Resource* createResource(const QFileInfo& info) override { return new ShaderPack(info); }
|
||||
|
||||
[[nodiscard]] Task* createParseTask(Resource& resource) override
|
||||
{
|
||||
return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
|
||||
}
|
||||
|
||||
RESOURCE_HELPERS(ShaderPack);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,22 +39,18 @@
|
|||
|
||||
#include "TexturePackFolderModel.h"
|
||||
|
||||
#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
|
||||
#include "minecraft/mod/tasks/LocalTexturePackParseTask.h"
|
||||
#include "minecraft/mod/tasks/ResourceFolderLoadTask.h"
|
||||
|
||||
TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance)
|
||||
TexturePackFolderModel::TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent)
|
||||
: ResourceFolderModel(QDir(dir), instance, is_indexed, create_dir, parent)
|
||||
{
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Size" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Size") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::SIZE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive,
|
||||
QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true };
|
||||
}
|
||||
|
||||
Task* TexturePackFolderModel::createUpdateTask()
|
||||
{
|
||||
return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<TexturePack>(entry); });
|
||||
m_column_names = QStringList({ "Enable", "Image", "Name", "Last Modified", "Provider", "Size" });
|
||||
m_column_names_translated = QStringList({ tr("Enable"), tr("Image"), tr("Name"), tr("Last Modified"), tr("Provider"), tr("Size") });
|
||||
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE, SortType::PROVIDER, SortType::SIZE };
|
||||
m_column_resize_modes = { QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::Interactive, QHeaderView::Interactive, QHeaderView::Interactive };
|
||||
m_columnsHideable = { false, true, false, true, true, true };
|
||||
m_columnsHiddenByDefault = { false, false, false, false, false, true };
|
||||
}
|
||||
|
||||
Task* TexturePackFolderModel::createParseTask(Resource& resource)
|
||||
|
|
@ -77,6 +73,8 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
return m_resources[row]->name();
|
||||
case DateColumn:
|
||||
return m_resources[row]->dateTimeChanged();
|
||||
case ProviderColumn:
|
||||
return m_resources[row]->provider();
|
||||
case SizeColumn:
|
||||
return m_resources[row]->sizeStr();
|
||||
default:
|
||||
|
|
@ -84,14 +82,14 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
}
|
||||
case Qt::ToolTipRole:
|
||||
if (column == NameColumn) {
|
||||
if (at(row)->isSymLinkUnder(instDirPath())) {
|
||||
if (at(row).isSymLinkUnder(instDirPath())) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is symbolically linked from elsewhere. Editing it will also change the original."
|
||||
"\nCanonical Path: %1")
|
||||
.arg(at(row)->fileinfo().canonicalFilePath());
|
||||
.arg(at(row).fileinfo().canonicalFilePath());
|
||||
;
|
||||
}
|
||||
if (at(row)->isMoreThanOneHardLink()) {
|
||||
if (at(row).isMoreThanOneHardLink()) {
|
||||
return m_resources[row]->internal_id() +
|
||||
tr("\nWarning: This resource is hard linked elsewhere. Editing it will also change the original.");
|
||||
}
|
||||
|
|
@ -99,10 +97,10 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
|
|||
|
||||
return m_resources[row]->internal_id();
|
||||
case Qt::DecorationRole: {
|
||||
if (column == NameColumn && (at(row)->isSymLinkUnder(instDirPath()) || at(row)->isMoreThanOneHardLink()))
|
||||
if (column == NameColumn && (at(row).isSymLinkUnder(instDirPath()) || at(row).isMoreThanOneHardLink()))
|
||||
return APPLICATION->getThemedIcon("status-yellow");
|
||||
if (column == ImageColumn) {
|
||||
return at(row)->image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
return at(row).image({ 32, 32 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
@ -130,6 +128,7 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
|
|||
case NameColumn:
|
||||
case DateColumn:
|
||||
case ImageColumn:
|
||||
case ProviderColumn:
|
||||
case SizeColumn:
|
||||
return columnNames().at(section);
|
||||
default:
|
||||
|
|
@ -138,14 +137,13 @@ QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Or
|
|||
case Qt::ToolTipRole: {
|
||||
switch (section) {
|
||||
case ActiveColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("Is the texture pack enabled?");
|
||||
case NameColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The name of the texture pack.");
|
||||
case DateColumn:
|
||||
//: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc.
|
||||
return tr("The date and time this texture pack was last changed (or added).");
|
||||
case ProviderColumn:
|
||||
return tr("The source provider of the texture pack.");
|
||||
case SizeColumn:
|
||||
return tr("The size of the texture pack.");
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ class TexturePackFolderModel : public ResourceFolderModel {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, SizeColumn, NUM_COLUMNS };
|
||||
enum Columns { ActiveColumn = 0, ImageColumn, NameColumn, DateColumn, ProviderColumn, SizeColumn, NUM_COLUMNS };
|
||||
|
||||
explicit TexturePackFolderModel(const QString& dir, std::shared_ptr<const BaseInstance> instance);
|
||||
explicit TexturePackFolderModel(const QDir& dir, BaseInstance* instance, bool is_indexed, bool create_dir, QObject* parent = nullptr);
|
||||
|
||||
virtual QString id() const override { return "texturepacks"; }
|
||||
|
||||
|
|
@ -55,8 +55,7 @@ class TexturePackFolderModel : public ResourceFolderModel {
|
|||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent) const override;
|
||||
|
||||
explicit TexturePackFolderModel(const QString& dir, BaseInstance* instance);
|
||||
[[nodiscard]] Task* createUpdateTask() override;
|
||||
[[nodiscard]] Resource* createResource(const QFileInfo& file) override { return new TexturePack(file); }
|
||||
[[nodiscard]] Task* createParseTask(Resource&) override;
|
||||
|
||||
RESOURCE_HELPERS(TexturePack)
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/Resource.h"
|
||||
|
||||
#include "tasks/Task.h"
|
||||
|
||||
/** Very simple task that just loads a folder's contents directly.
|
||||
*/
|
||||
class BasicFolderLoadTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QMap<QString, Resource::Ptr> resources;
|
||||
};
|
||||
using ResultPtr = std::shared_ptr<Result>;
|
||||
|
||||
[[nodiscard]] ResultPtr result() const { return m_result; }
|
||||
|
||||
public:
|
||||
BasicFolderLoadTask(QDir dir) : Task(false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread())
|
||||
{
|
||||
m_create_func = [](QFileInfo const& entry) -> Resource::Ptr { return makeShared<Resource>(entry); };
|
||||
}
|
||||
BasicFolderLoadTask(QDir dir, std::function<Resource::Ptr(QFileInfo const&)> create_function)
|
||||
: Task(false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread())
|
||||
{}
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override
|
||||
{
|
||||
m_aborted.store(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void executeTask() override
|
||||
{
|
||||
if (thread() != m_thread_to_spawn_into)
|
||||
connect(this, &Task::finished, this->thread(), &QThread::quit);
|
||||
|
||||
m_dir.refresh();
|
||||
for (auto entry : m_dir.entryInfoList()) {
|
||||
auto filePath = entry.absoluteFilePath();
|
||||
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||
continue;
|
||||
}
|
||||
auto newFilePath = FS::getUniqueResourceName(filePath);
|
||||
if (newFilePath != filePath) {
|
||||
FS::move(filePath, newFilePath);
|
||||
entry = QFileInfo(newFilePath);
|
||||
}
|
||||
auto resource = m_create_func(entry);
|
||||
resource->moveToThread(m_thread_to_spawn_into);
|
||||
m_result->resources.insert(resource->internal_id(), resource);
|
||||
}
|
||||
|
||||
if (m_aborted)
|
||||
emit finished();
|
||||
else
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
private:
|
||||
QDir m_dir;
|
||||
ResultPtr m_result;
|
||||
|
||||
std::atomic<bool> m_aborted = false;
|
||||
|
||||
std::function<Resource::Ptr(QFileInfo const&)> m_create_func;
|
||||
|
||||
/** This is the thread in which we should put new mod objects */
|
||||
QThread* m_thread_to_spawn_into;
|
||||
};
|
||||
|
|
@ -27,7 +27,7 @@ namespace ModUtils {
|
|||
// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
|
||||
ModDetails ReadMCModInfo(QByteArray contents)
|
||||
{
|
||||
auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails {
|
||||
auto getInfoFromArray = [](QJsonArray arr) -> ModDetails {
|
||||
if (!arr.at(0).isObject()) {
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,11 +47,6 @@ class LocalModParseTask : public Task {
|
|||
|
||||
[[nodiscard]] int token() const { return m_token; }
|
||||
|
||||
private:
|
||||
void processAsZip();
|
||||
void processAsFolder();
|
||||
void processAsLitemod();
|
||||
|
||||
private:
|
||||
int m_token;
|
||||
ResourceType m_type;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "LocalModUpdateTask.h"
|
||||
#include "LocalResourceUpdateTask.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/mod/MetadataHandler.h"
|
||||
|
|
@ -26,12 +26,12 @@
|
|||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version)
|
||||
: m_index_dir(index_dir), m_mod(mod), m_mod_version(mod_version)
|
||||
LocalResourceUpdateTask::LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version)
|
||||
: m_index_dir(index_dir), m_project(project), m_version(version)
|
||||
{
|
||||
// Ensure a '.index' folder exists in the mods folder, and create it if it does not
|
||||
if (!FS::ensureFolderPathExists(index_dir.path())) {
|
||||
emitFailed(QString("Unable to create index for mod %1!").arg(m_mod.name));
|
||||
emitFailed(QString("Unable to create index directory at %1!").arg(index_dir.absolutePath()));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
|
|
@ -39,28 +39,28 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
|
|||
#endif
|
||||
}
|
||||
|
||||
void LocalModUpdateTask::executeTask()
|
||||
void LocalResourceUpdateTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Updating index for mod:\n%1").arg(m_mod.name));
|
||||
setStatus(tr("Updating index for resource:\n%1").arg(m_project.name));
|
||||
|
||||
auto old_metadata = Metadata::get(m_index_dir, m_mod.addonId);
|
||||
auto old_metadata = Metadata::get(m_index_dir, m_project.addonId);
|
||||
if (old_metadata.isValid()) {
|
||||
emit hasOldMod(old_metadata.name, old_metadata.filename);
|
||||
if (m_mod.slug.isEmpty())
|
||||
m_mod.slug = old_metadata.slug;
|
||||
emit hasOldResource(old_metadata.name, old_metadata.filename);
|
||||
if (m_project.slug.isEmpty())
|
||||
m_project.slug = old_metadata.slug;
|
||||
}
|
||||
|
||||
auto pw_mod = Metadata::create(m_index_dir, m_mod, m_mod_version);
|
||||
auto pw_mod = Metadata::create(m_index_dir, m_project, m_version);
|
||||
if (pw_mod.isValid()) {
|
||||
Metadata::update(m_index_dir, pw_mod);
|
||||
emitSucceeded();
|
||||
} else {
|
||||
qCritical() << "Tried to update an invalid mod!";
|
||||
qCritical() << "Tried to update an invalid resource!";
|
||||
emitFailed(tr("Invalid metadata"));
|
||||
}
|
||||
}
|
||||
|
||||
auto LocalModUpdateTask::abort() -> bool
|
||||
auto LocalResourceUpdateTask::abort() -> bool
|
||||
{
|
||||
emitAborted();
|
||||
return true;
|
||||
|
|
@ -23,12 +23,12 @@
|
|||
#include "modplatform/ModIndex.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class LocalModUpdateTask : public Task {
|
||||
class LocalResourceUpdateTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using Ptr = shared_qobject_ptr<LocalModUpdateTask>;
|
||||
using Ptr = shared_qobject_ptr<LocalResourceUpdateTask>;
|
||||
|
||||
explicit LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack& mod, ModPlatform::IndexedVersion& mod_version);
|
||||
explicit LocalResourceUpdateTask(QDir index_dir, ModPlatform::IndexedPack& project, ModPlatform::IndexedVersion& version);
|
||||
|
||||
auto canAbort() const -> bool override { return true; }
|
||||
auto abort() -> bool override;
|
||||
|
|
@ -38,10 +38,10 @@ class LocalModUpdateTask : public Task {
|
|||
void executeTask() override;
|
||||
|
||||
signals:
|
||||
void hasOldMod(QString name, QString filename);
|
||||
void hasOldResource(QString name, QString filename);
|
||||
|
||||
private:
|
||||
QDir m_index_dir;
|
||||
ModPlatform::IndexedPack m_mod;
|
||||
ModPlatform::IndexedVersion m_mod_version;
|
||||
ModPlatform::IndexedPack m_project;
|
||||
ModPlatform::IndexedVersion m_version;
|
||||
};
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ModFolderLoadTask.h"
|
||||
#include "ResourceFolderLoadTask.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include "FileSystem.h"
|
||||
|
|
@ -42,17 +42,22 @@
|
|||
|
||||
#include <QThread>
|
||||
|
||||
ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan)
|
||||
ResourceFolderLoadTask::ResourceFolderLoadTask(const QDir& resource_dir,
|
||||
const QDir& index_dir,
|
||||
bool is_indexed,
|
||||
bool clean_orphan,
|
||||
std::function<Resource*(const QFileInfo&)> create_function)
|
||||
: Task(false)
|
||||
, m_mods_dir(mods_dir)
|
||||
, m_resource_dir(resource_dir)
|
||||
, m_index_dir(index_dir)
|
||||
, m_is_indexed(is_indexed)
|
||||
, m_clean_orphan(clean_orphan)
|
||||
, m_create_func(create_function)
|
||||
, m_result(new Result())
|
||||
, m_thread_to_spawn_into(thread())
|
||||
{}
|
||||
|
||||
void ModFolderLoadTask::executeTask()
|
||||
void ResourceFolderLoadTask::executeTask()
|
||||
{
|
||||
if (thread() != m_thread_to_spawn_into)
|
||||
connect(this, &Task::finished, this->thread(), &QThread::quit);
|
||||
|
|
@ -63,8 +68,8 @@ void ModFolderLoadTask::executeTask()
|
|||
}
|
||||
|
||||
// Read JAR files that don't have metadata
|
||||
m_mods_dir.refresh();
|
||||
for (auto entry : m_mods_dir.entryInfoList()) {
|
||||
m_resource_dir.refresh();
|
||||
for (auto entry : m_resource_dir.entryInfoList()) {
|
||||
auto filePath = entry.absoluteFilePath();
|
||||
if (auto app = APPLICATION_DYN; app && app->checkQSavePath(filePath)) {
|
||||
continue;
|
||||
|
|
@ -74,32 +79,33 @@ void ModFolderLoadTask::executeTask()
|
|||
FS::move(filePath, newFilePath);
|
||||
entry = QFileInfo(newFilePath);
|
||||
}
|
||||
Mod* mod(new Mod(entry));
|
||||
|
||||
if (mod->enabled()) {
|
||||
if (m_result->mods.contains(mod->internal_id())) {
|
||||
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed);
|
||||
Resource* resource = m_create_func(entry);
|
||||
|
||||
if (resource->enabled()) {
|
||||
if (m_result->resources.contains(resource->internal_id())) {
|
||||
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
|
||||
// Delete the object we just created, since a valid one is already in the mods list.
|
||||
delete mod;
|
||||
delete resource;
|
||||
} else {
|
||||
m_result->mods[mod->internal_id()].reset(std::move(mod));
|
||||
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
|
||||
m_result->resources[resource->internal_id()].reset(resource);
|
||||
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
|
||||
}
|
||||
} else {
|
||||
QString chopped_id = mod->internal_id().chopped(9);
|
||||
if (m_result->mods.contains(chopped_id)) {
|
||||
m_result->mods[mod->internal_id()].reset(std::move(mod));
|
||||
QString chopped_id = resource->internal_id().chopped(9);
|
||||
if (m_result->resources.contains(chopped_id)) {
|
||||
m_result->resources[resource->internal_id()].reset(resource);
|
||||
|
||||
auto metadata = m_result->mods[chopped_id]->metadata();
|
||||
auto metadata = m_result->resources[chopped_id]->metadata();
|
||||
if (metadata) {
|
||||
mod->setMetadata(*metadata);
|
||||
resource->setMetadata(*metadata);
|
||||
|
||||
m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed);
|
||||
m_result->mods.remove(chopped_id);
|
||||
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::INSTALLED);
|
||||
m_result->resources.remove(chopped_id);
|
||||
}
|
||||
} else {
|
||||
m_result->mods[mod->internal_id()].reset(std::move(mod));
|
||||
m_result->mods[mod->internal_id()]->setStatus(ModStatus::NoMetadata);
|
||||
m_result->resources[resource->internal_id()].reset(resource);
|
||||
m_result->resources[resource->internal_id()]->setStatus(ResourceStatus::NO_METADATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,17 +113,17 @@ void ModFolderLoadTask::executeTask()
|
|||
// Remove orphan metadata to prevent issues
|
||||
// See https://github.com/PolyMC/PolyMC/issues/996
|
||||
if (m_clean_orphan) {
|
||||
QMutableMapIterator iter(m_result->mods);
|
||||
QMutableMapIterator iter(m_result->resources);
|
||||
while (iter.hasNext()) {
|
||||
auto mod = iter.next().value();
|
||||
if (mod->status() == ModStatus::NotInstalled) {
|
||||
mod->destroy(m_index_dir, false, false);
|
||||
auto resource = iter.next().value();
|
||||
if (resource->status() == ResourceStatus::NOT_INSTALLED) {
|
||||
resource->destroy(m_index_dir, false, false);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto mod : m_result->mods)
|
||||
for (auto mod : m_result->resources)
|
||||
mod->moveToThread(m_thread_to_spawn_into);
|
||||
|
||||
if (m_aborted)
|
||||
|
|
@ -126,18 +132,18 @@ void ModFolderLoadTask::executeTask()
|
|||
emitSucceeded();
|
||||
}
|
||||
|
||||
void ModFolderLoadTask::getFromMetadata()
|
||||
void ResourceFolderLoadTask::getFromMetadata()
|
||||
{
|
||||
m_index_dir.refresh();
|
||||
for (auto entry : m_index_dir.entryList(QDir::Files)) {
|
||||
auto metadata = Metadata::get(m_index_dir, entry);
|
||||
|
||||
if (!metadata.isValid()) {
|
||||
if (!metadata.isValid())
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* mod = new Mod(m_mods_dir, metadata);
|
||||
mod->setStatus(ModStatus::NotInstalled);
|
||||
m_result->mods[mod->internal_id()].reset(std::move(mod));
|
||||
auto* resource = m_create_func(QFileInfo(m_resource_dir.filePath(metadata.filename)));
|
||||
resource->setMetadata(metadata);
|
||||
resource->setStatus(ResourceStatus::NOT_INSTALLED);
|
||||
m_result->resources[resource->internal_id()].reset(resource);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,17 +44,21 @@
|
|||
#include "minecraft/mod/Mod.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class ModFolderLoadTask : public Task {
|
||||
class ResourceFolderLoadTask : public Task {
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Result {
|
||||
QMap<QString, Mod::Ptr> mods;
|
||||
QMap<QString, Resource::Ptr> resources;
|
||||
};
|
||||
using ResultPtr = std::shared_ptr<Result>;
|
||||
ResultPtr result() const { return m_result; }
|
||||
|
||||
public:
|
||||
ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false);
|
||||
ResourceFolderLoadTask(const QDir& resource_dir,
|
||||
const QDir& index_dir,
|
||||
bool is_indexed,
|
||||
bool clean_orphan,
|
||||
std::function<Resource*(const QFileInfo&)> create_function);
|
||||
|
||||
[[nodiscard]] bool canAbort() const override { return true; }
|
||||
bool abort() override
|
||||
|
|
@ -69,9 +73,10 @@ class ModFolderLoadTask : public Task {
|
|||
void getFromMetadata();
|
||||
|
||||
private:
|
||||
QDir m_mods_dir, m_index_dir;
|
||||
QDir m_resource_dir, m_index_dir;
|
||||
bool m_is_indexed;
|
||||
bool m_clean_orphan;
|
||||
std::function<Resource*(QFileInfo const&)> m_create_func;
|
||||
ResultPtr m_result;
|
||||
|
||||
std::atomic<bool> m_aborted = false;
|
||||
|
|
@ -25,7 +25,7 @@ void LibrariesTask::executeTask()
|
|||
|
||||
auto metacache = APPLICATION->metacache();
|
||||
|
||||
auto processArtifactPool = [&](const QList<LibraryPtr>& pool, QStringList& errors, const QString& localPath) {
|
||||
auto processArtifactPool = [this, inst, metacache](const QList<LibraryPtr>& pool, QStringList& errors, const QString& localPath) {
|
||||
for (auto lib : pool) {
|
||||
if (!lib) {
|
||||
emitFailed(tr("Null jar is specified in the metadata, aborting."));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/mod/tasks/GetModDependenciesTask.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
#include "modplatform/ResourceAPI.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class ResourceDownloadTask;
|
||||
|
|
@ -12,13 +13,18 @@ class CheckUpdateTask : public Task {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheckUpdateTask(QList<Mod*>& mods,
|
||||
CheckUpdateTask(QList<Resource*>& resources,
|
||||
std::list<Version>& mcVersions,
|
||||
QList<ModPlatform::ModLoaderType> loadersList,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: Task(), m_mods(mods), m_game_versions(mcVersions), m_loaders_list(loadersList), m_mods_folder(mods_folder) {};
|
||||
std::shared_ptr<ResourceFolderModel> resourceModel)
|
||||
: Task()
|
||||
, m_resources(resources)
|
||||
, m_game_versions(mcVersions)
|
||||
, m_loaders_list(std::move(loadersList))
|
||||
, m_resource_model(std::move(resourceModel))
|
||||
{}
|
||||
|
||||
struct UpdatableMod {
|
||||
struct Update {
|
||||
QString name;
|
||||
QString old_hash;
|
||||
QString old_version;
|
||||
|
|
@ -30,28 +36,28 @@ class CheckUpdateTask : public Task {
|
|||
bool enabled = true;
|
||||
|
||||
public:
|
||||
UpdatableMod(QString name,
|
||||
QString old_h,
|
||||
QString old_v,
|
||||
QString new_v,
|
||||
std::optional<ModPlatform::IndexedVersionType> new_v_type,
|
||||
QString changelog,
|
||||
ModPlatform::ResourceProvider p,
|
||||
shared_qobject_ptr<ResourceDownloadTask> t,
|
||||
bool enabled = true)
|
||||
: name(name)
|
||||
, old_hash(old_h)
|
||||
, old_version(old_v)
|
||||
, new_version(new_v)
|
||||
, new_version_type(new_v_type)
|
||||
, changelog(changelog)
|
||||
Update(QString name,
|
||||
QString old_h,
|
||||
QString old_v,
|
||||
QString new_v,
|
||||
std::optional<ModPlatform::IndexedVersionType> new_v_type,
|
||||
QString changelog,
|
||||
ModPlatform::ResourceProvider p,
|
||||
shared_qobject_ptr<ResourceDownloadTask> t,
|
||||
bool enabled = true)
|
||||
: name(std::move(name))
|
||||
, old_hash(std::move(old_h))
|
||||
, old_version(std::move(old_v))
|
||||
, new_version(std::move(new_v))
|
||||
, new_version_type(std::move(new_v_type))
|
||||
, changelog(std::move(changelog))
|
||||
, provider(p)
|
||||
, download(t)
|
||||
, download(std::move(t))
|
||||
, enabled(enabled)
|
||||
{}
|
||||
};
|
||||
|
||||
auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); }
|
||||
auto getUpdates() -> std::vector<Update>&& { return std::move(m_updates); }
|
||||
auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
|
||||
|
||||
public slots:
|
||||
|
|
@ -61,14 +67,14 @@ class CheckUpdateTask : public Task {
|
|||
void executeTask() override = 0;
|
||||
|
||||
signals:
|
||||
void checkFailed(Mod* failed, QString reason, QUrl recover_url = {});
|
||||
void checkFailed(Resource* failed, QString reason, QUrl recover_url = {});
|
||||
|
||||
protected:
|
||||
QList<Mod*>& m_mods;
|
||||
QList<Resource*>& m_resources;
|
||||
std::list<Version>& m_game_versions;
|
||||
QList<ModPlatform::ModLoaderType> m_loaders_list;
|
||||
std::shared_ptr<ModFolderModel> m_mods_folder;
|
||||
std::shared_ptr<ResourceFolderModel> m_resource_model;
|
||||
|
||||
std::vector<UpdatableMod> m_updatable;
|
||||
std::vector<Update> m_updates;
|
||||
QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include "Json.h"
|
||||
|
||||
#include "minecraft/mod/Mod.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
|
@ -18,56 +18,57 @@
|
|||
static ModrinthAPI modrinth_api;
|
||||
static FlameAPI flame_api;
|
||||
|
||||
EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
EnsureMetadataTask::EnsureMetadataTask(Resource* resource, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
: Task(), m_index_dir(dir), m_provider(prov), m_hashingTask(nullptr), m_current_task(nullptr)
|
||||
{
|
||||
auto hashTask = createNewHash(mod);
|
||||
auto hashTask = createNewHash(resource);
|
||||
if (!hashTask)
|
||||
return;
|
||||
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
|
||||
connect(hashTask.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
connect(hashTask.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
|
||||
connect(hashTask.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
|
||||
m_hashingTask = hashTask;
|
||||
}
|
||||
|
||||
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
EnsureMetadataTask::EnsureMetadataTask(QList<Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
: Task(), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
|
||||
{
|
||||
auto hashTask = makeShared<ConcurrentTask>("MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||
m_hashingTask = hashTask;
|
||||
for (auto* mod : mods) {
|
||||
auto hash_task = createNewHash(mod);
|
||||
for (auto* resource : resources) {
|
||||
auto hash_task = createNewHash(resource);
|
||||
if (!hash_task)
|
||||
continue;
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mods.insert(hash, mod); });
|
||||
connect(hash_task.get(), &Task::failed, [this, mod] { emitFail(mod, "", RemoveFromList::No); });
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_resources.insert(hash, resource); });
|
||||
connect(hash_task.get(), &Task::failed, [this, resource] { emitFail(resource, "", RemoveFromList::No); });
|
||||
hashTask->addTask(hash_task);
|
||||
}
|
||||
}
|
||||
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
: Task(), m_mods(mods), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
|
||||
|
||||
EnsureMetadataTask::EnsureMetadataTask(QHash<QString, Resource*>& resources, QDir dir, ModPlatform::ResourceProvider prov)
|
||||
: Task(), m_resources(resources), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
|
||||
{}
|
||||
|
||||
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod)
|
||||
Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Resource* resource)
|
||||
{
|
||||
if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER)
|
||||
if (!resource || !resource->valid() || resource->type() == ResourceType::FOLDER)
|
||||
return nullptr;
|
||||
|
||||
return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider);
|
||||
return Hashing::createHasher(resource->fileinfo().absoluteFilePath(), m_provider);
|
||||
}
|
||||
|
||||
QString EnsureMetadataTask::getExistingHash(Mod* mod)
|
||||
QString EnsureMetadataTask::getExistingHash(Resource* resource)
|
||||
{
|
||||
// Check for already computed hashes
|
||||
// (linear on the number of mods vs. linear on the size of the mod's JAR)
|
||||
auto it = m_mods.keyValueBegin();
|
||||
while (it != m_mods.keyValueEnd()) {
|
||||
if ((*it).second == mod)
|
||||
auto it = m_resources.keyValueBegin();
|
||||
while (it != m_resources.keyValueEnd()) {
|
||||
if ((*it).second == resource)
|
||||
break;
|
||||
it++;
|
||||
}
|
||||
|
||||
// We already have the hash computed
|
||||
if (it != m_mods.keyValueEnd()) {
|
||||
if (it != m_resources.keyValueEnd()) {
|
||||
return (*it).first;
|
||||
}
|
||||
|
||||
|
|
@ -87,25 +88,25 @@ bool EnsureMetadataTask::abort()
|
|||
|
||||
void EnsureMetadataTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Checking if mods have metadata..."));
|
||||
setStatus(tr("Checking if resources have metadata..."));
|
||||
|
||||
for (auto* mod : m_mods) {
|
||||
if (!mod->valid()) {
|
||||
qDebug() << "Mod" << mod->name() << "is invalid!";
|
||||
emitFail(mod);
|
||||
for (auto* resource : m_resources) {
|
||||
if (!resource->valid()) {
|
||||
qDebug() << "Resource" << resource->name() << "is invalid!";
|
||||
emitFail(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
// They already have the right metadata :o
|
||||
if (mod->status() != ModStatus::NoMetadata && mod->metadata() && mod->metadata()->provider == m_provider) {
|
||||
qDebug() << "Mod" << mod->name() << "already has metadata!";
|
||||
emitReady(mod);
|
||||
if (resource->status() != ResourceStatus::NO_METADATA && resource->metadata() && resource->metadata()->provider == m_provider) {
|
||||
qDebug() << "Resource" << resource->name() << "already has metadata!";
|
||||
emitReady(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Folders don't have metadata
|
||||
if (mod->type() == ResourceType::FOLDER) {
|
||||
emitReady(mod);
|
||||
if (resource->type() == ResourceType::FOLDER) {
|
||||
emitReady(resource);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,9 +122,9 @@ void EnsureMetadataTask::executeTask()
|
|||
}
|
||||
|
||||
auto invalidade_leftover = [this] {
|
||||
for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++)
|
||||
emitFail(mod.value(), mod.key(), RemoveFromList::No);
|
||||
m_mods.clear();
|
||||
for (auto resource = m_resources.constBegin(); resource != m_resources.constEnd(); resource++)
|
||||
emitFail(resource.value(), resource.key(), RemoveFromList::No);
|
||||
m_resources.clear();
|
||||
|
||||
emitSucceeded();
|
||||
};
|
||||
|
|
@ -145,7 +146,7 @@ void EnsureMetadataTask::executeTask()
|
|||
return;
|
||||
}
|
||||
|
||||
connect(project_task.get(), &Task::finished, this, [=] {
|
||||
connect(project_task.get(), &Task::finished, this, [this, invalidade_leftover, project_task] {
|
||||
invalidade_leftover();
|
||||
project_task->deleteLater();
|
||||
if (m_current_task)
|
||||
|
|
@ -157,59 +158,59 @@ void EnsureMetadataTask::executeTask()
|
|||
project_task->start();
|
||||
});
|
||||
|
||||
connect(version_task.get(), &Task::finished, [=] {
|
||||
connect(version_task.get(), &Task::finished, [this, version_task] {
|
||||
version_task->deleteLater();
|
||||
if (m_current_task)
|
||||
m_current_task.reset();
|
||||
});
|
||||
|
||||
if (m_mods.size() > 1)
|
||||
if (m_resources.size() > 1)
|
||||
setStatus(tr("Requesting metadata information from %1...").arg(ModPlatform::ProviderCapabilities::readableName(m_provider)));
|
||||
else if (!m_mods.empty())
|
||||
else if (!m_resources.empty())
|
||||
setStatus(tr("Requesting metadata information from %1 for '%2'...")
|
||||
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_mods.begin().value()->name()));
|
||||
.arg(ModPlatform::ProviderCapabilities::readableName(m_provider), m_resources.begin().value()->name()));
|
||||
|
||||
m_current_task = version_task;
|
||||
version_task->start();
|
||||
}
|
||||
|
||||
void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove)
|
||||
void EnsureMetadataTask::emitReady(Resource* resource, QString key, RemoveFromList remove)
|
||||
{
|
||||
if (!m) {
|
||||
qCritical() << "Tried to mark a null mod as ready.";
|
||||
if (!resource) {
|
||||
qCritical() << "Tried to mark a null resource as ready.";
|
||||
if (!key.isEmpty())
|
||||
m_mods.remove(key);
|
||||
m_resources.remove(key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << QString("Generated metadata for %1").arg(m->name());
|
||||
emit metadataReady(m);
|
||||
qDebug() << QString("Generated metadata for %1").arg(resource->name());
|
||||
emit metadataReady(resource);
|
||||
|
||||
if (remove == RemoveFromList::Yes) {
|
||||
if (key.isEmpty())
|
||||
key = getExistingHash(m);
|
||||
m_mods.remove(key);
|
||||
key = getExistingHash(resource);
|
||||
m_resources.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove)
|
||||
void EnsureMetadataTask::emitFail(Resource* resource, QString key, RemoveFromList remove)
|
||||
{
|
||||
if (!m) {
|
||||
qCritical() << "Tried to mark a null mod as failed.";
|
||||
if (!resource) {
|
||||
qCritical() << "Tried to mark a null resource as failed.";
|
||||
if (!key.isEmpty())
|
||||
m_mods.remove(key);
|
||||
m_resources.remove(key);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << QString("Failed to generate metadata for %1").arg(m->name());
|
||||
emit metadataFailed(m);
|
||||
qDebug() << QString("Failed to generate metadata for %1").arg(resource->name());
|
||||
emit metadataFailed(resource);
|
||||
|
||||
if (remove == RemoveFromList::Yes) {
|
||||
if (key.isEmpty())
|
||||
key = getExistingHash(m);
|
||||
m_mods.remove(key);
|
||||
key = getExistingHash(resource);
|
||||
m_resources.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
|
|||
auto hash_type = ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first();
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
auto ver_task = modrinth_api.currentVersions(m_mods.keys(), hash_type, response);
|
||||
auto ver_task = modrinth_api.currentVersions(m_resources.keys(), hash_type, response);
|
||||
|
||||
// Prevents unfortunate timings when aborting the task
|
||||
if (!ver_task)
|
||||
|
|
@ -240,20 +241,20 @@ Task::Ptr EnsureMetadataTask::modrinthVersionsTask()
|
|||
|
||||
try {
|
||||
auto entries = Json::requireObject(doc);
|
||||
for (auto& hash : m_mods.keys()) {
|
||||
auto mod = m_mods.find(hash).value();
|
||||
for (auto& hash : m_resources.keys()) {
|
||||
auto resource = m_resources.find(hash).value();
|
||||
try {
|
||||
auto entry = Json::requireObject(entries, hash);
|
||||
|
||||
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
|
||||
qDebug() << "Getting version for" << mod->name() << "from Modrinth";
|
||||
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
|
||||
qDebug() << "Getting version for" << resource->name() << "from Modrinth";
|
||||
|
||||
m_temp_versions.insert(hash, Modrinth::loadIndexedPackVersion(entry));
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
qDebug() << entries;
|
||||
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
}
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
|
|
@ -325,23 +326,23 @@ Task::Ptr EnsureMetadataTask::modrinthProjectsTask()
|
|||
|
||||
auto hash = addonIds.find(pack.addonId.toString()).value();
|
||||
|
||||
auto mod_iter = m_mods.find(hash);
|
||||
if (mod_iter == m_mods.end()) {
|
||||
auto resource_iter = m_resources.find(hash);
|
||||
if (resource_iter == m_resources.end()) {
|
||||
qWarning() << "Invalid project id from the API response.";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* mod = mod_iter.value();
|
||||
auto* resource = resource_iter.value();
|
||||
|
||||
try {
|
||||
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(mod->name()));
|
||||
setStatus(tr("Parsing API response from Modrinth for '%1'...").arg(resource->name()));
|
||||
|
||||
modrinthCallback(pack, m_temp_versions.find(hash).value(), mod);
|
||||
modrinthCallback(pack, m_temp_versions.find(hash).value(), resource);
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
qDebug() << entries;
|
||||
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -355,7 +356,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
|
|||
auto response = std::make_shared<QByteArray>();
|
||||
|
||||
QList<uint> fingerprints;
|
||||
for (auto& murmur : m_mods.keys()) {
|
||||
for (auto& murmur : m_resources.keys()) {
|
||||
fingerprints.push_back(murmur.toUInt());
|
||||
}
|
||||
|
||||
|
|
@ -395,13 +396,13 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
|
|||
}
|
||||
|
||||
auto fingerprint = QString::number(Json::ensureVariant(file_obj, "fileFingerprint").toUInt());
|
||||
auto mod = m_mods.find(fingerprint);
|
||||
if (mod == m_mods.end()) {
|
||||
auto resource = m_resources.find(fingerprint);
|
||||
if (resource == m_resources.end()) {
|
||||
qWarning() << "Invalid fingerprint from the API response.";
|
||||
continue;
|
||||
}
|
||||
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*mod)->name()));
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg((*resource)->name()));
|
||||
|
||||
m_temp_versions.insert(fingerprint, FlameMod::loadIndexedPackVersion(file_obj));
|
||||
}
|
||||
|
|
@ -418,7 +419,7 @@ Task::Ptr EnsureMetadataTask::flameVersionsTask()
|
|||
Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
||||
{
|
||||
QHash<QString, QString> addonIds;
|
||||
for (auto const& hash : m_mods.keys()) {
|
||||
for (auto const& hash : m_resources.keys()) {
|
||||
if (m_temp_versions.contains(hash)) {
|
||||
auto data = m_temp_versions.find(hash).value();
|
||||
|
||||
|
|
@ -465,20 +466,20 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
|||
|
||||
auto id = QString::number(Json::requireInteger(entry_obj, "id"));
|
||||
auto hash = addonIds.find(id).value();
|
||||
auto mod = m_mods.find(hash).value();
|
||||
auto resource = m_resources.find(hash).value();
|
||||
|
||||
try {
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(mod->name()));
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
|
||||
ModPlatform::IndexedPack pack;
|
||||
FlameMod::loadIndexedPack(pack, entry_obj);
|
||||
|
||||
flameCallback(pack, m_temp_versions.find(hash).value(), mod);
|
||||
flameCallback(pack, m_temp_versions.find(hash).value(), resource);
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
qDebug() << entries;
|
||||
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
}
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
|
|
@ -490,17 +491,17 @@ Task::Ptr EnsureMetadataTask::flameProjectsTask()
|
|||
return proj_task;
|
||||
}
|
||||
|
||||
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
|
||||
void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
|
||||
{
|
||||
// Prevent file name mismatch
|
||||
ver.fileName = mod->fileinfo().fileName();
|
||||
ver.fileName = resource->fileinfo().fileName();
|
||||
if (ver.fileName.endsWith(".disabled"))
|
||||
ver.fileName.chop(9);
|
||||
|
||||
QDir tmp_index_dir(m_index_dir);
|
||||
|
||||
{
|
||||
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
|
||||
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
|
||||
QEventLoop loop;
|
||||
|
||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||
|
|
@ -514,27 +515,27 @@ void EnsureMetadataTask::modrinthCallback(ModPlatform::IndexedPack& pack, ModPla
|
|||
auto metadata = Metadata::get(tmp_index_dir, pack.slug);
|
||||
if (!metadata.isValid()) {
|
||||
qCritical() << "Failed to generate metadata at last step!";
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
return;
|
||||
}
|
||||
|
||||
mod->setMetadata(metadata);
|
||||
resource->setMetadata(metadata);
|
||||
|
||||
emitReady(mod);
|
||||
emitReady(resource);
|
||||
}
|
||||
|
||||
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod* mod)
|
||||
void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource* resource)
|
||||
{
|
||||
try {
|
||||
// Prevent file name mismatch
|
||||
ver.fileName = mod->fileinfo().fileName();
|
||||
ver.fileName = resource->fileinfo().fileName();
|
||||
if (ver.fileName.endsWith(".disabled"))
|
||||
ver.fileName.chop(9);
|
||||
|
||||
QDir tmp_index_dir(m_index_dir);
|
||||
|
||||
{
|
||||
LocalModUpdateTask update_metadata(m_index_dir, pack, ver);
|
||||
LocalResourceUpdateTask update_metadata(m_index_dir, pack, ver);
|
||||
QEventLoop loop;
|
||||
|
||||
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
|
||||
|
|
@ -548,16 +549,16 @@ void EnsureMetadataTask::flameCallback(ModPlatform::IndexedPack& pack, ModPlatfo
|
|||
auto metadata = Metadata::get(tmp_index_dir, pack.slug);
|
||||
if (!metadata.isValid()) {
|
||||
qCritical() << "Failed to generate metadata at last step!";
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
return;
|
||||
}
|
||||
|
||||
mod->setMetadata(metadata);
|
||||
resource->setMetadata(metadata);
|
||||
|
||||
emitReady(mod);
|
||||
emitReady(resource);
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
|
||||
emitFail(mod);
|
||||
emitFail(resource);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "ModIndex.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "modplatform/helpers/HashUtils.h"
|
||||
|
||||
#include "minecraft/mod/Resource.h"
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
class Mod;
|
||||
class QDir;
|
||||
|
||||
class EnsureMetadataTask : public Task {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
EnsureMetadataTask(Mod*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
EnsureMetadataTask(QList<Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
EnsureMetadataTask(QHash<QString, Mod*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
EnsureMetadataTask(Resource*, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
EnsureMetadataTask(QList<Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
EnsureMetadataTask(QHash<QString, Resource*>&, QDir, ModPlatform::ResourceProvider = ModPlatform::ResourceProvider::MODRINTH);
|
||||
|
||||
~EnsureMetadataTask() = default;
|
||||
|
||||
|
|
@ -37,23 +38,23 @@ class EnsureMetadataTask : public Task {
|
|||
|
||||
// Helpers
|
||||
enum class RemoveFromList { Yes, No };
|
||||
void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
|
||||
void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
|
||||
void emitReady(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
|
||||
void emitFail(Resource*, QString key = {}, RemoveFromList = RemoveFromList::Yes);
|
||||
|
||||
// Hashes and stuff
|
||||
auto createNewHash(Mod*) -> Hashing::Hasher::Ptr;
|
||||
auto getExistingHash(Mod*) -> QString;
|
||||
auto createNewHash(Resource*) -> Hashing::Hasher::Ptr;
|
||||
auto getExistingHash(Resource*) -> QString;
|
||||
|
||||
private slots:
|
||||
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
|
||||
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*);
|
||||
void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
|
||||
void flameCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Resource*);
|
||||
|
||||
signals:
|
||||
void metadataReady(Mod*);
|
||||
void metadataFailed(Mod*);
|
||||
void metadataReady(Resource*);
|
||||
void metadataFailed(Resource*);
|
||||
|
||||
private:
|
||||
QHash<QString, Mod*> m_mods;
|
||||
QHash<QString, Resource*> m_resources;
|
||||
QDir m_index_dir;
|
||||
ModPlatform::ResourceProvider m_provider;
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class ResourceAPI {
|
|||
std::optional<std::list<Version>> versions;
|
||||
std::optional<QString> side;
|
||||
std::optional<QStringList> categoryIds;
|
||||
bool openSource;
|
||||
};
|
||||
struct SearchCallbacks {
|
||||
std::function<void(QJsonDocument&)> on_succeed;
|
||||
|
|
@ -86,7 +87,7 @@ class ResourceAPI {
|
|||
struct VersionSearchArgs {
|
||||
ModPlatform::IndexedPack pack;
|
||||
|
||||
std::optional<std::list<Version> > mcVersions;
|
||||
std::optional<std::list<Version>> mcVersions;
|
||||
std::optional<ModPlatform::ModLoaderTypes> loaders;
|
||||
|
||||
VersionSearchArgs(VersionSearchArgs const&) = default;
|
||||
|
|
|
|||
|
|
@ -641,22 +641,22 @@ void PackInstallTask::installConfigs()
|
|||
jobPtr->addNetAction(dl);
|
||||
archivePath = entry->getFullPath();
|
||||
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [&]() {
|
||||
connect(jobPtr.get(), &NetJob::succeeded, this, [this]() {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
extractConfigs();
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::failed, [&](QString reason) {
|
||||
connect(jobPtr.get(), &NetJob::failed, [this](QString reason) {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
connect(jobPtr.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
|
||||
abortable = true;
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(jobPtr.get(), &NetJob::stepProgress, this, &PackInstallTask::propagateStepProgress);
|
||||
connect(jobPtr.get(), &NetJob::aborted, [&] {
|
||||
connect(jobPtr.get(), &NetJob::aborted, [this] {
|
||||
abortable = false;
|
||||
jobPtr.reset();
|
||||
emitAborted();
|
||||
|
|
@ -685,8 +685,8 @@ void PackInstallTask::extractConfigs()
|
|||
m_extractFuture =
|
||||
QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
|
||||
#endif
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]() { downloadMods(); });
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]() { emitAborted(); });
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [this]() { downloadMods(); });
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [this]() { emitAborted(); });
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,30 @@ void Flame::FileResolvingTask::executeTask()
|
|||
m_task->start();
|
||||
}
|
||||
|
||||
PackedResourceType getResourceType(int classId)
|
||||
{
|
||||
switch (classId) {
|
||||
case 17: // Worlds
|
||||
return PackedResourceType::WorldSave;
|
||||
case 6: // Mods
|
||||
return PackedResourceType::Mod;
|
||||
case 12: // Resource Packs
|
||||
// return PackedResourceType::ResourcePack; // not really a resourcepack
|
||||
/* fallthrough */
|
||||
case 4546: // Customization
|
||||
// return PackedResourceType::ShaderPack; // not really a shaderPack
|
||||
/* fallthrough */
|
||||
case 4471: // Modpacks
|
||||
/* fallthrough */
|
||||
case 5: // Bukkit Plugins
|
||||
/* fallthrough */
|
||||
case 4559: // Addons
|
||||
/* fallthrough */
|
||||
default:
|
||||
return PackedResourceType::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void Flame::FileResolvingTask::netJobFinished()
|
||||
{
|
||||
setProgress(1, 3);
|
||||
|
|
@ -232,6 +256,10 @@ void Flame::FileResolvingTask::getFlameProjects()
|
|||
|
||||
setStatus(tr("Parsing API response from CurseForge for '%1'...").arg(file->version.fileName));
|
||||
FlameMod::loadIndexedPack(file->pack, entry_obj);
|
||||
file->resourceType = getResourceType(Json::requireInteger(entry_obj, "classId", "modClassId"));
|
||||
if (file->resourceType == PackedResourceType::WorldSave) {
|
||||
file->targetFolder = "saves";
|
||||
}
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
qDebug() << e.cause();
|
||||
|
|
|
|||
|
|
@ -121,59 +121,65 @@ ModPlatform::IndexedVersion FlameCheckUpdate::getFileInfo(int addonId, int fileI
|
|||
* */
|
||||
void FlameCheckUpdate::executeTask()
|
||||
{
|
||||
setStatus(tr("Preparing mods for CurseForge..."));
|
||||
setStatus(tr("Preparing resources for CurseForge..."));
|
||||
|
||||
int i = 0;
|
||||
for (auto* mod : m_mods) {
|
||||
setStatus(tr("Getting API response from CurseForge for '%1'...").arg(mod->name()));
|
||||
setProgress(i++, m_mods.size());
|
||||
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({ { mod->metadata()->project_id.toString() }, m_game_versions });
|
||||
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, mod->loaders());
|
||||
auto latest_ver = api.getLatestVersion(latest_vers, m_loaders_list, resource->metadata()->loaders);
|
||||
|
||||
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(mod->name()));
|
||||
setStatus(tr("Parsing the API response from CurseForge for '%1'...").arg(resource->name()));
|
||||
|
||||
if (!latest_ver.has_value() || !latest_ver->addonId.isValid()) {
|
||||
emit checkFailed(mod, tr("No valid version found for this mod. It's probably unavailable for the current game "
|
||||
"version / mod loader."));
|
||||
QString reason;
|
||||
if (dynamic_cast<Mod*>(resource) != nullptr)
|
||||
reason =
|
||||
tr("No valid version found for this resource. It's probably unavailable for the current game "
|
||||
"version / mod loader.");
|
||||
else
|
||||
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
||||
|
||||
emit checkFailed(resource, reason);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (latest_ver->downloadUrl.isEmpty() && latest_ver->fileId != mod->metadata()->file_id) {
|
||||
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(mod, tr("Mod has a new update available, but is not downloadable using CurseForge."), recover_url);
|
||||
emit checkFailed(resource, tr("Resource has a new update available, but is not downloadable using CurseForge."), recover_url);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fake pack with the necessary info to pass to the download task :)
|
||||
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
pack->name = mod->name();
|
||||
pack->slug = mod->metadata()->slug;
|
||||
pack->addonId = mod->metadata()->project_id;
|
||||
pack->websiteUrl = mod->homeurl();
|
||||
for (auto& author : mod->authors())
|
||||
pack->authors.append({ author });
|
||||
pack->description = mod->description();
|
||||
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() && (mod->metadata()->hash != latest_ver->hash || mod->status() == ModStatus::NotInstalled)) {
|
||||
auto old_version = mod->version();
|
||||
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
|
||||
auto current_ver = getFileInfo(latest_ver->addonId.toInt(), mod->metadata()->file_id.toInt());
|
||||
old_version = current_ver.version;
|
||||
if (!latest_ver->hash.isEmpty() &&
|
||||
(resource->metadata()->hash != latest_ver->hash || resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
||||
auto old_version = resource->metadata()->version_number;
|
||||
if (old_version.isEmpty()) {
|
||||
if (resource->status() == ResourceStatus::NOT_INSTALLED)
|
||||
old_version = tr("Not installed");
|
||||
else
|
||||
old_version = tr("Unknown");
|
||||
}
|
||||
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_mods_folder);
|
||||
m_updatable.emplace_back(pack->name, mod->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, mod->enabled());
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver.value(), m_resource_model);
|
||||
m_updates.emplace_back(pack->name, resource->metadata()->hash, old_version, latest_ver->version, latest_ver->version_type,
|
||||
api.getModFileChangelog(latest_ver->addonId.toInt(), latest_ver->fileId.toInt()),
|
||||
ModPlatform::ResourceProvider::FLAME, download_task, resource->enabled());
|
||||
}
|
||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver.value()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ class FlameCheckUpdate : public CheckUpdateTask {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FlameCheckUpdate(QList<Mod*>& mods,
|
||||
FlameCheckUpdate(QList<Resource*>& resources,
|
||||
std::list<Version>& mcVersions,
|
||||
QList<ModPlatform::ModLoaderType> loadersList,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
|
||||
std::shared_ptr<ResourceFolderModel> resourceModel)
|
||||
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
|
||||
{}
|
||||
|
||||
public slots:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
#include "FlameInstanceCreationTask.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
|
||||
#include "minecraft/mod/tasks/LocalResourceUpdateTask.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "modplatform/flame/FlameAPI.h"
|
||||
#include "modplatform/flame/FlameModIndex.h"
|
||||
|
|
@ -75,12 +75,12 @@ bool FlameCreationTask::abort()
|
|||
return false;
|
||||
|
||||
m_abort = true;
|
||||
if (m_process_update_file_info_job)
|
||||
m_process_update_file_info_job->abort();
|
||||
if (m_files_job)
|
||||
m_files_job->abort();
|
||||
if (m_mod_id_resolver)
|
||||
m_mod_id_resolver->abort();
|
||||
if (m_processUpdateFileInfoJob)
|
||||
m_processUpdateFileInfoJob->abort();
|
||||
if (m_filesJob)
|
||||
m_filesJob->abort();
|
||||
if (m_modIdResolver)
|
||||
m_modIdResolver->abort();
|
||||
|
||||
return Task::abort();
|
||||
}
|
||||
|
|
@ -232,12 +232,12 @@ bool FlameCreationTask::updateInstance()
|
|||
connect(job.get(), &Task::failed, this, [](QString reason) { qCritical() << "Failed to get files: " << reason; });
|
||||
connect(job.get(), &Task::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
m_process_update_file_info_job = job;
|
||||
m_processUpdateFileInfoJob = job;
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
m_process_update_file_info_job = nullptr;
|
||||
m_processUpdateFileInfoJob = nullptr;
|
||||
} else {
|
||||
// We don't have an old index file, so we may duplicate stuff!
|
||||
auto dialog = CustomMessageBox::selectable(m_parent, tr("No index file."),
|
||||
|
|
@ -430,26 +430,26 @@ bool FlameCreationTask::createInstance()
|
|||
}
|
||||
|
||||
// Don't add managed info to packs without an ID (most likely imported from ZIP)
|
||||
if (!m_managed_id.isEmpty())
|
||||
instance.setManagedPack("flame", m_managed_id, m_pack.name, m_managed_version_id, m_pack.version);
|
||||
if (!m_managedId.isEmpty())
|
||||
instance.setManagedPack("flame", m_managedId, m_pack.name, m_managedVersionId, m_pack.version);
|
||||
else
|
||||
instance.setManagedPack("flame", "", name(), "", "");
|
||||
|
||||
instance.setName(name());
|
||||
|
||||
m_mod_id_resolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), m_pack));
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); });
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) {
|
||||
m_mod_id_resolver.reset();
|
||||
m_modIdResolver.reset(new Flame::FileResolvingTask(APPLICATION->network(), 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();
|
||||
setError(tr("Unable to resolve mod IDs:\n") + reason);
|
||||
loop.quit();
|
||||
});
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||
connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
||||
m_mod_id_resolver->start();
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::aborted, &loop, &QEventLoop::quit);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::details, this, &FlameCreationTask::setDetails);
|
||||
m_modIdResolver->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
|
|
@ -468,14 +468,14 @@ bool FlameCreationTask::createInstance()
|
|||
|
||||
void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
{
|
||||
auto results = m_mod_id_resolver->getResults();
|
||||
auto results = m_modIdResolver->getResults();
|
||||
|
||||
// first check for blocked mods
|
||||
QList<BlockedMod> blocked_mods;
|
||||
auto anyBlocked = false;
|
||||
for (const auto& result : results.files.values()) {
|
||||
if (result.version.fileName.endsWith(".zip")) {
|
||||
m_ZIP_resources.append(std::make_pair(result.version.fileName, result.targetFolder));
|
||||
if (result.resourceType != PackedResourceType::Mod) {
|
||||
m_otherResources.append(std::make_pair(result.version.fileName, result.targetFolder));
|
||||
}
|
||||
|
||||
if (result.version.downloadUrl.isEmpty()) {
|
||||
|
|
@ -507,7 +507,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||
copyBlockedMods(blocked_mods);
|
||||
setupDownloadJob(loop);
|
||||
} else {
|
||||
m_mod_id_resolver.reset();
|
||||
m_modIdResolver.reset();
|
||||
setError("Canceled");
|
||||
loop.quit();
|
||||
}
|
||||
|
|
@ -518,8 +518,8 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
|||
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||
auto results = m_mod_id_resolver->getResults().files;
|
||||
m_filesJob.reset(new NetJob(tr("Mod Download Flame"), APPLICATION->network()));
|
||||
auto results = m_modIdResolver->getResults().files;
|
||||
|
||||
QStringList optionalFiles;
|
||||
for (auto& result : results) {
|
||||
|
|
@ -554,26 +554,26 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
|||
if (!result.version.downloadUrl.isEmpty()) {
|
||||
qDebug() << "Will download" << result.version.downloadUrl << "to" << path;
|
||||
auto dl = Net::ApiDownload::makeFile(result.version.downloadUrl, path);
|
||||
m_files_job->addNetAction(dl);
|
||||
m_filesJob->addNetAction(dl);
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_files_job.get(), &NetJob::finished, this, [this, &loop]() {
|
||||
m_files_job.reset();
|
||||
validateZIPResources(loop);
|
||||
connect(m_filesJob.get(), &NetJob::finished, this, [this, &loop]() {
|
||||
m_filesJob.reset();
|
||||
validateOtherResources(loop);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::failed, [&](QString reason) {
|
||||
m_files_job.reset();
|
||||
connect(m_filesJob.get(), &NetJob::failed, [this](QString reason) {
|
||||
m_filesJob.reset();
|
||||
setError(reason);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
|
||||
connect(m_filesJob.get(), &NetJob::progress, this, [this](qint64 current, qint64 total) {
|
||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||
connect(m_filesJob.get(), &NetJob::stepProgress, this, &FlameCreationTask::propagateStepProgress);
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
m_files_job->start();
|
||||
m_filesJob->start();
|
||||
}
|
||||
|
||||
/// @brief copy the matched blocked mods to the instance staging area
|
||||
|
|
@ -597,8 +597,14 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|||
|
||||
qDebug() << "Will try to copy" << mod.localPath << "to" << destPath;
|
||||
|
||||
if (!FS::copy(mod.localPath, destPath)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
|
||||
if (mod.move) {
|
||||
if (!FS::move(mod.localPath, destPath)) {
|
||||
qDebug() << "Move of" << mod.localPath << "to" << destPath << "Failed";
|
||||
}
|
||||
} else {
|
||||
if (!FS::copy(mod.localPath, destPath)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << destPath << "Failed";
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
|
|
@ -608,11 +614,11 @@ void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
|||
setAbortable(true);
|
||||
}
|
||||
|
||||
void FlameCreationTask::validateZIPResources(QEventLoop& loop)
|
||||
void FlameCreationTask::validateOtherResources(QEventLoop& loop)
|
||||
{
|
||||
qDebug() << "Validating whether resources stored as .zip are in the right place";
|
||||
qDebug() << "Validating whether other resources are in the right place";
|
||||
QStringList zipMods;
|
||||
for (auto [fileName, targetFolder] : m_ZIP_resources) {
|
||||
for (auto [fileName, targetFolder] : m_otherResources) {
|
||||
qDebug() << "Checking" << fileName << "...";
|
||||
auto localPath = FS::PathCombine(m_stagingPath, "minecraft", targetFolder, fileName);
|
||||
|
||||
|
|
@ -672,21 +678,23 @@ void FlameCreationTask::validateZIPResources(QEventLoop& loop)
|
|||
installWorld(worldPath);
|
||||
break;
|
||||
case PackedResourceType::UNKNOWN:
|
||||
/* fallthrough */
|
||||
default:
|
||||
qDebug() << "Can't Identify" << fileName << "at" << localPath << ", leaving it where it is.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO make this work with other sorts of resource
|
||||
auto task = makeShared<ConcurrentTask>("CreateModMetadata", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||
auto results = m_mod_id_resolver->getResults().files;
|
||||
auto results = m_modIdResolver->getResults().files;
|
||||
auto folder = FS::PathCombine(m_stagingPath, "minecraft", "mods", ".index");
|
||||
for (auto file : results) {
|
||||
if (file.targetFolder != "mods" || (file.version.fileName.endsWith(".zip") && !zipMods.contains(file.version.fileName))) {
|
||||
continue;
|
||||
}
|
||||
task->addTask(makeShared<LocalModUpdateTask>(folder, file.pack, file.version));
|
||||
task->addTask(makeShared<LocalResourceUpdateTask>(folder, file.pack, file.version));
|
||||
}
|
||||
connect(task.get(), &Task::finished, &loop, &QEventLoop::quit);
|
||||
m_process_update_file_info_job = task;
|
||||
m_processUpdateFileInfoJob = task;
|
||||
task->start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||
QString id,
|
||||
QString version_id,
|
||||
QString original_instance_id = {})
|
||||
: InstanceCreationTask(), m_parent(parent), m_managed_id(std::move(id)), m_managed_version_id(std::move(version_id))
|
||||
: InstanceCreationTask(), m_parent(parent), m_managedId(std::move(id)), m_managedVersionId(std::move(version_id))
|
||||
{
|
||||
setStagingPath(staging_path);
|
||||
setParentSettings(global_settings);
|
||||
|
|
@ -74,22 +74,22 @@ class FlameCreationTask final : public InstanceCreationTask {
|
|||
void idResolverSucceeded(QEventLoop&);
|
||||
void setupDownloadJob(QEventLoop&);
|
||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||
void validateZIPResources(QEventLoop& loop);
|
||||
void validateOtherResources(QEventLoop& loop);
|
||||
QString getVersionForLoader(QString uid, QString loaderType, QString version, QString mcVersion);
|
||||
|
||||
private:
|
||||
QWidget* m_parent = nullptr;
|
||||
|
||||
shared_qobject_ptr<Flame::FileResolvingTask> m_mod_id_resolver;
|
||||
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
|
||||
Flame::Manifest m_pack;
|
||||
|
||||
// Handle to allow aborting
|
||||
Task::Ptr m_process_update_file_info_job = nullptr;
|
||||
NetJob::Ptr m_files_job = nullptr;
|
||||
Task::Ptr m_processUpdateFileInfoJob = nullptr;
|
||||
NetJob::Ptr m_filesJob = nullptr;
|
||||
|
||||
QString m_managed_id, m_managed_version_id;
|
||||
QString m_managedId, m_managedVersionId;
|
||||
|
||||
QList<std::pair<QString, QString>> m_ZIP_resources;
|
||||
QList<std::pair<QString, QString>> m_otherResources;
|
||||
|
||||
std::optional<InstancePtr> m_instance;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include "minecraft/mod/tasks/LocalResourceParse.h"
|
||||
#include "modplatform/ModIndex.h"
|
||||
|
||||
namespace Flame {
|
||||
|
|
@ -54,6 +55,7 @@ struct File {
|
|||
|
||||
// our
|
||||
QString targetFolder = QStringLiteral("mods");
|
||||
PackedResourceType resourceType;
|
||||
};
|
||||
|
||||
struct Modloader {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ QString toHTML(QList<Mod*> mods, OptionalData extraData)
|
|||
auto meta = mod->metadata();
|
||||
auto modName = mod->name().toHtmlEscaped();
|
||||
if (extraData & Url) {
|
||||
auto url = mod->metaurl().toHtmlEscaped();
|
||||
auto url = mod->homepage().toHtmlEscaped();
|
||||
if (!url.isEmpty())
|
||||
modName = QString("<a href=\"%1\">%2</a>").arg(url, modName);
|
||||
}
|
||||
|
|
@ -65,7 +65,7 @@ QString toMarkdown(QList<Mod*> mods, OptionalData extraData)
|
|||
auto meta = mod->metadata();
|
||||
auto modName = toMarkdownEscaped(mod->name());
|
||||
if (extraData & Url) {
|
||||
auto url = mod->metaurl();
|
||||
auto url = mod->homepage();
|
||||
if (!url.isEmpty())
|
||||
modName = QString("[%1](%2)").arg(modName, url);
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ QString toPlainTXT(QList<Mod*> mods, OptionalData extraData)
|
|||
|
||||
auto line = modName;
|
||||
if (extraData & Url) {
|
||||
auto url = mod->metaurl();
|
||||
auto url = mod->homepage();
|
||||
if (!url.isEmpty())
|
||||
line += QString(" (%1)").arg(url);
|
||||
}
|
||||
|
|
@ -124,7 +124,7 @@ QString toJSON(QList<Mod*> mods, OptionalData extraData)
|
|||
QJsonObject line;
|
||||
line["name"] = modName;
|
||||
if (extraData & Url) {
|
||||
auto url = mod->metaurl();
|
||||
auto url = mod->homepage();
|
||||
if (!url.isEmpty())
|
||||
line["url"] = url;
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ QString toCSV(QList<Mod*> mods, OptionalData extraData)
|
|||
|
||||
data << modName;
|
||||
if (extraData & Url)
|
||||
data << mod->metaurl();
|
||||
data << mod->homepage();
|
||||
if (extraData & Version) {
|
||||
auto ver = mod->version();
|
||||
if (ver.isEmpty() && meta != nullptr)
|
||||
|
|
@ -203,7 +203,7 @@ QString exportToModList(QList<Mod*> mods, QString lineTemplate)
|
|||
for (auto mod : mods) {
|
||||
auto meta = mod->metadata();
|
||||
auto modName = mod->name();
|
||||
auto url = mod->metaurl();
|
||||
auto url = mod->homepage();
|
||||
auto ver = mod->version();
|
||||
if (ver.isEmpty() && meta != nullptr)
|
||||
ver = meta->version().toString();
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
|
|||
|
||||
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
|
||||
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
|
||||
QObject::connect(netJob.get(), &NetJob::succeeded, [response, callbacks, args] {
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
|
|
|
|||
|
|
@ -71,13 +71,15 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||
|
||||
static auto getSideFilters(QString side) -> const QString
|
||||
{
|
||||
if (side.isEmpty() || side == "both") {
|
||||
if (side.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (side == "both")
|
||||
return QString("\"client_side:required\"],[\"server_side:required\"");
|
||||
if (side == "client")
|
||||
return QString("\"client_side:required\",\"client_side:optional\"");
|
||||
return QString("\"client_side:required\",\"client_side:optional\"],[\"server_side:optional\",\"server_side:unsupported\"");
|
||||
if (side == "server")
|
||||
return QString("\"server_side:required\",\"server_side:optional\"");
|
||||
return QString("\"server_side:required\",\"server_side:optional\"],[\"client_side:optional\",\"client_side:unsupported\"");
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +133,8 @@ class ModrinthAPI : public NetworkResourceAPI {
|
|||
}
|
||||
if (args.categoryIds.has_value() && !args.categoryIds->empty())
|
||||
facets_list.append(QString("[%1]").arg(getCategoriesFilters(args.categoryIds.value())));
|
||||
if (args.openSource)
|
||||
facets_list.append("[\"open_source:true\"]");
|
||||
|
||||
facets_list.append(QString("[\"project_type:%1\"]").arg(resourceTypeParameter(args.type)));
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,6 @@
|
|||
|
||||
static ModrinthAPI api;
|
||||
|
||||
ModrinthCheckUpdate::ModrinthCheckUpdate(QList<Mod*>& mods,
|
||||
std::list<Version>& mcVersions,
|
||||
QList<ModPlatform::ModLoaderType> loadersList,
|
||||
std::shared_ptr<ModFolderModel> mods_folder)
|
||||
: CheckUpdateTask(mods, mcVersions, loadersList, mods_folder)
|
||||
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
|
||||
{}
|
||||
|
||||
bool ModrinthCheckUpdate::abort()
|
||||
{
|
||||
if (m_job)
|
||||
|
|
@ -37,24 +29,24 @@ bool ModrinthCheckUpdate::abort()
|
|||
* */
|
||||
void ModrinthCheckUpdate::executeTask()
|
||||
{
|
||||
setStatus(tr("Preparing mods for Modrinth..."));
|
||||
setProgress(0, 9);
|
||||
setStatus(tr("Preparing resources for Modrinth..."));
|
||||
setProgress(0, (m_loaders_list.isEmpty() ? 1 : m_loaders_list.length()) * 2 + 1);
|
||||
|
||||
auto hashing_task =
|
||||
makeShared<ConcurrentTask>("MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
|
||||
for (auto* mod : m_mods) {
|
||||
auto hash = mod->metadata()->hash;
|
||||
for (auto* resource : m_resources) {
|
||||
auto hash = resource->metadata()->hash;
|
||||
|
||||
// Sadly the API can only handle one hash type per call, se we
|
||||
// need to generate a new hash if the current one is innadequate
|
||||
// (though it will rarely happen, if at all)
|
||||
if (mod->metadata()->hash_format != m_hash_type) {
|
||||
auto hash_task = Hashing::createHasher(mod->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, mod](QString hash) { m_mappings.insert(hash, mod); });
|
||||
if (resource->metadata()->hash_format != m_hash_type) {
|
||||
auto hash_task = Hashing::createHasher(resource->fileinfo().absoluteFilePath(), ModPlatform::ResourceProvider::MODRINTH);
|
||||
connect(hash_task.get(), &Hashing::Hasher::resultsReady, [this, resource](QString hash) { m_mappings.insert(hash, resource); });
|
||||
connect(hash_task.get(), &Task::failed, [this] { failed("Failed to generate hash"); });
|
||||
hashing_task->addTask(hash_task);
|
||||
} else {
|
||||
m_mappings.insert(hash, mod);
|
||||
m_mappings.insert(hash, resource);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -63,10 +55,28 @@ void ModrinthCheckUpdate::executeTask()
|
|||
hashing_task->start();
|
||||
}
|
||||
|
||||
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response,
|
||||
ModPlatform::ModLoaderTypes loader,
|
||||
bool forceModLoaderCheck)
|
||||
void ModrinthCheckUpdate::getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader)
|
||||
{
|
||||
setStatus(tr("Waiting for the API response from Modrinth..."));
|
||||
setProgress(m_progress + 1, m_progressTotal);
|
||||
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
QStringList hashes = m_mappings.keys();
|
||||
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
|
||||
|
||||
connect(job.get(), &Task::succeeded, this, [this, response, loader] { checkVersionsResponse(response, loader); });
|
||||
|
||||
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
|
||||
|
||||
m_job = job;
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader)
|
||||
{
|
||||
setStatus(tr("Parsing the API response from Modrinth..."));
|
||||
setProgress(m_progress + 1, m_progressTotal);
|
||||
|
||||
QJsonParseError parse_error{};
|
||||
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
|
||||
if (parse_error.error != QJsonParseError::NoError) {
|
||||
|
|
@ -78,29 +88,31 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
|
|||
return;
|
||||
}
|
||||
|
||||
setStatus(tr("Parsing the API response from Modrinth..."));
|
||||
setProgress(m_next_loader_idx * 2, 9);
|
||||
|
||||
try {
|
||||
for (auto hash : m_mappings.keys()) {
|
||||
if (forceModLoaderCheck && !(m_mappings[hash]->loaders() & loader)) {
|
||||
continue;
|
||||
}
|
||||
auto iter = m_mappings.begin();
|
||||
|
||||
while (iter != m_mappings.end()) {
|
||||
const QString hash = iter.key();
|
||||
Resource* resource = iter.value();
|
||||
|
||||
auto project_obj = doc[hash].toObject();
|
||||
|
||||
// If the returned project is empty, but we have Modrinth metadata,
|
||||
// it means this specific version is not available
|
||||
if (project_obj.isEmpty()) {
|
||||
qDebug() << "Mod " << m_mappings.find(hash).value()->name() << " got an empty response." << "Hash: " << hash;
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sometimes a version may have multiple files, one with "forge" and one with "fabric",
|
||||
// so we may want to filter it
|
||||
QString loader_filter;
|
||||
for (auto flag : ModPlatform::modLoaderTypesToList(loader)) {
|
||||
loader_filter = ModPlatform::getModLoaderAsString(flag);
|
||||
break;
|
||||
if (loader.has_value()) {
|
||||
for (auto flag : ModPlatform::modLoaderTypesToList(*loader)) {
|
||||
loader_filter = ModPlatform::getModLoaderAsString(flag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Currently, we rely on a couple heuristics to determine whether an update is actually available or not:
|
||||
|
|
@ -112,106 +124,72 @@ void ModrinthCheckUpdate::checkVersionsResponse(std::shared_ptr<QByteArray> resp
|
|||
auto project_ver = Modrinth::loadIndexedPackVersion(project_obj, m_hash_type, loader_filter);
|
||||
if (project_ver.downloadUrl.isEmpty()) {
|
||||
qCritical() << "Modrinth mod without download url!" << project_ver.fileName;
|
||||
|
||||
++iter;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto mod_iter = m_mappings.find(hash);
|
||||
if (mod_iter == m_mappings.end()) {
|
||||
qCritical() << "Failed to remap mod from Modrinth!";
|
||||
continue;
|
||||
}
|
||||
auto mod = *mod_iter;
|
||||
m_mappings.remove(hash);
|
||||
|
||||
auto key = project_ver.hash;
|
||||
|
||||
// Fake pack with the necessary info to pass to the download task :)
|
||||
auto pack = std::make_shared<ModPlatform::IndexedPack>();
|
||||
pack->name = mod->name();
|
||||
pack->slug = mod->metadata()->slug;
|
||||
pack->addonId = mod->metadata()->project_id;
|
||||
pack->websiteUrl = mod->homeurl();
|
||||
for (auto& author : mod->authors())
|
||||
pack->authors.append({ author });
|
||||
pack->description = mod->description();
|
||||
pack->name = resource->name();
|
||||
pack->slug = resource->metadata()->slug;
|
||||
pack->addonId = resource->metadata()->project_id;
|
||||
pack->provider = ModPlatform::ResourceProvider::MODRINTH;
|
||||
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
|
||||
if (mod->version() == project_ver.version_number)
|
||||
continue;
|
||||
if ((project_ver.hash != hash && project_ver.is_preferred) || (resource->status() == ResourceStatus::NOT_INSTALLED)) {
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_resource_model);
|
||||
|
||||
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
|
||||
QString old_version = resource->metadata()->version_number;
|
||||
if (old_version.isEmpty()) {
|
||||
if (resource->status() == ResourceStatus::NOT_INSTALLED)
|
||||
old_version = tr("Not installed");
|
||||
else
|
||||
old_version = tr("Unknown");
|
||||
}
|
||||
|
||||
m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
|
||||
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, mod->enabled());
|
||||
m_updates.emplace_back(pack->name, hash, old_version, project_ver.version_number, project_ver.version_type,
|
||||
project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task, resource->enabled());
|
||||
}
|
||||
m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
|
||||
|
||||
iter = m_mappings.erase(iter);
|
||||
}
|
||||
} catch (Json::JsonException& e) {
|
||||
emitFailed(e.cause() + " : " + e.what());
|
||||
emitFailed(e.cause() + ": " + e.what());
|
||||
return;
|
||||
}
|
||||
checkNextLoader();
|
||||
}
|
||||
|
||||
void ModrinthCheckUpdate::getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck)
|
||||
{
|
||||
auto response = std::make_shared<QByteArray>();
|
||||
QStringList hashes;
|
||||
if (forceModLoaderCheck) {
|
||||
for (auto hash : m_mappings.keys()) {
|
||||
if (m_mappings[hash]->loaders() & loader) {
|
||||
hashes.append(hash);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hashes = m_mappings.keys();
|
||||
}
|
||||
auto job = api.latestVersions(hashes, m_hash_type, m_game_versions, loader, response);
|
||||
|
||||
connect(job.get(), &Task::succeeded, this,
|
||||
[this, response, loader, forceModLoaderCheck] { checkVersionsResponse(response, loader, forceModLoaderCheck); });
|
||||
|
||||
connect(job.get(), &Task::failed, this, &ModrinthCheckUpdate::checkNextLoader);
|
||||
|
||||
setStatus(tr("Waiting for the API response from Modrinth..."));
|
||||
setProgress(m_next_loader_idx * 2 - 1, 9);
|
||||
|
||||
m_job = job;
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ModrinthCheckUpdate::checkNextLoader()
|
||||
{
|
||||
if (m_mappings.isEmpty()) {
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
if (m_next_loader_idx < m_loaders_list.size()) {
|
||||
getUpdateModsForLoader(m_loaders_list.at(m_next_loader_idx));
|
||||
m_next_loader_idx++;
|
||||
|
||||
if (m_loaders_list.isEmpty() && m_loader_idx == 0) {
|
||||
getUpdateModsForLoader({});
|
||||
m_loader_idx++;
|
||||
return;
|
||||
}
|
||||
static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge, ModPlatform::ModLoaderType::Quilt,
|
||||
ModPlatform::ModLoaderType::Fabric };
|
||||
for (auto flag : flags) {
|
||||
if (!m_loaders_list.contains(flag)) {
|
||||
m_loaders_list.append(flag);
|
||||
m_next_loader_idx++;
|
||||
setProgress(m_next_loader_idx * 2 - 1, 9);
|
||||
for (auto m : m_mappings) {
|
||||
if (m->loaders() & flag) {
|
||||
getUpdateModsForLoader(flag, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setProgress(m_next_loader_idx * 2, 9);
|
||||
}
|
||||
|
||||
if (m_loader_idx < m_loaders_list.size()) {
|
||||
getUpdateModsForLoader(m_loaders_list.at(m_loader_idx));
|
||||
m_loader_idx++;
|
||||
return;
|
||||
}
|
||||
for (auto m : m_mappings) {
|
||||
emit checkFailed(m,
|
||||
tr("No valid version found for this mod. It's probably unavailable for the current game version / mod loader."));
|
||||
|
||||
for (auto resource : m_mappings) {
|
||||
QString reason;
|
||||
|
||||
if (dynamic_cast<Mod*>(resource) != nullptr)
|
||||
reason =
|
||||
tr("No valid version found for this resource. It's probably unavailable for the current game "
|
||||
"version / mod loader.");
|
||||
else
|
||||
reason = tr("No valid version found for this resource. It's probably unavailable for the current game version.");
|
||||
|
||||
emit checkFailed(resource, reason);
|
||||
}
|
||||
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,23 +6,26 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModrinthCheckUpdate(QList<Mod*>& mods,
|
||||
ModrinthCheckUpdate(QList<Resource*>& resources,
|
||||
std::list<Version>& mcVersions,
|
||||
QList<ModPlatform::ModLoaderType> loadersList,
|
||||
std::shared_ptr<ModFolderModel> mods_folder);
|
||||
std::shared_ptr<ResourceFolderModel> resourceModel)
|
||||
: CheckUpdateTask(resources, mcVersions, std::move(loadersList), std::move(resourceModel))
|
||||
, m_hash_type(ModPlatform::ProviderCapabilities::hashType(ModPlatform::ResourceProvider::MODRINTH).first())
|
||||
{}
|
||||
|
||||
public slots:
|
||||
bool abort() override;
|
||||
|
||||
protected slots:
|
||||
void executeTask() override;
|
||||
void getUpdateModsForLoader(ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
|
||||
void checkVersionsResponse(std::shared_ptr<QByteArray> response, ModPlatform::ModLoaderTypes loader, bool forceModLoaderCheck = false);
|
||||
void getUpdateModsForLoader(std::optional<ModPlatform::ModLoaderTypes> loader);
|
||||
void checkVersionsResponse(std::shared_ptr<QByteArray> response, std::optional<ModPlatform::ModLoaderTypes> loader);
|
||||
void checkNextLoader();
|
||||
|
||||
private:
|
||||
Task::Ptr m_job = nullptr;
|
||||
QHash<QString, Mod*> m_mappings;
|
||||
QHash<QString, Resource*> m_mappings;
|
||||
QString m_hash_type;
|
||||
int m_next_loader_idx = 0;
|
||||
int m_loader_idx = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -243,7 +243,8 @@ bool ModrinthCreationTask::createInstance()
|
|||
|
||||
auto root_modpack_path = FS::PathCombine(m_stagingPath, m_root_path);
|
||||
auto root_modpack_url = QUrl::fromLocalFile(root_modpack_path);
|
||||
QHash<QString, Mod*> mods;
|
||||
// TODO make this work with other sorts of resource
|
||||
QHash<QString, Resource*> resources;
|
||||
for (auto file : m_files) {
|
||||
auto fileName = file.path;
|
||||
fileName = FS::RemoveInvalidPathChars(fileName);
|
||||
|
|
@ -259,7 +260,7 @@ bool ModrinthCreationTask::createInstance()
|
|||
ModDetails d;
|
||||
d.mod_id = file_path;
|
||||
mod->setDetails(d);
|
||||
mods[file.hash.toHex()] = mod;
|
||||
resources[file.hash.toHex()] = mod;
|
||||
}
|
||||
if (file.downloads.empty()) {
|
||||
setError(tr("The file '%1' is missing a download link. This is invalid in the pack format.").arg(fileName));
|
||||
|
|
@ -285,13 +286,13 @@ bool ModrinthCreationTask::createInstance()
|
|||
|
||||
bool ended_well = false;
|
||||
|
||||
connect(downloadMods.get(), &NetJob::succeeded, this, [&]() { ended_well = true; });
|
||||
connect(downloadMods.get(), &NetJob::failed, [&](const QString& reason) {
|
||||
connect(downloadMods.get(), &NetJob::succeeded, this, [&ended_well]() { ended_well = true; });
|
||||
connect(downloadMods.get(), &NetJob::failed, [this, &ended_well](const QString& reason) {
|
||||
ended_well = false;
|
||||
setError(reason);
|
||||
});
|
||||
connect(downloadMods.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||
connect(downloadMods.get(), &NetJob::progress, [&](qint64 current, qint64 total) {
|
||||
connect(downloadMods.get(), &NetJob::progress, [this](qint64 current, qint64 total) {
|
||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
|
@ -304,18 +305,18 @@ bool ModrinthCreationTask::createInstance()
|
|||
loop.exec();
|
||||
|
||||
if (!ended_well) {
|
||||
for (auto m : mods) {
|
||||
delete m;
|
||||
for (auto resource : resources) {
|
||||
delete resource;
|
||||
}
|
||||
return ended_well;
|
||||
}
|
||||
|
||||
QEventLoop ensureMetaLoop;
|
||||
QDir folder = FS::PathCombine(instance.modsRoot(), ".index");
|
||||
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(mods, folder, ModPlatform::ResourceProvider::MODRINTH);
|
||||
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&]() { ended_well = true; });
|
||||
auto ensureMetadataTask = makeShared<EnsureMetadataTask>(resources, folder, ModPlatform::ResourceProvider::MODRINTH);
|
||||
connect(ensureMetadataTask.get(), &Task::succeeded, this, [&ended_well]() { ended_well = true; });
|
||||
connect(ensureMetadataTask.get(), &Task::finished, &ensureMetaLoop, &QEventLoop::quit);
|
||||
connect(ensureMetadataTask.get(), &Task::progress, [&](qint64 current, qint64 total) {
|
||||
connect(ensureMetadataTask.get(), &Task::progress, [this](qint64 current, qint64 total) {
|
||||
setDetails(tr("%1 out of %2 complete").arg(current).arg(total));
|
||||
setProgress(current, total);
|
||||
});
|
||||
|
|
@ -325,10 +326,10 @@ bool ModrinthCreationTask::createInstance()
|
|||
m_task = ensureMetadataTask;
|
||||
|
||||
ensureMetaLoop.exec();
|
||||
for (auto m : mods) {
|
||||
delete m;
|
||||
for (auto resource : resources) {
|
||||
delete resource;
|
||||
}
|
||||
mods.clear();
|
||||
resources.clear();
|
||||
|
||||
// Update information of the already installed instance, if any.
|
||||
if (m_instance && ended_well) {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ void ModrinthPackExportTask::collectHashes()
|
|||
modIter != allMods.end()) {
|
||||
const Mod* mod = *modIter;
|
||||
if (mod->metadata() != nullptr) {
|
||||
QUrl& url = mod->metadata()->url;
|
||||
const QUrl& url = mod->metadata()->url;
|
||||
// ensure the url is permitted on modrinth.com
|
||||
if (!url.isEmpty() && BuildConfig.MODRINTH_MRPACK_HOSTS.contains(url.host())) {
|
||||
qDebug() << "Resolving" << relative << "from index";
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
namespace Packwiz {
|
||||
|
||||
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
|
||||
auto getRealIndexName(const QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
|
||||
{
|
||||
QFile index_file(index_dir.absoluteFilePath(normalized_fname));
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ auto intEntry(toml::table table, QString entry_name) -> int
|
|||
return node.value_or(0);
|
||||
}
|
||||
|
||||
auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
|
||||
auto V1::createModFormat([[maybe_unused]] const QDir& index_dir,
|
||||
ModPlatform::IndexedPack& mod_pack,
|
||||
ModPlatform::IndexedVersion& mod_version) -> Mod
|
||||
{
|
||||
|
|
@ -119,10 +119,14 @@ auto V1::createModFormat([[maybe_unused]] QDir& index_dir,
|
|||
mod.mcVersions.sort();
|
||||
mod.releaseType = mod_version.version_type;
|
||||
|
||||
mod.version_number = mod_version.version_number;
|
||||
if (mod.version_number.isNull()) // on CurseForge, there is only a version name - not a version number
|
||||
mod.version_number = mod_version.version;
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
|
||||
auto V1::createModFormat(const QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod, QString slug) -> Mod
|
||||
{
|
||||
// Try getting metadata if it exists
|
||||
Mod mod{ getIndexForMod(index_dir, slug) };
|
||||
|
|
@ -134,7 +138,7 @@ auto V1::createModFormat(QDir& index_dir, [[maybe_unused]] ::Mod& internal_mod,
|
|||
return {};
|
||||
}
|
||||
|
||||
void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
||||
void V1::updateModIndex(const QDir& index_dir, Mod& mod)
|
||||
{
|
||||
if (!mod.isValid()) {
|
||||
qCritical() << QString("Tried to update metadata of an invalid mod!");
|
||||
|
|
@ -208,6 +212,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
|||
{ "x-prismlauncher-loaders", loaders },
|
||||
{ "x-prismlauncher-mc-versions", mcVersions },
|
||||
{ "x-prismlauncher-release-type", mod.releaseType.toString().toStdString() },
|
||||
{ "x-prismlauncher-version-number", mod.version_number.toStdString() },
|
||||
{ "download",
|
||||
toml::table{
|
||||
{ "mode", mod.mode.toStdString() },
|
||||
|
|
@ -225,7 +230,7 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
|
|||
index_file.close();
|
||||
}
|
||||
|
||||
void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
|
||||
void V1::deleteModIndex(const QDir& index_dir, QString& mod_slug)
|
||||
{
|
||||
auto normalized_fname = indexFileName(mod_slug);
|
||||
auto real_fname = getRealIndexName(index_dir, normalized_fname);
|
||||
|
|
@ -244,7 +249,7 @@ void V1::deleteModIndex(QDir& index_dir, QString& mod_slug)
|
|||
}
|
||||
}
|
||||
|
||||
void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
|
||||
void V1::deleteModIndex(const QDir& index_dir, QVariant& mod_id)
|
||||
{
|
||||
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
|
||||
auto mod = getIndexForMod(index_dir, file_name);
|
||||
|
|
@ -256,7 +261,7 @@ void V1::deleteModIndex(QDir& index_dir, QVariant& mod_id)
|
|||
}
|
||||
}
|
||||
|
||||
auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
||||
auto V1::getIndexForMod(const QDir& index_dir, QString slug) -> Mod
|
||||
{
|
||||
Mod mod;
|
||||
|
||||
|
|
@ -292,7 +297,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
|||
mod.name = stringEntry(table, "name");
|
||||
mod.filename = stringEntry(table, "filename");
|
||||
mod.side = stringToSide(stringEntry(table, "side"));
|
||||
mod.releaseType = ModPlatform::IndexedVersionType(stringEntry(table, "x-prismlauncher-release-type"));
|
||||
mod.releaseType = ModPlatform::IndexedVersionType(table["x-prismlauncher-release-type"].value_or(""));
|
||||
if (auto loaders = table["x-prismlauncher-loaders"]; loaders && loaders.is_array()) {
|
||||
for (auto&& loader : *loaders.as_array()) {
|
||||
if (loader.is_string()) {
|
||||
|
|
@ -312,6 +317,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
|||
mod.mcVersions.sort();
|
||||
}
|
||||
}
|
||||
mod.version_number = table["x-prismlauncher-version-number"].value_or("");
|
||||
|
||||
{ // [download] info
|
||||
auto download_table = table["download"].as_table();
|
||||
|
|
@ -353,7 +359,7 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
|
|||
return mod;
|
||||
}
|
||||
|
||||
auto V1::getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod
|
||||
auto V1::getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod
|
||||
{
|
||||
for (auto& file_name : index_dir.entryList(QDir::Filter::Files)) {
|
||||
auto mod = getIndexForMod(index_dir, file_name);
|
||||
|
|
|
|||
|
|
@ -32,11 +32,13 @@ class Mod;
|
|||
|
||||
namespace Packwiz {
|
||||
|
||||
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
|
||||
auto getRealIndexName(const QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
|
||||
|
||||
class V1 {
|
||||
public:
|
||||
enum class Side { ClientSide = 1 << 0, ServerSide = 1 << 1, UniversalSide = ClientSide | ServerSide };
|
||||
|
||||
// can also represent other resources beside loader mods - but this is what packwiz calls it
|
||||
struct Mod {
|
||||
QString slug{};
|
||||
QString name{};
|
||||
|
|
@ -56,6 +58,7 @@ class V1 {
|
|||
ModPlatform::ResourceProvider provider{};
|
||||
QVariant file_id{};
|
||||
QVariant project_id{};
|
||||
QString version_number{};
|
||||
|
||||
public:
|
||||
// This is a totally heuristic, but should work for now.
|
||||
|
|
@ -70,33 +73,33 @@ class V1 {
|
|||
/* Generates the object representing the information in a mod.pw.toml file via
|
||||
* its common representation in the launcher, when downloading mods.
|
||||
* */
|
||||
static auto createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
|
||||
static auto createModFormat(const QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod;
|
||||
/* Generates the object representing the information in a mod.pw.toml file via
|
||||
* its common representation in the launcher, plus a necessary slug.
|
||||
* */
|
||||
static auto createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
|
||||
static auto createModFormat(const QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod;
|
||||
|
||||
/* Updates the mod index for the provided mod.
|
||||
* This creates a new index if one does not exist already
|
||||
* TODO: Ask the user if they want to override, and delete the old mod's files, or keep the old one.
|
||||
* */
|
||||
static void updateModIndex(QDir& index_dir, Mod& mod);
|
||||
static void updateModIndex(const QDir& index_dir, Mod& mod);
|
||||
|
||||
/* Deletes the metadata for the mod with the given slug. If the metadata doesn't exist, it does nothing. */
|
||||
static void deleteModIndex(QDir& index_dir, QString& mod_slug);
|
||||
static void deleteModIndex(const QDir& index_dir, QString& mod_slug);
|
||||
|
||||
/* Deletes the metadata for the mod with the given id. If the metadata doesn't exist, it does nothing. */
|
||||
static void deleteModIndex(QDir& index_dir, QVariant& mod_id);
|
||||
static void deleteModIndex(const QDir& index_dir, QVariant& mod_id);
|
||||
|
||||
/* Gets the metadata for a mod with a particular file name.
|
||||
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
|
||||
* */
|
||||
static auto getIndexForMod(QDir& index_dir, QString slug) -> Mod;
|
||||
static auto getIndexForMod(const QDir& index_dir, QString slug) -> Mod;
|
||||
|
||||
/* Gets the metadata for a mod with a particular id.
|
||||
* If the mod doesn't have a metadata, it simply returns an empty Mod object.
|
||||
* */
|
||||
static auto getIndexForMod(QDir& index_dir, QVariant& mod_id) -> Mod;
|
||||
static auto getIndexForMod(const QDir& index_dir, QVariant& mod_id) -> Mod;
|
||||
|
||||
static auto sideToString(Side side) -> QString;
|
||||
static auto stringToSide(QString side) -> Side;
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.3 KiB |