mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-13 20:22:13 +00:00
Compare commits
463 Commits
0.5.2-fina
...
feature/li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91bf81b621 | ||
|
|
9db802b468 | ||
|
|
ce12f1a734 | ||
|
|
c1953739d9 | ||
|
|
e8bf9cef24 | ||
|
|
8aa4b9dac5 | ||
|
|
4836ba22cd | ||
|
|
6c30076b6c | ||
|
|
83c48a0f1a | ||
|
|
d251097545 | ||
|
|
c35dbd972e | ||
|
|
0c5d121452 | ||
|
|
53ad5cb436 | ||
|
|
d87bd4b588 | ||
|
|
86850ef5d0 | ||
|
|
30fba4d407 | ||
|
|
932160818e | ||
|
|
59e1ed3d87 | ||
|
|
3470a3df96 | ||
|
|
61913daaf3 | ||
|
|
cb71dfa605 | ||
|
|
2be98baaba | ||
|
|
0ce637bf0e | ||
|
|
70ed30f9e6 | ||
|
|
414946cad9 | ||
|
|
1fc199697c | ||
|
|
9292d846b6 | ||
|
|
22db95ce3b | ||
|
|
597fe50d37 | ||
|
|
bf93ba02e9 | ||
|
|
07c1685ff1 | ||
|
|
a5e531d4f1 | ||
|
|
ae956d8fd6 | ||
|
|
73e487a31e | ||
|
|
0b95d2b03f | ||
|
|
9c82adaee5 | ||
|
|
0a99d037c4 | ||
|
|
6e8e4c5dfa | ||
|
|
e4599ee2d1 | ||
|
|
62c9fcdc6c | ||
|
|
c1ea42d3d9 | ||
|
|
437dec91f9 | ||
|
|
7436c94976 | ||
|
|
c08053d8b8 | ||
|
|
e71786d7b9 | ||
|
|
819503d530 | ||
|
|
d6ace374d8 | ||
|
|
6a21c043ce | ||
|
|
4474d269cc | ||
|
|
ec2732ccd1 | ||
|
|
4b7971f60f | ||
|
|
4cbd1a7692 | ||
|
|
f481940aeb | ||
|
|
037fb6fdb7 | ||
|
|
3e60e770b5 | ||
|
|
70052b180c | ||
|
|
2e58429b6a | ||
|
|
56a9b65efb | ||
|
|
4a4ba954ed | ||
|
|
14bb666a20 | ||
|
|
075e173fbd | ||
|
|
3fe9165201 | ||
|
|
13b293dd65 | ||
|
|
de568b32b8 | ||
|
|
fb29e45bd0 | ||
|
|
3018310be3 | ||
|
|
f111d1bc0c | ||
|
|
0ee915200b | ||
|
|
9eb456336d | ||
|
|
3f6aecf5a2 | ||
|
|
e8c382bede | ||
|
|
54e857a7f5 | ||
|
|
74c598d756 | ||
|
|
c214c13fb3 | ||
|
|
c4a472981f | ||
|
|
33a7cc1890 | ||
|
|
a8e77f0ecc | ||
|
|
5603133822 | ||
|
|
47b1f9a860 | ||
|
|
0f0dc30729 | ||
|
|
1648b34aed | ||
|
|
4cc7329ce3 | ||
|
|
16df8d7b88 | ||
|
|
3d630c7856 | ||
|
|
51a6883737 | ||
|
|
d6367407b0 | ||
|
|
defa911705 | ||
|
|
17e09a292d | ||
|
|
8a7f1e405f | ||
|
|
58260da861 | ||
|
|
16cc20aefd | ||
|
|
0572a1e4e6 | ||
|
|
9b74e73ad3 | ||
|
|
24fd2918c7 | ||
|
|
9eb165bfee | ||
|
|
e4ce74e622 | ||
|
|
59e2f52db7 | ||
|
|
d5037d4f79 | ||
|
|
aef0ccb1a2 | ||
|
|
d9c1fc09e7 | ||
|
|
8b56d77f5e | ||
|
|
e3ab393cec | ||
|
|
2c03c67c36 | ||
|
|
a279df8bda | ||
|
|
5f2d3f014a | ||
|
|
6aada8adf7 | ||
|
|
6cee50eac6 | ||
|
|
9cc93ae81d | ||
|
|
0c73ddee73 | ||
|
|
9965decd81 | ||
|
|
9e7cdbfe11 | ||
|
|
76d6ec91a4 | ||
|
|
7b439c85c0 | ||
|
|
12f2716f31 | ||
|
|
4169f53b19 | ||
|
|
e4c33458f2 | ||
|
|
14f85813c8 | ||
|
|
bbb3b3e6f6 | ||
|
|
03280cc62e | ||
|
|
128bce6acb | ||
|
|
8108c61745 | ||
|
|
a222c94d34 | ||
|
|
a872085f9a | ||
|
|
8516a6646e | ||
|
|
44381c09d7 | ||
|
|
bb599abf59 | ||
|
|
07f7ec8eef | ||
|
|
7fe94ca7b4 | ||
|
|
b5f636b3d5 | ||
|
|
19bb50b872 | ||
|
|
4d68c1b509 | ||
|
|
f0ff2db4e1 | ||
|
|
72c0002b45 | ||
|
|
b9fd381eee | ||
|
|
584f1e89b9 | ||
|
|
72ff342d63 | ||
|
|
6284f070c1 | ||
|
|
be9063317e | ||
|
|
8ec36e2cb9 | ||
|
|
8cefc76108 | ||
|
|
172f83c7e2 | ||
|
|
b1e0cbf852 | ||
|
|
9b7f82ff26 | ||
|
|
67cef79d81 | ||
|
|
7e1c5d439a | ||
|
|
38ed0c2a1f | ||
|
|
9b7564e967 | ||
|
|
15926b2b4a | ||
|
|
6323aae56f | ||
|
|
c9c6037f50 | ||
|
|
97b74ef56a | ||
|
|
df6e66101c | ||
|
|
bbd523acb8 | ||
|
|
67d2f283da | ||
|
|
4530d9064b | ||
|
|
3406335cd8 | ||
|
|
c9832d0d86 | ||
|
|
fa8f19b046 | ||
|
|
370bbada87 | ||
|
|
1ef416cb56 | ||
|
|
b46a34d0ae | ||
|
|
6188c577e3 | ||
|
|
40a30b67f4 | ||
|
|
12b304ea73 | ||
|
|
8e44ab2338 | ||
|
|
4c7ea0f99a | ||
|
|
a1c713811c | ||
|
|
106155dd62 | ||
|
|
303842a19e | ||
|
|
ea151ca9d4 | ||
|
|
6e69370fbf | ||
|
|
82208be49e | ||
|
|
b497aee920 | ||
|
|
0812e3a87b | ||
|
|
b8ca36372b | ||
|
|
2d295d5afb | ||
|
|
ab3fe74c97 | ||
|
|
1a43f28297 | ||
|
|
093dd22826 | ||
|
|
06ccff0f47 | ||
|
|
65bca65489 | ||
|
|
a7957f24ba | ||
|
|
2ea22d407d | ||
|
|
22b32fce12 | ||
|
|
2c219df061 | ||
|
|
604295e6d5 | ||
|
|
f259e9f727 | ||
|
|
38e669dbf5 | ||
|
|
ca11765436 | ||
|
|
354ed2e7f0 | ||
|
|
a08afb8172 | ||
|
|
2dac9d02d8 | ||
|
|
b3fb437f8e | ||
|
|
f115bdf5b8 | ||
|
|
41aef8414a | ||
|
|
83649b5d52 | ||
|
|
595d157180 | ||
|
|
3843a4ddb9 | ||
|
|
088e8e0eff | ||
|
|
418251bd86 | ||
|
|
0bcb24502e | ||
|
|
3277b820a7 | ||
|
|
d66ae206dd | ||
|
|
cd55674b36 | ||
|
|
166e5a03d6 | ||
|
|
0c2e2094ee | ||
|
|
c33b4e252f | ||
|
|
0942867ecc | ||
|
|
e8336babad | ||
|
|
d0e58acd84 | ||
|
|
360d877abf | ||
|
|
7ea1d68244 | ||
|
|
b7f28a92d4 | ||
|
|
1dbc4e16f7 | ||
|
|
0636c03d7c | ||
|
|
ee341b78ba | ||
|
|
9510a1bbf2 | ||
|
|
03a93b8e4a | ||
|
|
b08ec1e97a | ||
|
|
d7ed472e68 | ||
|
|
7cd13302c5 | ||
|
|
4340068a84 | ||
|
|
c7032ce68a | ||
|
|
58ead6a1f4 | ||
|
|
5937b1c3d4 | ||
|
|
6c30c84b11 | ||
|
|
75c0046f41 | ||
|
|
719f3e863a | ||
|
|
50ca6cbb4d | ||
|
|
257f8ca9fd | ||
|
|
642c3f1d09 | ||
|
|
c2726037ce | ||
|
|
9eb0525dab | ||
|
|
fb7897a6f4 | ||
|
|
5858483592 | ||
|
|
6fd91be137 | ||
|
|
245a19e9df | ||
|
|
16e390d2e7 | ||
|
|
daf9d0eaa7 | ||
|
|
f18afd3d1e | ||
|
|
ef2cbe16e6 | ||
|
|
57accb1cbb | ||
|
|
6d034bda82 | ||
|
|
44475350eb | ||
|
|
df1ec1f7c2 | ||
|
|
00814830c4 | ||
|
|
c2766e5c6e | ||
|
|
90a3997d2c | ||
|
|
d6fc37e486 | ||
|
|
8eb1397a8a | ||
|
|
4bae6fe491 | ||
|
|
95e6f37d39 | ||
|
|
e0bea1e46a | ||
|
|
6a462d0778 | ||
|
|
0a56b56286 | ||
|
|
30fad998a6 | ||
|
|
34de313feb | ||
|
|
85ae710d40 | ||
|
|
17c8f31a09 | ||
|
|
3470158943 | ||
|
|
b000b33661 | ||
|
|
322922e013 | ||
|
|
fede712a26 | ||
|
|
87edaa7dcd | ||
|
|
e5da2e36c7 | ||
|
|
e44a0cb944 | ||
|
|
e6d734d9ac | ||
|
|
1489720b90 | ||
|
|
b76bdf9368 | ||
|
|
7add9de1cf | ||
|
|
a6a642eb7e | ||
|
|
c78498f40c | ||
|
|
4b80d34be4 | ||
|
|
d4b82f11ca | ||
|
|
a87d96349a | ||
|
|
3eebc641f9 | ||
|
|
ab870648bd | ||
|
|
1388751fd4 | ||
|
|
eba8e61ce9 | ||
|
|
b88206907e | ||
|
|
4b90a078de | ||
|
|
d8c8a41dfa | ||
|
|
e2a4fbc589 | ||
|
|
edfca7da66 | ||
|
|
a1d501d394 | ||
|
|
ea71281629 | ||
|
|
c51512f940 | ||
|
|
9a2d203c0d | ||
|
|
79d208795c | ||
|
|
d276da1359 | ||
|
|
464bc0f770 | ||
|
|
0595a00090 | ||
|
|
1a38587877 | ||
|
|
719f112f64 | ||
|
|
2b998bb8cc | ||
|
|
eac892965e | ||
|
|
678c7ab271 | ||
|
|
f26ca143c4 | ||
|
|
a269e0c92f | ||
|
|
f8c5cee982 | ||
|
|
c82042dcfa | ||
|
|
b5b16d0972 | ||
|
|
76c7e0fe1c | ||
|
|
fce2b0ce5f | ||
|
|
e9434fce3d | ||
|
|
c707042dd7 | ||
|
|
102804ef82 | ||
|
|
ba3cbb7330 | ||
|
|
9a6c2b0e2c | ||
|
|
b2b0487600 | ||
|
|
9491396292 | ||
|
|
13628e7a82 | ||
|
|
b29382c748 | ||
|
|
4c01983f47 | ||
|
|
3fb4ce713f | ||
|
|
32a2cb5a0d | ||
|
|
a1ef043030 | ||
|
|
b61407a75d | ||
|
|
d80382180e | ||
|
|
b8adbb9b73 | ||
|
|
6381bfdb88 | ||
|
|
3ed990861a | ||
|
|
2c1ca040f8 | ||
|
|
300aa9e6d6 | ||
|
|
8227725adb | ||
|
|
53bf21ddcd | ||
|
|
59b681a174 | ||
|
|
6e25624623 | ||
|
|
60d10201e9 | ||
|
|
0cd55d943e | ||
|
|
48274e889f | ||
|
|
9d3a847555 | ||
|
|
ced67a7400 | ||
|
|
4d54d2662a | ||
|
|
140c31293c | ||
|
|
8cf88ffc58 | ||
|
|
117bfef151 | ||
|
|
632f232e36 | ||
|
|
bea1b5de5e | ||
|
|
c19f6d4dcd | ||
|
|
afb0db24a8 | ||
|
|
5ea170db78 | ||
|
|
e5b4b5d295 | ||
|
|
fbeceaa98c | ||
|
|
50697735b5 | ||
|
|
1797f45e8f | ||
|
|
8dd9987a9c | ||
|
|
259021bc98 | ||
|
|
26f7f017d4 | ||
|
|
71b129538b | ||
|
|
d6ab4b4a7f | ||
|
|
74c455ff35 | ||
|
|
36f3e24cf3 | ||
|
|
89d3a66658 | ||
|
|
9583d0d8ee | ||
|
|
2973b11d3e | ||
|
|
8a1da91219 | ||
|
|
cdc9bed83f | ||
|
|
7acf1998eb | ||
|
|
50b8412a26 | ||
|
|
2a81e21f5e | ||
|
|
3597a50854 | ||
|
|
4133247bff | ||
|
|
c329bc73c0 | ||
|
|
d432d8ecfc | ||
|
|
8bd8be95f0 | ||
|
|
572a6026b5 | ||
|
|
d70c783de8 | ||
|
|
e1a03e8724 | ||
|
|
868669a497 | ||
|
|
4cf4110d9d | ||
|
|
e029b7764e | ||
|
|
8a526fab0e | ||
|
|
8fe18cfabc | ||
|
|
0f311e12ee | ||
|
|
01058b1db1 | ||
|
|
c407004e3a | ||
|
|
3fb15e4a4d | ||
|
|
a379d43d7c | ||
|
|
3263b52e0c | ||
|
|
15c829fd3c | ||
|
|
349381cb2b | ||
|
|
4183cc203f | ||
|
|
2b3e87b7d1 | ||
|
|
e5147e6b65 | ||
|
|
9d4c188fd4 | ||
|
|
e854894a3c | ||
|
|
0ce44dbd41 | ||
|
|
35836c7709 | ||
|
|
e76e6329cd | ||
|
|
0efa714ba5 | ||
|
|
907aa36704 | ||
|
|
6a8bb3691b | ||
|
|
0132fd9929 | ||
|
|
9bde1c8512 | ||
|
|
b20688a18d | ||
|
|
21df531db1 | ||
|
|
f06ac02396 | ||
|
|
794102b32c | ||
|
|
1be99b075a | ||
|
|
e1465f4848 | ||
|
|
243f7e4fb4 | ||
|
|
edc25dab17 | ||
|
|
6dc1bc65e1 | ||
|
|
6fe9258161 | ||
|
|
4fa3e2a714 | ||
|
|
d25a7ad3a6 | ||
|
|
3f24c4cfe5 | ||
|
|
b414bbe395 | ||
|
|
8084f27ec8 | ||
|
|
249baf6fcf | ||
|
|
8ace5fa91d | ||
|
|
30863a88ab | ||
|
|
77a1d39f6b | ||
|
|
6a525db78d | ||
|
|
1d71214d4a | ||
|
|
ab5045b54c | ||
|
|
f3c46dbf11 | ||
|
|
581460dcf9 | ||
|
|
6bd2605a79 | ||
|
|
e9a6199507 | ||
|
|
60777ad8ce | ||
|
|
88041783e6 | ||
|
|
47e075babd | ||
|
|
5565a2f85e | ||
|
|
c4c8e99681 | ||
|
|
fc28aacdea | ||
|
|
07cde802e4 | ||
|
|
2aaf9827a6 | ||
|
|
910766458d | ||
|
|
be53eb66f8 | ||
|
|
db7357d008 | ||
|
|
795889d934 | ||
|
|
8e58d61150 | ||
|
|
dddc5cedf3 | ||
|
|
d864c95e2b | ||
|
|
dff307557b | ||
|
|
643d74f66c | ||
|
|
f565798650 | ||
|
|
e0596d3c86 | ||
|
|
2ac0edbbdb | ||
|
|
53188386b8 | ||
|
|
af3384c649 | ||
|
|
22735f275e | ||
|
|
5fabb4f254 | ||
|
|
6f2a87167a | ||
|
|
3aa28bd64a | ||
|
|
da4ae1bc1e | ||
|
|
77f27a628f | ||
|
|
f557c13679 | ||
|
|
2660418d58 | ||
|
|
e46aba9da5 | ||
|
|
0060b50625 | ||
|
|
40cf38bc32 | ||
|
|
f6eb8fa1e4 | ||
|
|
2980322c3b | ||
|
|
8321187a20 | ||
|
|
ab868df50e | ||
|
|
1fbe03f982 | ||
|
|
7382360771 | ||
|
|
8cbe13c656 | ||
|
|
160b5033a7 | ||
|
|
01cb5ebb35 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"project_id": "MultiMC5",
|
||||
"conduit_uri": "http://ph.multimc.org"
|
||||
"project_id": "MultiMC5",
|
||||
"conduit_uri": "http://ph.multimc.org"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
UseTab: true
|
||||
UseTab: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
||||
11
.github/ISSUE_TEMPLATE.md
vendored
11
.github/ISSUE_TEMPLATE.md
vendored
@@ -2,15 +2,16 @@
|
||||
Before submitting this issue, please make sure you have:
|
||||
|
||||
1. Filled out this form completely, the only optional field is "additional info".
|
||||
- Use as many details as possible and state the problem clearly.
|
||||
- Use as many details as possible and state the problem clearly.
|
||||
2. Proof-read your ENTIRE issue report.
|
||||
- Grammar and spelling mistakes make issue reports harder to understand.
|
||||
- Grammar and spelling mistakes make issue reports harder to understand.
|
||||
3. Made sure your problem is not caused by an issue in your own modpack.
|
||||
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
|
||||
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
|
||||
4. Given the issue a descriptive title.
|
||||
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
|
||||
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
|
||||
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
|
||||
5. Place all information below the ---- of lines.
|
||||
- It makes the issue look pretty
|
||||
- It makes the issue look pretty
|
||||
|
||||
If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
|
||||
-->
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,6 +15,8 @@ CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
/.project
|
||||
/.settings
|
||||
/.idea
|
||||
cmake-build-*/
|
||||
|
||||
# Build dirs
|
||||
build
|
||||
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
||||
[submodule "depends/libnbtplusplus"]
|
||||
path = libraries/libnbtplusplus
|
||||
url = https://github.com/MultiMC/libnbtplusplus.git
|
||||
path = libraries/libnbtplusplus
|
||||
url = https://github.com/MultiMC/libnbtplusplus.git
|
||||
[submodule "libraries/quazip"]
|
||||
path = libraries/quazip
|
||||
url = https://github.com/MultiMC/quazip.git
|
||||
path = libraries/quazip
|
||||
url = https://github.com/MultiMC/quazip.git
|
||||
|
||||
41
.travis.yml
41
.travis.yml
@@ -2,29 +2,28 @@
|
||||
language: cpp
|
||||
cache: apt
|
||||
|
||||
# Build matrix set up
|
||||
compiler:
|
||||
- gcc
|
||||
# - clang
|
||||
os:
|
||||
- linux
|
||||
# - osx
|
||||
env:
|
||||
- QT_VERSION=5.4.2
|
||||
- QT_VERSION=5.5.1
|
||||
# - QT_VERSION=5.6.2
|
||||
matrix:
|
||||
exclude:
|
||||
# only use clang on OS X
|
||||
- os: osx
|
||||
include:
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
# only use the qt available from homebrew
|
||||
- os: osx
|
||||
env: QT_VERSION=5.4.2
|
||||
- os: osx
|
||||
env: QT_VERSION=5.5.1
|
||||
# - os: osx
|
||||
# env: QT_VERSION=5.6
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.6.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
|
||||
20
BUILD.md
20
BUILD.md
@@ -32,11 +32,11 @@ git submodule update
|
||||
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
|
||||
|
||||
## Build dependencies
|
||||
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
|
||||
* Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution.
|
||||
* A C++ compiler capable of building C++11 code.
|
||||
* Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version.
|
||||
* cmake 3.1 or newer
|
||||
* zlib (for example, `zlib1g-dev`)
|
||||
* java (for example, `openjdk-8-jdk`)
|
||||
* Java JDK 8 (for example, `openjdk-8-jdk`)
|
||||
* GL headers (for example, `libgl1-mesa-dev`)
|
||||
|
||||
### Building from command line
|
||||
@@ -64,7 +64,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
1. Run the Qt installer.
|
||||
2. Choose a place to install Qt.
|
||||
3. Choose the components you want to install.
|
||||
- You need Qt 5.4.1/gcc 64-bit ticked.
|
||||
- You need Qt 5.6.x 64-bit ticked.
|
||||
- You need Tools/Qt Creator ticked.
|
||||
- Other components are selected by default, you can untick them if you don't need them.
|
||||
4. Accept the license agreements.
|
||||
@@ -77,7 +77,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
|
||||
4. Read the instructions that just popped up about a build location and choose one.
|
||||
5. You should see "Run CMake" in the window.
|
||||
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)".
|
||||
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
|
||||
- Hit the "Run CMake" button.
|
||||
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
|
||||
- Hit "Finish" if CMake ran successfully.
|
||||
@@ -91,13 +91,13 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
|
||||
|
||||
## Dependencies
|
||||
* [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
|
||||
* [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
|
||||
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
|
||||
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
|
||||
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
|
||||
* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
|
||||
* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
|
||||
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
|
||||
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
|
||||
* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
|
||||
|
||||
Put it somewhere on the `PATH`, so that it is accessible from the console.
|
||||
|
||||
@@ -107,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
1. Run the Qt installer
|
||||
2. Choose a place to install Qt (C:\Qt is the default),
|
||||
3. Choose the components you want to install
|
||||
- You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked,
|
||||
- You need Qt 5.6 (32 bit) ticked,
|
||||
- You need Tools/Qt Creator ticked,
|
||||
- Other components are selected by default, you can untick them if you don't need them.
|
||||
4. Accept the license agreements,
|
||||
@@ -132,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
|
||||
- Otherwise you can skip this step.
|
||||
6. You should see "Run CMake" in the window,
|
||||
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)",
|
||||
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)",
|
||||
- Hit the "Run CMake" button,
|
||||
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
|
||||
- Hit "Finish" if CMake ran successfully.
|
||||
|
||||
196
CMakeLists.txt
196
CMakeLists.txt
@@ -2,18 +2,18 @@ cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
|
||||
if(IS_IN_SOURCE_BUILD)
|
||||
message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!")
|
||||
message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
|
||||
cmake_policy(SET CMP0020 OLD)
|
||||
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
|
||||
cmake_policy(SET CMP0020 OLD)
|
||||
endif()
|
||||
|
||||
project(MultiMC)
|
||||
enable_testing()
|
||||
|
||||
######## Set CMake options ########
|
||||
##################################### Set CMake options #####################################
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
@@ -22,7 +22,7 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
# Output all executables and shared libs in the main build folder, not in subfolders.
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
if(UNIX)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
|
||||
endif()
|
||||
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
|
||||
|
||||
@@ -32,22 +32,61 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS " -Wall -D_GLIBCXX_USE_CXX11_ABI=0 ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
if(UNIX AND APPLE)
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
|
||||
|
||||
##################################### Set Application options #####################################
|
||||
|
||||
######## Set URLs ########
|
||||
set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(MultiMC_VERSION_MAJOR 0)
|
||||
set(MultiMC_VERSION_MINOR 6)
|
||||
set(MultiMC_VERSION_HOTFIX 5)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
|
||||
# Build platform.
|
||||
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
|
||||
|
||||
# Channel list URL
|
||||
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
|
||||
|
||||
# Notification URL
|
||||
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||
|
||||
# paste.ee API key
|
||||
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
|
||||
|
||||
# Google analytics ID
|
||||
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
|
||||
|
||||
#### Check the current Git commit and branch
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
|
||||
|
||||
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
|
||||
message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
|
||||
|
||||
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
|
||||
|
||||
#### Custom target to just print the version.
|
||||
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
# Find the required Qt parts
|
||||
find_package(Qt5Core)
|
||||
find_package(Qt5Widgets)
|
||||
find_package(Qt5Concurrent)
|
||||
find_package(Qt5Network)
|
||||
find_package(Qt5Test)
|
||||
find_package(Qt5Xml)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Concurrent REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Test REQUIRED)
|
||||
find_package(Qt5Xml REQUIRED)
|
||||
|
||||
# The Qt5 cmake files don't provide its install paths, so ask qmake.
|
||||
include(QMakeQuery)
|
||||
@@ -62,6 +101,133 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
####################################### Install layout #######################################
|
||||
|
||||
# How to install the build results
|
||||
set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
|
||||
set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
|
||||
|
||||
if(MultiMC_LAYOUT STREQUAL "auto")
|
||||
if(UNIX AND APPLE)
|
||||
set(MultiMC_LAYOUT_REAL "mac-bundle")
|
||||
elseif(UNIX)
|
||||
set(MultiMC_LAYOUT_REAL "lin-nodeps")
|
||||
elseif(WIN32)
|
||||
set(MultiMC_LAYOUT_REAL "win-bundle")
|
||||
else()
|
||||
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
|
||||
endif()
|
||||
else()
|
||||
set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
|
||||
endif()
|
||||
|
||||
if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
|
||||
set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
|
||||
set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
|
||||
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
|
||||
|
||||
# Mac bundle settings
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
|
||||
set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
|
||||
# Add the icon
|
||||
install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
|
||||
set(BINARY_DEST_DIR "bin")
|
||||
set(LIBRARY_DEST_DIR "bin")
|
||||
set(PLUGIN_DEST_DIR "plugins")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "bin/jars")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
|
||||
# Set RPATH
|
||||
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
|
||||
|
||||
# Install basic runner script
|
||||
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
|
||||
set(BINARY_DEST_DIR "bin")
|
||||
set(LIBRARY_DEST_DIR "bin")
|
||||
set(PLUGIN_DEST_DIR "plugins")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "bin/jars")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
|
||||
# Set RPATH
|
||||
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
|
||||
|
||||
# Install basic runner script
|
||||
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
|
||||
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
|
||||
set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
|
||||
set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
|
||||
set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
|
||||
set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
|
||||
|
||||
set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
|
||||
set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
|
||||
|
||||
MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
|
||||
SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
|
||||
set(BINARY_DEST_DIR ".")
|
||||
set(LIBRARY_DEST_DIR ".")
|
||||
set(PLUGIN_DEST_DIR ".")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "jars")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
else()
|
||||
message(FATAL_ERROR "No sensible install layout set.")
|
||||
endif()
|
||||
|
||||
################################ Included Libs ################################
|
||||
|
||||
include(ExternalProject)
|
||||
@@ -71,6 +237,7 @@ option(NBT_BUILD_SHARED "Build NBT shared library" ON)
|
||||
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
|
||||
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
|
||||
set(NBT_NAME MultiMC_nbt++)
|
||||
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
|
||||
add_subdirectory(libraries/libnbtplusplus)
|
||||
|
||||
add_subdirectory(libraries/ganalytics) # google analytics library
|
||||
@@ -84,11 +251,12 @@ add_subdirectory(libraries/pack200) # java pack200 compression
|
||||
add_subdirectory(libraries/rainbow) # Qt extension for colors
|
||||
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
|
||||
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
|
||||
add_subdirectory(libraries/classparser) # google analytics library
|
||||
|
||||
############################### Built Artifacts ###############################
|
||||
|
||||
add_subdirectory(api/logic)
|
||||
add_subdirectory(api/gui)
|
||||
|
||||
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(wonkoclient)
|
||||
|
||||
349
COPYING.md
349
COPYING.md
@@ -1,205 +1,254 @@
|
||||
#MultiMC
|
||||
# MultiMC
|
||||
|
||||
Copyright 2012-2017 MultiMC Contributors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
Copyright 2012-2019 MultiMC Contributors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
#MinGW runtime (Windows)
|
||||
# MinGW runtime (Windows)
|
||||
|
||||
Copyright (c) 2012 MinGW.org project
|
||||
Copyright (c) 2012 MinGW.org project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice, this permission notice and the below disclaimer
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
The above copyright notice, this permission notice and the below disclaimer
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#Qt 5
|
||||
# Qt 5
|
||||
|
||||
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
Contact: http://www.qt-project.org/legal
|
||||
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Licensed under LGPL v2.1
|
||||
Licensed under LGPL v2.1
|
||||
|
||||
#libnbt++
|
||||
# libnbt++
|
||||
|
||||
libnbt++ - A library for the Minecraft Named Binary Tag format.
|
||||
Copyright (C) 2013, 2015 ljfa-ag
|
||||
libnbt++ - A library for the Minecraft Named Binary Tag format.
|
||||
Copyright (C) 2013, 2015 ljfa-ag
|
||||
|
||||
libnbt++ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
libnbt++ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
libnbt++ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
libnbt++ is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#Group View
|
||||
# rainbow (KGuiAddons)
|
||||
|
||||
Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
|
||||
Copyright (C) 2007 John Tapsell <tapsell@kde.org>
|
||||
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
|
||||
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
|
||||
Copyright (C) 2007 Thomas Zander <zander@kde.org>
|
||||
Copyright (C) 2007 Zack Rusin <zack@kde.org>
|
||||
Copyright (C) 2015 Petr Mrazek <peterix@gmail.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Library General Public License
|
||||
along with this library; see the file COPYING.LIB. If not, write to
|
||||
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
You should have received a copy of the GNU Library General Public License
|
||||
along with this library; see the file COPYING.LIB. If not, write to
|
||||
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
#rainbow (KGuiAddons)
|
||||
# Hoedown
|
||||
|
||||
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
|
||||
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
|
||||
Copyright (C) 2007 Thomas Zander <zander@kde.org>
|
||||
Copyright (C) 2007 Zack Rusin <zack@kde.org>
|
||||
Copyright (C) 2015 Petr Mrazek <peterix@gmail.com>
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Library General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Library General Public License for more details.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
You should have received a copy of the GNU Library General Public License
|
||||
along with this library; see the file COPYING.LIB. If not, write to
|
||||
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
# Batch icon set
|
||||
|
||||
#Hoedown
|
||||
You are free to use Batch (the "icon set") or any part thereof (the "icons")
|
||||
in any personal, open-source or commercial work without obligation of payment
|
||||
(monetary or otherwise) or attribution. Do not sell the icon set, host
|
||||
the icon set or rent the icon set (either in existing or modified form).
|
||||
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
|
||||
While attribution is optional, it is always appreciated.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
Intellectual property rights are not transferred with the download of the icons.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT
|
||||
BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
|
||||
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
|
||||
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
#Batch icon set
|
||||
# Material Design Icons
|
||||
|
||||
You are free to use Batch (the "icon set") or any part thereof (the "icons")
|
||||
in any personal, open-source or commercial work without obligation of payment
|
||||
(monetary or otherwise) or attribution. Do not sell the icon set, host
|
||||
the icon set or rent the icon set (either in existing or modified form).
|
||||
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
|
||||
with Reserved Font Name Material Design Icons.
|
||||
Copyright (c) 2014, Google (http://www.google.com/design/)
|
||||
uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE
|
||||
|
||||
While attribution is optional, it is always appreciated.
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
Intellectual property rights are not transferred with the download of the icons.
|
||||
# Pack200
|
||||
|
||||
EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT
|
||||
BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
|
||||
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
|
||||
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
The GNU General Public License (GPL)
|
||||
|
||||
#Pack200
|
||||
Version 2, June 1991
|
||||
|
||||
The GNU General Public License (GPL)
|
||||
+ "CLASSPATH" EXCEPTION TO THE GPL
|
||||
|
||||
Version 2, June 1991
|
||||
Certain source files distributed by Oracle America and/or its affiliates are
|
||||
subject to the following clarification and special exception to the GPL, but
|
||||
only where Oracle has expressly included in the particular source file's header
|
||||
the words "Oracle designates this particular file as subject to the "Classpath"
|
||||
exception as provided by Oracle in the LICENSE file that accompanied this code."
|
||||
|
||||
+ "CLASSPATH" EXCEPTION TO THE GPL
|
||||
Linking this library statically or dynamically with other modules is making
|
||||
a combined work based on this library. Thus, the terms and conditions of
|
||||
the GNU General Public License cover the whole combination.
|
||||
|
||||
Certain source files distributed by Oracle America and/or its affiliates are
|
||||
subject to the following clarification and special exception to the GPL, but
|
||||
only where Oracle has expressly included in the particular source file's header
|
||||
the words "Oracle designates this particular file as subject to the "Classpath"
|
||||
exception as provided by Oracle in the LICENSE file that accompanied this code."
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent modules,
|
||||
and to copy and distribute the resulting executable under terms of your
|
||||
choice, provided that you also meet, for each linked independent module,
|
||||
the terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library. If
|
||||
you modify this library, you may extend this exception to your version of
|
||||
the library, but you are not obligated to do so. If you do not wish to do
|
||||
so, delete this exception statement from your version.
|
||||
|
||||
Linking this library statically or dynamically with other modules is making
|
||||
a combined work based on this library. Thus, the terms and conditions of
|
||||
the GNU General Public License cover the whole combination.
|
||||
# Quazip
|
||||
|
||||
As a special exception, the copyright holders of this library give you
|
||||
permission to link this library with independent modules to produce an
|
||||
executable, regardless of the license terms of these independent modules,
|
||||
and to copy and distribute the resulting executable under terms of your
|
||||
choice, provided that you also meet, for each linked independent module,
|
||||
the terms and conditions of the license of that module. An independent
|
||||
module is a module which is not derived from or based on this library. If
|
||||
you modify this library, you may extend this exception to your version of
|
||||
the library, but you are not obligated to do so. If you do not wish to do
|
||||
so, delete this exception statement from your version.
|
||||
Copyright (C) 2005-2011 Sergey A. Tachenov
|
||||
|
||||
#Quazip
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or (at
|
||||
your option) any later version.
|
||||
|
||||
Copyright (C) 2005-2011 Sergey A. Tachenov
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
General Public License for more details.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or (at
|
||||
your option) any later version.
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
||||
General Public License for more details.
|
||||
See COPYING file for the full LGPL text.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program; if not, write to the Free Software Foundation,
|
||||
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Original ZIP package is copyrighted by Gilles Vollant, see
|
||||
quazip/(un)zip.h files for details, basically it's zlib license.
|
||||
|
||||
See COPYING file for the full LGPL text.
|
||||
# xz-minidec
|
||||
|
||||
Original ZIP package is copyrighted by Gilles Vollant, see
|
||||
quazip/(un)zip.h files for details, basically it's zlib license.
|
||||
XZ decompressor
|
||||
|
||||
#xz-minidec
|
||||
Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
Igor Pavlov <http://7-zip.org/>
|
||||
|
||||
XZ decompressor
|
||||
This file has been put into the public domain.
|
||||
You can do whatever you want with this file.
|
||||
|
||||
Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
Igor Pavlov <http://7-zip.org/>
|
||||
# ColumnResizer
|
||||
|
||||
This file has been put into the public domain.
|
||||
You can do whatever you want with this file.
|
||||
Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
|
||||
|
||||
#ColumnResizer
|
||||
All rights reserved.
|
||||
|
||||
Copyright 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
License: LGPL v2.1 or later (see COPYING)
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the
|
||||
disclaimer below) provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* The name of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
|
||||
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
|
||||
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# lionshead
|
||||
|
||||
Code has been taken from https://github.com/natefoo/lionshead and loosely
|
||||
translated to C++ laced with Qt.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Nate Coraor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
12
README.md
12
README.md
@@ -1,4 +1,6 @@
|
||||

|
||||
<p align="center">
|
||||
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
|
||||
</p>
|
||||
|
||||
MultiMC 5
|
||||
=========
|
||||
@@ -11,7 +13,7 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
|
||||
|
||||
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
|
||||
|
||||
If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [](http://waffle.io/MultiMC/MultiMC5)
|
||||
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
|
||||
|
||||
### Building
|
||||
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
|
||||
@@ -25,9 +27,7 @@ We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the
|
||||
|
||||
|
||||
## Translations
|
||||
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5) or using our [translation server](http://translate.multimc.org). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
|
||||
|
||||
Currently, MultiMC is [](http://translate.multimc.org/engage/multimc/?utm_source=widget)
|
||||
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
|
||||
|
||||
## Forking/Redistributing
|
||||
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
|
||||
@@ -38,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
|
||||
|
||||
|
||||
## License
|
||||
Copyright © 2013-2017 MultiMC Contributors
|
||||
Copyright © 2013-2019 MultiMC Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
project(MultiMC_gui LANGUAGES CXX)
|
||||
|
||||
set(GUI_SOURCES
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
|
||||
# Icons
|
||||
icons/MMCIcon.h
|
||||
icons/MMCIcon.cpp
|
||||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
# Icons
|
||||
icons/MMCIcon.h
|
||||
icons/MMCIcon.cpp
|
||||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
|
||||
SkinUtils.cpp
|
||||
SkinUtils.h
|
||||
SkinUtils.cpp
|
||||
SkinUtils.h
|
||||
)
|
||||
################################ COMPILE ################################
|
||||
|
||||
@@ -21,8 +21,14 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
|
||||
generate_export_header(MultiMC_gui)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
|
||||
qt5_use_modules(MultiMC_gui Gui)
|
||||
target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# Install it
|
||||
install(
|
||||
TARGETS MultiMC_gui
|
||||
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
|
||||
)
|
||||
@@ -17,132 +17,132 @@
|
||||
template <typename T>
|
||||
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
|
||||
{
|
||||
auto pid = fork();
|
||||
if(pid_forked)
|
||||
{
|
||||
if(pid > 0)
|
||||
*pid_forked = pid;
|
||||
else
|
||||
*pid_forked = 0;
|
||||
}
|
||||
if(pid == -1)
|
||||
{
|
||||
qWarning() << "IndirectOpen failed to fork: " << errno;
|
||||
return false;
|
||||
}
|
||||
// child - do the stuff
|
||||
if(pid == 0)
|
||||
{
|
||||
// unset all this garbage so it doesn't get passed to the child process
|
||||
qunsetenv("LD_PRELOAD");
|
||||
qunsetenv("LD_LIBRARY_PATH");
|
||||
qunsetenv("LD_DEBUG");
|
||||
qunsetenv("QT_PLUGIN_PATH");
|
||||
qunsetenv("QT_FONTPATH");
|
||||
auto pid = fork();
|
||||
if(pid_forked)
|
||||
{
|
||||
if(pid > 0)
|
||||
*pid_forked = pid;
|
||||
else
|
||||
*pid_forked = 0;
|
||||
}
|
||||
if(pid == -1)
|
||||
{
|
||||
qWarning() << "IndirectOpen failed to fork: " << errno;
|
||||
return false;
|
||||
}
|
||||
// child - do the stuff
|
||||
if(pid == 0)
|
||||
{
|
||||
// unset all this garbage so it doesn't get passed to the child process
|
||||
qunsetenv("LD_PRELOAD");
|
||||
qunsetenv("LD_LIBRARY_PATH");
|
||||
qunsetenv("LD_DEBUG");
|
||||
qunsetenv("QT_PLUGIN_PATH");
|
||||
qunsetenv("QT_FONTPATH");
|
||||
|
||||
// open the URL
|
||||
auto status = callable();
|
||||
// open the URL
|
||||
auto status = callable();
|
||||
|
||||
// detach from the parent process group.
|
||||
setsid();
|
||||
// detach from the parent process group.
|
||||
setsid();
|
||||
|
||||
// die. now. do not clean up anything, it would just hang forever.
|
||||
_exit(status ? 0 : 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
//parent - assume it worked.
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0))
|
||||
{
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
return WEXITSTATUS(status) == 0;
|
||||
}
|
||||
if(WIFSIGNALED(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// die. now. do not clean up anything, it would just hang forever.
|
||||
_exit(status ? 0 : 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
//parent - assume it worked.
|
||||
int status;
|
||||
while (waitpid(pid, &status, 0))
|
||||
{
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
return WEXITSTATUS(status) == 0;
|
||||
}
|
||||
if(WIFSIGNALED(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace DesktopServices {
|
||||
bool openDirectory(const QString &path, bool ensureExists)
|
||||
{
|
||||
qDebug() << "Opening directory" << path;
|
||||
QDir parentPath;
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
{
|
||||
parentPath.mkpath(dir.absolutePath());
|
||||
}
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||
};
|
||||
qDebug() << "Opening directory" << path;
|
||||
QDir parentPath;
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
{
|
||||
parentPath.mkpath(dir.absolutePath());
|
||||
}
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &path)
|
||||
{
|
||||
qDebug() << "Opening file" << path;
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
qDebug() << "Opening file" << path;
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
|
||||
{
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
qDebug() << "Opening file" << path << "using" << application;
|
||||
#if defined(Q_OS_LINUX)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
|
||||
}, pid);
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory);
|
||||
}, pid);
|
||||
#else
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
||||
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
|
||||
{
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
qDebug() << "Running" << application << "with args" << args.join(' ');
|
||||
#if defined(Q_OS_LINUX)
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, args, workingDirectory);
|
||||
}, pid);
|
||||
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
|
||||
return IndirectOpen([&]()
|
||||
{
|
||||
return QProcess::startDetached(application, args, workingDirectory);
|
||||
}, pid);
|
||||
#else
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openUrl(const QUrl &url)
|
||||
{
|
||||
qDebug() << "Opening URL" << url.toString();
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
qDebug() << "Opening URL" << url.toString();
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -10,28 +10,28 @@
|
||||
*/
|
||||
namespace DesktopServices
|
||||
{
|
||||
/**
|
||||
* Open a file in whatever application is applicable
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
|
||||
/**
|
||||
* Open a file in whatever application is applicable
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
|
||||
|
||||
/**
|
||||
* Open a file in the specified application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
/**
|
||||
* Open a file in the specified application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
|
||||
/**
|
||||
* Run an application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
/**
|
||||
* Run an application
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
|
||||
|
||||
/**
|
||||
* Open a directory
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
|
||||
/**
|
||||
* Open a directory
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
|
||||
|
||||
/**
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
|
||||
};
|
||||
/**
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,19 +29,19 @@ namespace SkinUtils
|
||||
*/
|
||||
QPixmap getFaceFromCache(QString username, int height, int width)
|
||||
{
|
||||
QFile fskin(ENV.metacache()
|
||||
->resolveEntry("skins", username + ".png")
|
||||
->getFullPath());
|
||||
QFile fskin(ENV.metacache()
|
||||
->resolveEntry("skins", username + ".png")
|
||||
->getFullPath());
|
||||
|
||||
if (fskin.exists())
|
||||
{
|
||||
QPixmap skin(fskin.fileName());
|
||||
if(!skin.isNull())
|
||||
{
|
||||
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
|
||||
}
|
||||
}
|
||||
if (fskin.exists())
|
||||
{
|
||||
QPixmap skin(fskin.fileName());
|
||||
if(!skin.isNull())
|
||||
{
|
||||
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
return QPixmap();
|
||||
return QPixmap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,392 +27,393 @@
|
||||
|
||||
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
QSet<QString> builtinNames;
|
||||
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.baseName());
|
||||
}
|
||||
}
|
||||
for(auto & builtinName : builtinNames)
|
||||
{
|
||||
addThemeIcon(builtinName);
|
||||
}
|
||||
// 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.baseName());
|
||||
}
|
||||
}
|
||||
for(auto & builtinName : builtinNames)
|
||||
{
|
||||
addThemeIcon(builtinName);
|
||||
}
|
||||
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
is_watching = false;
|
||||
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
is_watching = false;
|
||||
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
|
||||
directoryChanged(path);
|
||||
directoryChanged(path);
|
||||
}
|
||||
|
||||
void IconList::directoryChanged(const QString &path)
|
||||
{
|
||||
QDir new_dir (path);
|
||||
if(m_dir.absolutePath() != new_dir.absolutePath())
|
||||
{
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if(is_watching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
if(!m_dir.exists())
|
||||
if(!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);
|
||||
}
|
||||
auto new_set = new_list.toSet();
|
||||
QList<QString> current_list;
|
||||
for (auto &it : icons)
|
||||
{
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
QSet<QString> current_set = current_list.toSet();
|
||||
QDir new_dir (path);
|
||||
if(m_dir.absolutePath() != new_dir.absolutePath())
|
||||
{
|
||||
m_dir.setPath(path);
|
||||
m_dir.refresh();
|
||||
if(is_watching)
|
||||
stopWatching();
|
||||
startWatching();
|
||||
}
|
||||
if(!m_dir.exists())
|
||||
if(!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);
|
||||
}
|
||||
auto new_set = new_list.toSet();
|
||||
QList<QString> current_list;
|
||||
for (auto &it : icons)
|
||||
{
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
QSet<QString> current_set = current_list.toSet();
|
||||
|
||||
QSet<QString> to_remove = current_set;
|
||||
to_remove -= new_set;
|
||||
QSet<QString> to_remove = current_set;
|
||||
to_remove -= new_set;
|
||||
|
||||
QSet<QString> to_add = new_set;
|
||||
to_add -= current_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.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(IconType::FileBased);
|
||||
if (icons[idx].type() == IconType::ToBeDeleted)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
reindex();
|
||||
endRemoveRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataChanged(index(idx), index(idx));
|
||||
}
|
||||
m_watcher->removePath(remove);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
for (auto remove : to_remove)
|
||||
{
|
||||
qDebug() << "Removing " << remove;
|
||||
QFileInfo rmfile(remove);
|
||||
QString key = rmfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(IconType::FileBased);
|
||||
if (icons[idx].type() == IconType::ToBeDeleted)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
reindex();
|
||||
endRemoveRows();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataChanged(index(idx), index(idx));
|
||||
}
|
||||
m_watcher->removePath(remove);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
for (auto add : to_add)
|
||||
{
|
||||
qDebug() << "Adding " << add;
|
||||
QFileInfo addfile(add);
|
||||
QString key = addfile.baseName();
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
|
||||
{
|
||||
m_watcher->addPath(add);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
}
|
||||
for (auto add : to_add)
|
||||
{
|
||||
qDebug() << "Adding " << add;
|
||||
QFileInfo addfile(add);
|
||||
QString key = addfile.baseName();
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
|
||||
{
|
||||
m_watcher->addPath(add);
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::fileChanged(const QString &path)
|
||||
{
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
QString key = checkfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
return;
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
return;
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
QString key = checkfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
return;
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
return;
|
||||
|
||||
icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||
dataChanged(index(idx), index(idx));
|
||||
emit iconUpdated(key);
|
||||
icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||
dataChanged(index(idx), index(idx));
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
|
||||
void IconList::SettingChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
if(setting.id() != "IconsDir")
|
||||
return;
|
||||
if(setting.id() != "IconsDir")
|
||||
return;
|
||||
|
||||
directoryChanged(value.toString());
|
||||
directoryChanged(value.toString());
|
||||
}
|
||||
|
||||
void IconList::startWatching()
|
||||
{
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching)
|
||||
{
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
}
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching)
|
||||
{
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::stopWatching()
|
||||
{
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
is_watching = false;
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
is_watching = false;
|
||||
}
|
||||
|
||||
QStringList IconList::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
return types;
|
||||
QStringList types;
|
||||
types << "text/uri-list";
|
||||
return types;
|
||||
}
|
||||
Qt::DropActions IconList::supportedDropActions() const
|
||||
{
|
||||
return Qt::CopyAction;
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
// check if the action is supported
|
||||
if (!data || !(action & supportedDropActions()))
|
||||
return false;
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
// check if the action is supported
|
||||
if (!data || !(action & supportedDropActions()))
|
||||
return false;
|
||||
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
auto urls = data->urls();
|
||||
QStringList iconFiles;
|
||||
for (auto url : urls)
|
||||
{
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
iconFiles += url.toLocalFile();
|
||||
}
|
||||
installIcons(iconFiles);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
// files dropped from outside?
|
||||
if (data->hasUrls())
|
||||
{
|
||||
auto urls = data->urls();
|
||||
QStringList iconFiles;
|
||||
for (auto url : urls)
|
||||
{
|
||||
// only local files may be dropped...
|
||||
if (!url.isLocalFile())
|
||||
continue;
|
||||
iconFiles += url.toLocalFile();
|
||||
}
|
||||
installIcons(iconFiles);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Qt::ItemFlags IconList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
else
|
||||
return Qt::ItemIsDropEnabled | defaultFlags;
|
||||
}
|
||||
|
||||
QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
int row = index.row();
|
||||
int row = index.row();
|
||||
|
||||
if (row < 0 || row >= icons.size())
|
||||
return QVariant();
|
||||
if (row < 0 || row >= icons.size())
|
||||
return QVariant();
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DecorationRole:
|
||||
return icons[row].icon();
|
||||
case Qt::DisplayRole:
|
||||
return icons[row].name();
|
||||
case Qt::UserRole:
|
||||
return icons[row].m_key;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return icons.size();
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
|
||||
continue;
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
continue;
|
||||
}
|
||||
if (!QFile::copy(file, target))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::installIcon(const QString &file, const QString &name)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if(!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
return;
|
||||
|
||||
QString target = FS::PathCombine(m_dir.dirName(), name);
|
||||
|
||||
QFile::copy(file, target);
|
||||
}
|
||||
|
||||
bool IconList::iconFileExists(const QString &key) const
|
||||
{
|
||||
auto iconEntry = icon(key);
|
||||
if(!iconEntry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return iconEntry->has(IconType::FileBased);
|
||||
auto iconEntry = icon(key);
|
||||
if(!iconEntry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return iconEntry->has(IconType::FileBased);
|
||||
}
|
||||
|
||||
const MMCIcon *IconList::icon(const QString &key) const
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return nullptr;
|
||||
return &icons[iconIdx];
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return nullptr;
|
||||
return &icons[iconIdx];
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(const QString &key)
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return false;
|
||||
auto &iconEntry = icons[iconIdx];
|
||||
if (iconEntry.has(IconType::FileBased))
|
||||
{
|
||||
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
return false;
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return false;
|
||||
auto &iconEntry = icons[iconIdx];
|
||||
if (iconEntry.has(IconType::FileBased))
|
||||
{
|
||||
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IconList::addThemeIcon(const QString& key)
|
||||
{
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
oldOne.replace(Builtin, key);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), 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;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
oldOne.replace(Builtin, key);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), 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;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
|
||||
{
|
||||
// replace the icon even? is the input valid?
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
oldOne.replace(type, icon, path);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), 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;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
// replace the icon even? is the input valid?
|
||||
QIcon icon(path);
|
||||
if (icon.isNull())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
{
|
||||
auto &oldOne = icons[*iter];
|
||||
oldOne.replace(type, icon, path);
|
||||
dataChanged(index(*iter), index(*iter));
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add a new icon
|
||||
beginInsertRows(QModelIndex(), icons.size(), 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;
|
||||
}
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::saveIcon(const QString &key, const QString &path, const char * format) const
|
||||
{
|
||||
auto icon = getIcon(key);
|
||||
auto pixmap = icon.pixmap(128, 128);
|
||||
pixmap.save(path, format);
|
||||
auto icon = getIcon(key);
|
||||
auto pixmap = icon.pixmap(128, 128);
|
||||
pixmap.save(path, format);
|
||||
}
|
||||
|
||||
|
||||
void IconList::reindex()
|
||||
{
|
||||
name_index.clear();
|
||||
int i = 0;
|
||||
for (auto &iter : icons)
|
||||
{
|
||||
name_index[iter.m_key] = i;
|
||||
i++;
|
||||
}
|
||||
name_index.clear();
|
||||
int i = 0;
|
||||
for (auto &iter : icons)
|
||||
{
|
||||
name_index[iter.m_key] = i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconList::getIcon(const QString &key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex("infinity");
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex("infinity");
|
||||
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon IconList::getBigIcon(const QString &key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
|
||||
|
||||
if (icon_index == -1)
|
||||
return QIcon();
|
||||
|
||||
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
|
||||
return QIcon(bigone);
|
||||
if (icon_index != -1)
|
||||
return icons[icon_index].icon();
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(const QString &key) const
|
||||
{
|
||||
auto iter = name_index.find(key == "default" ? "infinity" : key);
|
||||
if (iter != name_index.end())
|
||||
return *iter;
|
||||
auto iter = name_index.find(key == "default" ? "infinity" : key);
|
||||
if (iter != name_index.end())
|
||||
return *iter;
|
||||
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString IconList::getDirectory() const
|
||||
{
|
||||
return m_dir.absolutePath();
|
||||
}
|
||||
|
||||
//#include "IconList.moc"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,56 +32,57 @@ class QFileSystemWatcher;
|
||||
|
||||
class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(const QString &key) const;
|
||||
QIcon getBigIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
QIcon getIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
QString getDirectory() const;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
bool addThemeIcon(const QString &key);
|
||||
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override;
|
||||
void saveIcon(const QString &key, const QString &path, const char * format) const override;
|
||||
bool deleteIcon(const QString &key) override;
|
||||
bool iconFileExists(const QString &key) const override;
|
||||
bool addThemeIcon(const QString &key);
|
||||
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override;
|
||||
void saveIcon(const QString &key, const QString &path, const char * format) const override;
|
||||
bool deleteIcon(const QString &key) override;
|
||||
bool iconFileExists(const QString &key) const override;
|
||||
|
||||
virtual QStringList mimeTypes() const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual QStringList mimeTypes() const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
void installIcons(const QStringList &iconFiles) override;
|
||||
void installIcons(const QStringList &iconFiles) override;
|
||||
void installIcon(const QString &file, const QString &name) override;
|
||||
|
||||
const MMCIcon * icon(const QString &key) const;
|
||||
const MMCIcon * icon(const QString &key) const;
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
signals:
|
||||
void iconUpdated(QString key);
|
||||
void iconUpdated(QString key);
|
||||
|
||||
private:
|
||||
// hide copy constructor
|
||||
IconList(const IconList &) = delete;
|
||||
// hide assign op
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
void reindex();
|
||||
// hide copy constructor
|
||||
IconList(const IconList &) = delete;
|
||||
// hide assign op
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
void reindex();
|
||||
|
||||
public slots:
|
||||
void directoryChanged(const QString &path);
|
||||
void directoryChanged(const QString &path);
|
||||
|
||||
protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
std::shared_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
QDir m_dir;
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
QDir m_dir;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,86 +19,100 @@
|
||||
|
||||
IconType operator--(IconType &t, int)
|
||||
{
|
||||
IconType temp = t;
|
||||
switch (t)
|
||||
{
|
||||
case IconType::Builtin:
|
||||
t = IconType::ToBeDeleted;
|
||||
break;
|
||||
case IconType::Transient:
|
||||
t = IconType::Builtin;
|
||||
break;
|
||||
case IconType::FileBased:
|
||||
t = IconType::Transient;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
IconType temp = t;
|
||||
switch (t)
|
||||
{
|
||||
case IconType::Builtin:
|
||||
t = IconType::ToBeDeleted;
|
||||
break;
|
||||
case IconType::Transient:
|
||||
t = IconType::Builtin;
|
||||
break;
|
||||
case IconType::FileBased:
|
||||
t = IconType::Transient;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
}
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
IconType MMCIcon::type() const
|
||||
{
|
||||
return m_current_type;
|
||||
return m_current_type;
|
||||
}
|
||||
|
||||
QString MMCIcon::name() const
|
||||
{
|
||||
if (m_name.size())
|
||||
return m_name;
|
||||
return m_key;
|
||||
if (m_name.size())
|
||||
return m_name;
|
||||
return m_key;
|
||||
}
|
||||
|
||||
bool MMCIcon::has(IconType _type) const
|
||||
{
|
||||
return m_images[_type].present();
|
||||
return m_images[_type].present();
|
||||
}
|
||||
|
||||
QIcon MMCIcon::icon() const
|
||||
{
|
||||
if (m_current_type == IconType::ToBeDeleted)
|
||||
return QIcon();
|
||||
auto & icon = m_images[m_current_type].icon;
|
||||
if(!icon.isNull())
|
||||
return icon;
|
||||
// FIXME: inject this.
|
||||
return XdgIcon::fromTheme(m_images[m_current_type].key);
|
||||
if (m_current_type == IconType::ToBeDeleted)
|
||||
return QIcon();
|
||||
auto & icon = m_images[m_current_type].icon;
|
||||
if(!icon.isNull())
|
||||
return icon;
|
||||
// FIXME: inject this.
|
||||
return XdgIcon::fromTheme(m_images[m_current_type].key);
|
||||
}
|
||||
|
||||
void MMCIcon::remove(IconType rm_type)
|
||||
{
|
||||
m_images[rm_type].filename = QString();
|
||||
m_images[rm_type].icon = QIcon();
|
||||
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
|
||||
{
|
||||
if (m_images[iter].present())
|
||||
{
|
||||
m_current_type = iter;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_current_type = IconType::ToBeDeleted;
|
||||
m_images[rm_type].filename = QString();
|
||||
m_images[rm_type].icon = QIcon();
|
||||
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
|
||||
{
|
||||
if (m_images[iter].present())
|
||||
{
|
||||
m_current_type = iter;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_current_type = IconType::ToBeDeleted;
|
||||
}
|
||||
|
||||
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = icon;
|
||||
m_images[new_type].filename = path;
|
||||
m_images[new_type].key = QString();
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = icon;
|
||||
m_images[new_type].filename = path;
|
||||
m_images[new_type].key = QString();
|
||||
}
|
||||
|
||||
void MMCIcon::replace(IconType new_type, const QString& key)
|
||||
{
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = QIcon();
|
||||
m_images[new_type].filename = QString();
|
||||
m_images[new_type].key = key;
|
||||
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
|
||||
{
|
||||
m_current_type = new_type;
|
||||
}
|
||||
m_images[new_type].icon = QIcon();
|
||||
m_images[new_type].filename = QString();
|
||||
m_images[new_type].key = key;
|
||||
}
|
||||
|
||||
QString MMCIcon::getFilePath() const
|
||||
{
|
||||
if(m_current_type == IconType::ToBeDeleted){
|
||||
return QString();
|
||||
}
|
||||
return m_images[m_current_type].filename;
|
||||
}
|
||||
|
||||
|
||||
bool MMCIcon::isBuiltIn() const
|
||||
{
|
||||
return m_current_type == IconType::Builtin;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,27 +23,29 @@
|
||||
|
||||
struct MULTIMC_GUI_EXPORT MMCImage
|
||||
{
|
||||
QIcon icon;
|
||||
QString key;
|
||||
QString filename;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull() || !key.isEmpty();
|
||||
}
|
||||
QIcon icon;
|
||||
QString key;
|
||||
QString filename;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull() || !key.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct MULTIMC_GUI_EXPORT MMCIcon
|
||||
{
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
IconType m_current_type = ToBeDeleted;
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
IconType m_current_type = ToBeDeleted;
|
||||
|
||||
IconType type() const;
|
||||
QString name() const;
|
||||
bool has(IconType _type) const;
|
||||
QIcon icon() const;
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
IconType type() const;
|
||||
QString name() const;
|
||||
bool has(IconType _type) const;
|
||||
QIcon icon() const;
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
bool isBuiltIn() const;
|
||||
QString getFilePath() const;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,46 +16,46 @@
|
||||
#include <QFile>
|
||||
|
||||
#include "BaseInstaller.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
BaseInstaller::BaseInstaller()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool BaseInstaller::isApplied(OneSixInstance *on)
|
||||
bool BaseInstaller::isApplied(MinecraftInstance *on)
|
||||
{
|
||||
return QFile::exists(filename(on->instanceRoot()));
|
||||
return QFile::exists(filename(on->instanceRoot()));
|
||||
}
|
||||
|
||||
bool BaseInstaller::add(OneSixInstance *to)
|
||||
bool BaseInstaller::add(MinecraftInstance *to)
|
||||
{
|
||||
if (!patchesDir(to->instanceRoot()).exists())
|
||||
{
|
||||
QDir(to->instanceRoot()).mkdir("patches");
|
||||
}
|
||||
if (!patchesDir(to->instanceRoot()).exists())
|
||||
{
|
||||
QDir(to->instanceRoot()).mkdir("patches");
|
||||
}
|
||||
|
||||
if (isApplied(to))
|
||||
{
|
||||
if (!remove(to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isApplied(to))
|
||||
{
|
||||
if (!remove(to))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseInstaller::remove(OneSixInstance *from)
|
||||
bool BaseInstaller::remove(MinecraftInstance *from)
|
||||
{
|
||||
return QFile::remove(filename(from->instanceRoot()));
|
||||
return QFile::remove(filename(from->instanceRoot()));
|
||||
}
|
||||
|
||||
QString BaseInstaller::filename(const QString &root) const
|
||||
{
|
||||
return patchesDir(root).absoluteFilePath(id() + ".json");
|
||||
return patchesDir(root).absoluteFilePath(id() + ".json");
|
||||
}
|
||||
QDir BaseInstaller::patchesDir(const QString &root) const
|
||||
{
|
||||
return QDir(root + "/patches/");
|
||||
return QDir(root + "/patches/");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class OneSixInstance;
|
||||
class MinecraftInstance;
|
||||
class QDir;
|
||||
class QString;
|
||||
class QObject;
|
||||
@@ -30,17 +30,17 @@ typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstaller
|
||||
{
|
||||
public:
|
||||
BaseInstaller();
|
||||
virtual ~BaseInstaller(){};
|
||||
bool isApplied(OneSixInstance *on);
|
||||
BaseInstaller();
|
||||
virtual ~BaseInstaller(){};
|
||||
bool isApplied(MinecraftInstance *on);
|
||||
|
||||
virtual bool add(OneSixInstance *to);
|
||||
virtual bool remove(OneSixInstance *from);
|
||||
virtual bool add(MinecraftInstance *to);
|
||||
virtual bool remove(MinecraftInstance *from);
|
||||
|
||||
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
|
||||
protected:
|
||||
virtual QString id() const = 0;
|
||||
QString filename(const QString &root) const;
|
||||
QDir patchesDir(const QString &root) const;
|
||||
virtual QString id() const = 0;
|
||||
QString filename(const QString &root) const;
|
||||
QDir patchesDir(const QString &root) const;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,285 +23,232 @@
|
||||
#include "settings/Setting.h"
|
||||
#include "settings/OverrideSetting.h"
|
||||
|
||||
#include "minecraft/MinecraftVersionList.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Commandline.h"
|
||||
|
||||
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
|
||||
: QObject()
|
||||
: QObject()
|
||||
{
|
||||
m_settings = settings;
|
||||
m_rootDir = rootDir;
|
||||
m_settings = settings;
|
||||
m_rootDir = rootDir;
|
||||
|
||||
m_settings->registerSetting("name", "Unnamed Instance");
|
||||
m_settings->registerSetting("iconKey", "default");
|
||||
m_settings->registerSetting("notes", "");
|
||||
m_settings->registerSetting("lastLaunchTime", 0);
|
||||
m_settings->registerSetting("totalTimePlayed", 0);
|
||||
m_settings->registerSetting("name", "Unnamed Instance");
|
||||
m_settings->registerSetting("iconKey", "default");
|
||||
m_settings->registerSetting("notes", "");
|
||||
m_settings->registerSetting("lastLaunchTime", 0);
|
||||
m_settings->registerSetting("totalTimePlayed", 0);
|
||||
|
||||
// Custom Commands
|
||||
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
|
||||
// Custom Commands
|
||||
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
|
||||
|
||||
// Console
|
||||
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
|
||||
// Console
|
||||
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting);
|
||||
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
|
||||
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
|
||||
}
|
||||
|
||||
QString BaseInstance::getPreLaunchCommand()
|
||||
{
|
||||
return settings()->get("PreLaunchCommand").toString();
|
||||
return settings()->get("PreLaunchCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getWrapperCommand()
|
||||
{
|
||||
return settings()->get("WrapperCommand").toString();
|
||||
return settings()->get("WrapperCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getPostExitCommand()
|
||||
{
|
||||
return settings()->get("PostExitCommand").toString();
|
||||
return settings()->get("PostExitCommand").toString();
|
||||
}
|
||||
|
||||
int BaseInstance::getConsoleMaxLines() const
|
||||
{
|
||||
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
|
||||
bool conversionOk = false;
|
||||
int maxLines = lineSetting->get().toInt(&conversionOk);
|
||||
if(!conversionOk)
|
||||
{
|
||||
maxLines = lineSetting->defValue().toInt();
|
||||
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
|
||||
}
|
||||
return maxLines;
|
||||
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
|
||||
bool conversionOk = false;
|
||||
int maxLines = lineSetting->get().toInt(&conversionOk);
|
||||
if(!conversionOk)
|
||||
{
|
||||
maxLines = lineSetting->defValue().toInt();
|
||||
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
|
||||
}
|
||||
return maxLines;
|
||||
}
|
||||
|
||||
bool BaseInstance::shouldStopOnConsoleOverflow() const
|
||||
{
|
||||
return settings()->get("ConsoleOverflowStop").toBool();
|
||||
return settings()->get("ConsoleOverflowStop").toBool();
|
||||
}
|
||||
|
||||
void BaseInstance::iconUpdated(QString key)
|
||||
{
|
||||
if(iconKey() == key)
|
||||
{
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
if(iconKey() == key)
|
||||
{
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseInstance::invalidate()
|
||||
{
|
||||
changeStatus(Status::Gone);
|
||||
qDebug() << "Instance" << id() << "has been invalidated.";
|
||||
}
|
||||
|
||||
void BaseInstance::nuke()
|
||||
{
|
||||
changeStatus(Status::Gone);
|
||||
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
|
||||
FS::deletePath(instanceRoot());
|
||||
changeStatus(Status::Gone);
|
||||
qDebug() << "Instance" << id() << "has been invalidated.";
|
||||
}
|
||||
|
||||
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
|
||||
{
|
||||
Status status = currentStatus();
|
||||
if(status != newStatus)
|
||||
{
|
||||
m_status = newStatus;
|
||||
emit statusChanged(status, newStatus);
|
||||
}
|
||||
Status status = currentStatus();
|
||||
if(status != newStatus)
|
||||
{
|
||||
m_status = newStatus;
|
||||
emit statusChanged(status, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
BaseInstance::Status BaseInstance::currentStatus() const
|
||||
{
|
||||
return m_status;
|
||||
return m_status;
|
||||
}
|
||||
|
||||
QString BaseInstance::id() const
|
||||
{
|
||||
return QFileInfo(instanceRoot()).fileName();
|
||||
return QFileInfo(instanceRoot()).fileName();
|
||||
}
|
||||
|
||||
bool BaseInstance::isRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
void BaseInstance::setRunning(bool running)
|
||||
{
|
||||
if(running == m_isRunning)
|
||||
return;
|
||||
if(running == m_isRunning)
|
||||
return;
|
||||
|
||||
m_isRunning = running;
|
||||
m_isRunning = running;
|
||||
|
||||
if(running)
|
||||
{
|
||||
m_timeStarted = QDateTime::currentDateTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
QDateTime timeEnded = QDateTime::currentDateTime();
|
||||
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
if(running)
|
||||
{
|
||||
m_timeStarted = QDateTime::currentDateTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
QDateTime timeEnded = QDateTime::currentDateTime();
|
||||
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
emit runningStatusChanged(running);
|
||||
emit runningStatusChanged(running);
|
||||
}
|
||||
|
||||
int64_t BaseInstance::totalTimePlayed() const
|
||||
{
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
if(m_isRunning)
|
||||
{
|
||||
QDateTime timeNow = QDateTime::currentDateTime();
|
||||
return current + m_timeStarted.secsTo(timeNow);
|
||||
}
|
||||
return current;
|
||||
qint64 current = settings()->get("totalTimePlayed").toLongLong();
|
||||
if(m_isRunning)
|
||||
{
|
||||
QDateTime timeNow = QDateTime::currentDateTime();
|
||||
return current + m_timeStarted.secsTo(timeNow);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
void BaseInstance::resetTimePlayed()
|
||||
{
|
||||
settings()->reset("totalTimePlayed");
|
||||
settings()->reset("totalTimePlayed");
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceType() const
|
||||
{
|
||||
return m_settings->get("InstanceType").toString();
|
||||
return m_settings->get("InstanceType").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceRoot() const
|
||||
{
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
InstancePtr BaseInstance::getSharedPtr()
|
||||
{
|
||||
return shared_from_this();
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
SettingsObjectPtr BaseInstance::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
bool BaseInstance::canLaunch() const
|
||||
{
|
||||
return (!hasVersionBroken() && !isRunning());
|
||||
return (!hasVersionBroken() && !isRunning());
|
||||
}
|
||||
|
||||
bool BaseInstance::reload()
|
||||
bool BaseInstance::reloadSettings()
|
||||
{
|
||||
return m_settings->reload();
|
||||
return m_settings->reload();
|
||||
}
|
||||
|
||||
qint64 BaseInstance::lastLaunch() const
|
||||
{
|
||||
return m_settings->get("lastLaunchTime").value<qint64>();
|
||||
return m_settings->get("lastLaunchTime").value<qint64>();
|
||||
}
|
||||
|
||||
void BaseInstance::setLastLaunch(qint64 val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("lastLaunchTime", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupInitial(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_group = val;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupPost(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
setGroupInitial(val);
|
||||
emit groupChanged();
|
||||
}
|
||||
|
||||
QString BaseInstance::group() const
|
||||
{
|
||||
return m_group;
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("lastLaunchTime", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setNotes(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("notes", val);
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("notes", val);
|
||||
}
|
||||
|
||||
QString BaseInstance::notes() const
|
||||
{
|
||||
return m_settings->get("notes").toString();
|
||||
return m_settings->get("notes").toString();
|
||||
}
|
||||
|
||||
void BaseInstance::setIconKey(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("iconKey", val);
|
||||
emit propertiesChanged(this);
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("iconKey", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
QString BaseInstance::iconKey() const
|
||||
{
|
||||
return m_settings->get("iconKey").toString();
|
||||
return m_settings->get("iconKey").toString();
|
||||
}
|
||||
|
||||
void BaseInstance::setName(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("name", val);
|
||||
emit propertiesChanged(this);
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
m_settings->set("name", val);
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
QString BaseInstance::name() const
|
||||
{
|
||||
return m_settings->get("name").toString();
|
||||
return m_settings->get("name").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::windowTitle() const
|
||||
{
|
||||
return "MultiMC: " + name();
|
||||
return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " ");
|
||||
}
|
||||
|
||||
// FIXME: why is this here? move it to MinecraftInstance!!!
|
||||
QStringList BaseInstance::extraArguments() const
|
||||
{
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
{
|
||||
return m_launchProcess;
|
||||
}
|
||||
|
||||
void BaseInstance::setProvider(BaseInstanceProvider* provider)
|
||||
{
|
||||
// only once.
|
||||
assert(!m_provider);
|
||||
if(m_provider)
|
||||
{
|
||||
qWarning() << "Provider set more than once for instance" << id();
|
||||
}
|
||||
m_provider = provider;
|
||||
}
|
||||
|
||||
BaseInstanceProvider* BaseInstance::provider() const
|
||||
{
|
||||
return m_provider;
|
||||
return m_launchProcess;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,13 +30,14 @@
|
||||
#include "MessageLevel.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QDir;
|
||||
class Task;
|
||||
class LaunchTask;
|
||||
class BaseInstance;
|
||||
class BaseInstanceProvider;
|
||||
|
||||
// pointer for lazy people
|
||||
typedef std::shared_ptr<BaseInstance> InstancePtr;
|
||||
@@ -51,260 +52,217 @@ typedef std::shared_ptr<BaseInstance> InstancePtr;
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
protected:
|
||||
/// no-touchy!
|
||||
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
/// no-touchy!
|
||||
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
|
||||
public: /* types */
|
||||
enum class Status
|
||||
{
|
||||
Present,
|
||||
Gone // either nuked or invalidated
|
||||
};
|
||||
enum class Status
|
||||
{
|
||||
Present,
|
||||
Gone // either nuked or invalidated
|
||||
};
|
||||
|
||||
public:
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
|
||||
virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
|
||||
virtual void saveNow() = 0;
|
||||
|
||||
virtual void init() = 0;
|
||||
/***
|
||||
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
|
||||
* but it has not necessarily been deleted.
|
||||
*
|
||||
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
|
||||
*/
|
||||
void invalidate();
|
||||
|
||||
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
|
||||
/// responsible of cleaning up the husk
|
||||
void nuke();
|
||||
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
|
||||
/// be unique.
|
||||
virtual QString id() const;
|
||||
|
||||
/***
|
||||
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
|
||||
* but it has not necessarily been deleted.
|
||||
*
|
||||
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
|
||||
*/
|
||||
void invalidate();
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const;
|
||||
int64_t totalTimePlayed() const;
|
||||
void resetTimePlayed();
|
||||
|
||||
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
|
||||
/// be unique.
|
||||
virtual QString id() const;
|
||||
/// get the type of this instance
|
||||
QString instanceType() const;
|
||||
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const;
|
||||
int64_t totalTimePlayed() const;
|
||||
void resetTimePlayed();
|
||||
/// Path to the instance's root directory.
|
||||
QString instanceRoot() const;
|
||||
|
||||
void setProvider(BaseInstanceProvider * provider);
|
||||
BaseInstanceProvider * provider() const;
|
||||
/// Path to the instance's game root directory.
|
||||
virtual QString gameRoot() const
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
|
||||
/// get the type of this instance
|
||||
QString instanceType() const;
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
|
||||
/// Path to the instance's root directory.
|
||||
QString instanceRoot() const;
|
||||
/// Value used for instance window titles
|
||||
QString windowTitle() const;
|
||||
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
QString iconKey() const;
|
||||
void setIconKey(QString val);
|
||||
|
||||
/// Value used for instance window titles
|
||||
QString windowTitle() const;
|
||||
QString notes() const;
|
||||
void setNotes(QString val);
|
||||
|
||||
QString iconKey() const;
|
||||
void setIconKey(QString val);
|
||||
QString getPreLaunchCommand();
|
||||
QString getPostExitCommand();
|
||||
QString getWrapperCommand();
|
||||
|
||||
QString notes() const;
|
||||
void setNotes(QString val);
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
||||
QString group() const;
|
||||
void setGroupInitial(QString val);
|
||||
void setGroupPost(QString val);
|
||||
virtual QStringList extraArguments() const;
|
||||
|
||||
QString getPreLaunchCommand();
|
||||
QString getPostExitCommand();
|
||||
QString getWrapperCommand();
|
||||
/// Traits. Normally inside the version, depends on instance implementation.
|
||||
virtual QSet <QString> traits() const = 0;
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
/**
|
||||
* Gets the time that the instance was last launched.
|
||||
* Stored in milliseconds since epoch.
|
||||
*/
|
||||
qint64 lastLaunch() const;
|
||||
/// Sets the last launched time to 'val' milliseconds since epoch
|
||||
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
virtual QStringList extraArguments() const;
|
||||
/*!
|
||||
* \brief Gets this instance's settings object.
|
||||
* This settings object stores instance-specific settings.
|
||||
* \return A pointer to this instance's settings object.
|
||||
*/
|
||||
virtual SettingsObjectPtr settings() const;
|
||||
|
||||
virtual QString intendedVersionId() const = 0;
|
||||
virtual bool setIntendedVersionId(QString version) = 0;
|
||||
/// returns a valid update task
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
||||
|
||||
/*!
|
||||
* The instance's current version.
|
||||
* This value represents the instance's current version. If this value is
|
||||
* different from the intendedVersion, the instance should be updated.
|
||||
* \warning Don't change this value unless you know what you're doing.
|
||||
*/
|
||||
virtual QString currentVersionId() const = 0;
|
||||
/// returns a valid launcher (task container)
|
||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
|
||||
/*!
|
||||
* Whether or not 'the game' should be downloaded when the instance is launched.
|
||||
*/
|
||||
virtual bool shouldUpdate() const = 0;
|
||||
virtual void setShouldUpdate(bool val) = 0;
|
||||
/// returns the current launch task (if any)
|
||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||
|
||||
/// Traits. Normally inside the version, depends on instance implementation.
|
||||
virtual QSet <QString> traits() = 0;
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
*/
|
||||
virtual QProcessEnvironment createEnvironment() = 0;
|
||||
|
||||
/**
|
||||
* Gets the time that the instance was last launched.
|
||||
* Stored in milliseconds since epoch.
|
||||
*/
|
||||
qint64 lastLaunch() const;
|
||||
/// Sets the last launched time to 'val' milliseconds since epoch
|
||||
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
|
||||
/*!
|
||||
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
|
||||
*/
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
|
||||
|
||||
InstancePtr getSharedPtr();
|
||||
/*!
|
||||
* Returns the root folder to use for looking up log files
|
||||
*/
|
||||
virtual QString getLogFileRoot() = 0;
|
||||
|
||||
/*!
|
||||
* \brief Gets a pointer to this instance's version list.
|
||||
* \return A pointer to the available version list for this instance.
|
||||
*/
|
||||
virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
|
||||
virtual QString getStatusbarDescription() = 0;
|
||||
|
||||
/*!
|
||||
* \brief Gets this instance's settings object.
|
||||
* This settings object stores instance-specific settings.
|
||||
* \return A pointer to this instance's settings object.
|
||||
*/
|
||||
virtual SettingsObjectPtr settings() const;
|
||||
/// FIXME: this really should be elsewhere...
|
||||
virtual QString instanceConfigFolder() const = 0;
|
||||
|
||||
/// returns a valid update task
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
|
||||
/// get variables this instance exports
|
||||
virtual QMap<QString, QString> getVariables() const = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
virtual QString typeName() const = 0;
|
||||
|
||||
/// returns the current launch task (if any)
|
||||
std::shared_ptr<LaunchTask> getLaunchTask();
|
||||
bool hasVersionBroken() const
|
||||
{
|
||||
return m_hasBrokenVersion;
|
||||
}
|
||||
void setVersionBroken(bool value)
|
||||
{
|
||||
if(m_hasBrokenVersion != value)
|
||||
{
|
||||
m_hasBrokenVersion = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a task that should be done right before launch
|
||||
* This task should do any extra preparations needed
|
||||
*/
|
||||
virtual std::shared_ptr<Task> createJarModdingTask() = 0;
|
||||
bool hasUpdateAvailable() const
|
||||
{
|
||||
return m_hasUpdate;
|
||||
}
|
||||
void setUpdateAvailable(bool value)
|
||||
{
|
||||
if(m_hasUpdate != value)
|
||||
{
|
||||
m_hasUpdate = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
*/
|
||||
virtual QProcessEnvironment createEnvironment() = 0;
|
||||
bool hasCrashed() const
|
||||
{
|
||||
return m_crashed;
|
||||
}
|
||||
void setCrashed(bool value)
|
||||
{
|
||||
if(m_crashed != value)
|
||||
{
|
||||
m_crashed = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
|
||||
*/
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
|
||||
virtual bool canLaunch() const;
|
||||
virtual bool canEdit() const = 0;
|
||||
virtual bool canExport() const = 0;
|
||||
|
||||
/*!
|
||||
* Returns the root folder to use for looking up log files
|
||||
*/
|
||||
virtual QString getLogFileRoot() = 0;
|
||||
bool reloadSettings();
|
||||
|
||||
virtual QString getStatusbarDescription() = 0;
|
||||
/**
|
||||
* 'print' a verbose desription of the instance into a QStringList
|
||||
*/
|
||||
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
|
||||
|
||||
/// FIXME: this really should be elsewhere...
|
||||
virtual QString instanceConfigFolder() const = 0;
|
||||
Status currentStatus() const;
|
||||
|
||||
/// get variables this instance exports
|
||||
virtual QMap<QString, QString> getVariables() const = 0;
|
||||
|
||||
virtual QString typeName() const = 0;
|
||||
|
||||
bool hasVersionBroken() const
|
||||
{
|
||||
return m_hasBrokenVersion;
|
||||
}
|
||||
void setVersionBroken(bool value)
|
||||
{
|
||||
if(m_hasBrokenVersion != value)
|
||||
{
|
||||
m_hasBrokenVersion = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasUpdateAvailable() const
|
||||
{
|
||||
return m_hasUpdate;
|
||||
}
|
||||
void setUpdateAvailable(bool value)
|
||||
{
|
||||
if(m_hasUpdate != value)
|
||||
{
|
||||
m_hasUpdate = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCrashed() const
|
||||
{
|
||||
return m_crashed;
|
||||
}
|
||||
void setCrashed(bool value)
|
||||
{
|
||||
if(m_crashed != value)
|
||||
{
|
||||
m_crashed = value;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool canLaunch() const;
|
||||
virtual bool canExport() const = 0;
|
||||
|
||||
virtual bool reload();
|
||||
|
||||
/**
|
||||
* 'print' a verbose desription of the instance into a QStringList
|
||||
*/
|
||||
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
|
||||
|
||||
Status currentStatus() const;
|
||||
|
||||
int getConsoleMaxLines() const;
|
||||
bool shouldStopOnConsoleOverflow() const;
|
||||
int getConsoleMaxLines() const;
|
||||
bool shouldStopOnConsoleOverflow() const;
|
||||
|
||||
protected:
|
||||
void changeStatus(Status newStatus);
|
||||
void changeStatus(Status newStatus);
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* \brief Signal emitted when properties relevant to the instance view change
|
||||
*/
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
/*!
|
||||
* \brief Signal emitted when groups are affected in any way
|
||||
*/
|
||||
void groupChanged();
|
||||
/*!
|
||||
* \brief Signal emitted when properties relevant to the instance view change
|
||||
*/
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
|
||||
void launchTaskChanged(std::shared_ptr<LaunchTask>);
|
||||
void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
|
||||
|
||||
void runningStatusChanged(bool running);
|
||||
void runningStatusChanged(bool running);
|
||||
|
||||
void statusChanged(Status from, Status to);
|
||||
void statusChanged(Status from, Status to);
|
||||
|
||||
protected slots:
|
||||
void iconUpdated(QString key);
|
||||
void iconUpdated(QString key);
|
||||
|
||||
protected: /* data */
|
||||
QString m_rootDir;
|
||||
QString m_group;
|
||||
SettingsObjectPtr m_settings;
|
||||
// InstanceFlags m_flags;
|
||||
bool m_isRunning = false;
|
||||
std::shared_ptr<LaunchTask> m_launchProcess;
|
||||
QDateTime m_timeStarted;
|
||||
BaseInstanceProvider * m_provider = nullptr;
|
||||
QString m_rootDir;
|
||||
SettingsObjectPtr m_settings;
|
||||
// InstanceFlags m_flags;
|
||||
bool m_isRunning = false;
|
||||
shared_qobject_ptr<LaunchTask> m_launchProcess;
|
||||
QDateTime m_timeStarted;
|
||||
|
||||
private: /* data */
|
||||
Status m_status = Status::Present;
|
||||
bool m_crashed = false;
|
||||
bool m_hasUpdate = false;
|
||||
bool m_hasBrokenVersion = false;
|
||||
Status m_status = Status::Present;
|
||||
bool m_crashed = false;
|
||||
bool m_hasUpdate = false;
|
||||
bool m_hasBrokenVersion = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
|
||||
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
|
||||
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
|
||||
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include "BaseInstance.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
using InstanceId = QString;
|
||||
using InstanceLocator = std::pair<InstancePtr, int>;
|
||||
|
||||
enum class InstCreateError
|
||||
{
|
||||
NoCreateError = 0,
|
||||
NoSuchVersion,
|
||||
UnknownCreateError,
|
||||
InstExists,
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
|
||||
{
|
||||
// nil
|
||||
}
|
||||
public:
|
||||
virtual QList<InstanceId> discoverInstances() = 0;
|
||||
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
|
||||
virtual void loadGroupList() = 0;
|
||||
virtual void saveGroupList() = 0;
|
||||
|
||||
virtual QString getStagedInstancePath()
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool destroyStagingPath(const QString & path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
signals:
|
||||
// Emit this when the list of provided instances changed
|
||||
void instancesChanged();
|
||||
// Emit when the set of groups your provider supplies changes.
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
protected:
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,33 +25,33 @@
|
||||
class BaseVersion
|
||||
{
|
||||
public:
|
||||
virtual ~BaseVersion() {}
|
||||
/*!
|
||||
* A string used to identify this version in config files.
|
||||
* This should be unique within the version list or shenanigans will occur.
|
||||
*/
|
||||
virtual QString descriptor() = 0;
|
||||
virtual ~BaseVersion() {}
|
||||
/*!
|
||||
* A string used to identify this version in config files.
|
||||
* This should be unique within the version list or shenanigans will occur.
|
||||
*/
|
||||
virtual QString descriptor() = 0;
|
||||
|
||||
/*!
|
||||
* The name of this version as it is displayed to the user.
|
||||
* For example: "1.5.1"
|
||||
*/
|
||||
virtual QString name() = 0;
|
||||
/*!
|
||||
* The name of this version as it is displayed to the user.
|
||||
* For example: "1.5.1"
|
||||
*/
|
||||
virtual QString name() = 0;
|
||||
|
||||
/*!
|
||||
* This should return a string that describes
|
||||
* the kind of version this is (Stable, Beta, Snapshot, whatever)
|
||||
*/
|
||||
virtual QString typeString() const = 0;
|
||||
/*!
|
||||
* This should return a string that describes
|
||||
* the kind of version this is (Stable, Beta, Snapshot, whatever)
|
||||
*/
|
||||
virtual QString typeString() const = 0;
|
||||
|
||||
virtual bool operator<(BaseVersion &a)
|
||||
{
|
||||
return name() < a.name();
|
||||
};
|
||||
virtual bool operator>(BaseVersion &a)
|
||||
{
|
||||
return name() > a.name();
|
||||
};
|
||||
virtual bool operator<(BaseVersion &a)
|
||||
{
|
||||
return name() < a.name();
|
||||
};
|
||||
virtual bool operator>(BaseVersion &a)
|
||||
{
|
||||
return name() > a.name();
|
||||
};
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,83 +22,78 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
|
||||
|
||||
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
if (at(i)->descriptor() == descriptor)
|
||||
return at(i);
|
||||
}
|
||||
return BaseVersionPtr();
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getLatestStable() const
|
||||
{
|
||||
if (count() <= 0)
|
||||
return BaseVersionPtr();
|
||||
else
|
||||
return at(0);
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
if (at(i)->descriptor() == descriptor)
|
||||
return at(i);
|
||||
}
|
||||
return BaseVersionPtr();
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getRecommended() const
|
||||
{
|
||||
return getLatestStable();
|
||||
if (count() <= 0)
|
||||
return BaseVersionPtr();
|
||||
else
|
||||
return at(0);
|
||||
}
|
||||
|
||||
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
BaseVersionPtr version = at(index.row());
|
||||
BaseVersionPtr version = at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
|
||||
case VersionRole:
|
||||
return version->name();
|
||||
case VersionRole:
|
||||
return version->name();
|
||||
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
|
||||
case TypeRole:
|
||||
return version->typeString();
|
||||
case TypeRole:
|
||||
return version->typeString();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
{
|
||||
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
|
||||
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
|
||||
}
|
||||
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
// Return count
|
||||
return count();
|
||||
}
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> BaseVersionList::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
|
||||
roles.insert(VersionRole, "version");
|
||||
roles.insert(VersionIdRole, "versionId");
|
||||
roles.insert(ParentGameVersionRole, "parentGameVersion");
|
||||
roles.insert(RecommendedRole, "recommended");
|
||||
roles.insert(LatestRole, "latest");
|
||||
roles.insert(TypeRole, "type");
|
||||
roles.insert(BranchRole, "branch");
|
||||
roles.insert(PathRole, "path");
|
||||
roles.insert(ArchitectureRole, "architecture");
|
||||
return roles;
|
||||
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
|
||||
roles.insert(VersionRole, "version");
|
||||
roles.insert(VersionIdRole, "versionId");
|
||||
roles.insert(ParentVersionRole, "parentGameVersion");
|
||||
roles.insert(RecommendedRole, "recommended");
|
||||
roles.insert(LatestRole, "latest");
|
||||
roles.insert(TypeRole, "type");
|
||||
roles.insert(BranchRole, "branch");
|
||||
roles.insert(PathRole, "path");
|
||||
roles.insert(ArchitectureRole, "architecture");
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "BaseVersion.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
/*!
|
||||
* \brief Class that each instance type's version list derives from.
|
||||
@@ -37,90 +38,85 @@
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
VersionPointerRole = Qt::UserRole,
|
||||
VersionRole,
|
||||
VersionIdRole,
|
||||
ParentGameVersionRole,
|
||||
RecommendedRole,
|
||||
LatestRole,
|
||||
TypeRole,
|
||||
BranchRole,
|
||||
PathRole,
|
||||
ArchitectureRole,
|
||||
SortRole
|
||||
};
|
||||
typedef QList<int> RoleList;
|
||||
enum ModelRoles
|
||||
{
|
||||
VersionPointerRole = Qt::UserRole,
|
||||
VersionRole,
|
||||
VersionIdRole,
|
||||
ParentVersionRole,
|
||||
RecommendedRole,
|
||||
LatestRole,
|
||||
TypeRole,
|
||||
BranchRole,
|
||||
PathRole,
|
||||
ArchitectureRole,
|
||||
SortRole
|
||||
};
|
||||
typedef QList<int> RoleList;
|
||||
|
||||
explicit BaseVersionList(QObject *parent = 0);
|
||||
explicit BaseVersionList(QObject *parent = 0);
|
||||
|
||||
/*!
|
||||
* \brief Gets a task that will reload the version list.
|
||||
* Simply execute the task to load the list.
|
||||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual Task *getLoadTask() = 0;
|
||||
/*!
|
||||
* \brief Gets a task that will reload the version list.
|
||||
* Simply execute the task to load the list.
|
||||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual shared_qobject_ptr<Task> getLoadTask() = 0;
|
||||
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be
|
||||
//loaded.
|
||||
virtual bool isLoaded() = 0;
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be
|
||||
//loaded.
|
||||
virtual bool isLoaded() = 0;
|
||||
|
||||
//! Gets the version at the given index.
|
||||
virtual const BaseVersionPtr at(int i) const = 0;
|
||||
//! Gets the version at the given index.
|
||||
virtual const BaseVersionPtr at(int i) const = 0;
|
||||
|
||||
//! Returns the number of versions in the list.
|
||||
virtual int count() const = 0;
|
||||
//! Returns the number of versions in the list.
|
||||
virtual int count() const = 0;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
virtual QHash<int, QByteArray> roleNames() const override;
|
||||
//////// List Model Functions ////////
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
//! which roles are provided by this version list?
|
||||
virtual RoleList providesRoles() const;
|
||||
//! which roles are provided by this version list?
|
||||
virtual RoleList providesRoles() const;
|
||||
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
* \param The descriptor of the version to find.
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
* \param descriptor The descriptor of the version to find.
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the latest stable version from this list
|
||||
*/
|
||||
virtual BaseVersionPtr getLatestStable() const;
|
||||
/*!
|
||||
* \brief Gets the recommended version from this list
|
||||
* If the list doesn't support recommended versions, this works exactly as getLatestStable
|
||||
*/
|
||||
virtual BaseVersionPtr getRecommended() const;
|
||||
|
||||
/*!
|
||||
* \brief Gets the recommended version from this list
|
||||
* If the list doesn't support recommended versions, this works exactly as getLatestStable
|
||||
*/
|
||||
virtual BaseVersionPtr getRecommended() const;
|
||||
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
*/
|
||||
virtual void sortVersions() = 0;
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
*/
|
||||
virtual void sortVersions() = 0;
|
||||
|
||||
protected
|
||||
slots:
|
||||
/*!
|
||||
* Updates this list with the given list of versions.
|
||||
* This is done by copying each version in the given list and inserting it
|
||||
* into this one.
|
||||
* We need to do this so that we can set the parents of the versions are set to this
|
||||
* version list. This can't be done in the load task, because the versions the load
|
||||
* task creates are on the load task's thread and Qt won't allow their parents
|
||||
* to be set to something created on another thread.
|
||||
* To get around that problem, we invoke this method on the GUI thread, which
|
||||
* then copies the versions and sets their parents correctly.
|
||||
* \param versions List of versions whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
|
||||
/*!
|
||||
* Updates this list with the given list of versions.
|
||||
* This is done by copying each version in the given list and inserting it
|
||||
* into this one.
|
||||
* We need to do this so that we can set the parents of the versions are set to this
|
||||
* version list. This can't be done in the load task, because the versions the load
|
||||
* task creates are on the load task's thread and Qt won't allow their parents
|
||||
* to be set to something created on another thread.
|
||||
* To get around that problem, we invoke this method on the GUI thread, which
|
||||
* then copies the versions and sets their parents correctly.
|
||||
* \param versions List of versions whose parents should be set.
|
||||
*/
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
|
||||
};
|
||||
|
||||
@@ -3,461 +3,452 @@ project(MultiMC_logic)
|
||||
include (UnitTest)
|
||||
|
||||
set(CORE_SOURCES
|
||||
# LOGIC - Base classes and infrastructure
|
||||
BaseInstaller.h
|
||||
BaseInstaller.cpp
|
||||
BaseVersionList.h
|
||||
BaseVersionList.cpp
|
||||
InstanceCreationTask.h
|
||||
InstanceCreationTask.cpp
|
||||
InstanceCopyTask.h
|
||||
InstanceCopyTask.cpp
|
||||
InstanceImportTask.h
|
||||
InstanceImportTask.cpp
|
||||
InstanceList.h
|
||||
InstanceList.cpp
|
||||
LoggedProcess.h
|
||||
LoggedProcess.cpp
|
||||
MessageLevel.cpp
|
||||
MessageLevel.h
|
||||
BaseInstanceProvider.h
|
||||
FolderInstanceProvider.h
|
||||
FolderInstanceProvider.cpp
|
||||
BaseVersion.h
|
||||
BaseInstance.h
|
||||
BaseInstance.cpp
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
MMCStrings.h
|
||||
MMCStrings.cpp
|
||||
# LOGIC - Base classes and infrastructure
|
||||
BaseInstaller.h
|
||||
BaseInstaller.cpp
|
||||
BaseVersionList.h
|
||||
BaseVersionList.cpp
|
||||
InstanceList.h
|
||||
InstanceList.cpp
|
||||
InstanceTask.h
|
||||
InstanceTask.cpp
|
||||
LoggedProcess.h
|
||||
LoggedProcess.cpp
|
||||
MessageLevel.cpp
|
||||
MessageLevel.h
|
||||
BaseVersion.h
|
||||
BaseInstance.h
|
||||
BaseInstance.cpp
|
||||
NullInstance.h
|
||||
MMCZip.h
|
||||
MMCZip.cpp
|
||||
MMCStrings.h
|
||||
MMCStrings.cpp
|
||||
|
||||
# Use tracking separate from memory management
|
||||
Usable.h
|
||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||
InstanceCreationTask.h
|
||||
InstanceCreationTask.cpp
|
||||
InstanceCopyTask.h
|
||||
InstanceCopyTask.cpp
|
||||
InstanceImportTask.h
|
||||
InstanceImportTask.cpp
|
||||
|
||||
# Prefix tree where node names are strings between separators
|
||||
SeparatorPrefixTree.h
|
||||
# Use tracking separate from memory management
|
||||
Usable.h
|
||||
|
||||
# WARNING: globals live here
|
||||
Env.h
|
||||
Env.cpp
|
||||
# Prefix tree where node names are strings between separators
|
||||
SeparatorPrefixTree.h
|
||||
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
# WARNING: globals live here
|
||||
Env.h
|
||||
Env.cpp
|
||||
|
||||
FileSystem.h
|
||||
FileSystem.cpp
|
||||
# String filters
|
||||
Filter.h
|
||||
Filter.cpp
|
||||
|
||||
Exception.h
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
|
||||
# RW lock protected map
|
||||
RWStorage.h
|
||||
FileSystem.h
|
||||
FileSystem.cpp
|
||||
|
||||
# A variable that has an implicit default value and keeps track of changes
|
||||
DefaultVariable.h
|
||||
Exception.h
|
||||
|
||||
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
|
||||
QObjectPtr.h
|
||||
# RW lock protected map
|
||||
RWStorage.h
|
||||
|
||||
# Compression support
|
||||
GZip.h
|
||||
GZip.cpp
|
||||
# A variable that has an implicit default value and keeps track of changes
|
||||
DefaultVariable.h
|
||||
|
||||
# Command line parameter parsing
|
||||
Commandline.h
|
||||
Commandline.cpp
|
||||
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
|
||||
QObjectPtr.h
|
||||
|
||||
# Version number string support
|
||||
Version.h
|
||||
Version.cpp
|
||||
# Compression support
|
||||
GZip.h
|
||||
GZip.cpp
|
||||
|
||||
# A Recursive file system watcher
|
||||
RecursiveFileSystemWatcher.h
|
||||
RecursiveFileSystemWatcher.cpp
|
||||
# Command line parameter parsing
|
||||
Commandline.h
|
||||
Commandline.cpp
|
||||
|
||||
# Version number string support
|
||||
Version.h
|
||||
Version.cpp
|
||||
|
||||
# A Recursive file system watcher
|
||||
RecursiveFileSystemWatcher.h
|
||||
RecursiveFileSystemWatcher.cpp
|
||||
)
|
||||
|
||||
add_unit_test(FileSystem
|
||||
SOURCES FileSystem_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA testdata
|
||||
)
|
||||
SOURCES FileSystem_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA testdata
|
||||
)
|
||||
|
||||
add_unit_test(GZip
|
||||
SOURCES GZip_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES GZip_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
set(PATHMATCHER_SOURCES
|
||||
# Path matchers
|
||||
pathmatcher/FSTreeMatcher.h
|
||||
pathmatcher/IPathMatcher.h
|
||||
pathmatcher/MultiMatcher.h
|
||||
pathmatcher/RegexpMatcher.h
|
||||
# Path matchers
|
||||
pathmatcher/FSTreeMatcher.h
|
||||
pathmatcher/IPathMatcher.h
|
||||
pathmatcher/MultiMatcher.h
|
||||
pathmatcher/RegexpMatcher.h
|
||||
)
|
||||
|
||||
set(NET_SOURCES
|
||||
# network stuffs
|
||||
net/ByteArraySink.h
|
||||
net/ChecksumValidator.h
|
||||
net/Download.cpp
|
||||
net/Download.h
|
||||
net/FileSink.cpp
|
||||
net/FileSink.h
|
||||
net/HttpMetaCache.cpp
|
||||
net/HttpMetaCache.h
|
||||
net/MetaCacheSink.cpp
|
||||
net/MetaCacheSink.h
|
||||
net/NetAction.h
|
||||
net/NetJob.cpp
|
||||
net/NetJob.h
|
||||
net/PasteUpload.cpp
|
||||
net/PasteUpload.h
|
||||
net/Sink.h
|
||||
net/URLConstants.cpp
|
||||
net/URLConstants.h
|
||||
net/Validator.h
|
||||
# network stuffs
|
||||
net/ByteArraySink.h
|
||||
net/ChecksumValidator.h
|
||||
net/Download.cpp
|
||||
net/Download.h
|
||||
net/FileSink.cpp
|
||||
net/FileSink.h
|
||||
net/HttpMetaCache.cpp
|
||||
net/HttpMetaCache.h
|
||||
net/MetaCacheSink.cpp
|
||||
net/MetaCacheSink.h
|
||||
net/NetAction.h
|
||||
net/NetJob.cpp
|
||||
net/NetJob.h
|
||||
net/PasteUpload.cpp
|
||||
net/PasteUpload.h
|
||||
net/Sink.h
|
||||
net/URLConstants.cpp
|
||||
net/URLConstants.h
|
||||
net/Validator.h
|
||||
)
|
||||
|
||||
# Game launch logic
|
||||
set(LAUNCH_SOURCES
|
||||
launch/steps/PostLaunchCommand.cpp
|
||||
launch/steps/PostLaunchCommand.h
|
||||
launch/steps/PreLaunchCommand.cpp
|
||||
launch/steps/PreLaunchCommand.h
|
||||
launch/steps/TextPrint.cpp
|
||||
launch/steps/TextPrint.h
|
||||
launch/steps/Update.cpp
|
||||
launch/steps/Update.h
|
||||
launch/LaunchStep.cpp
|
||||
launch/LaunchStep.h
|
||||
launch/LaunchTask.cpp
|
||||
launch/LaunchTask.h
|
||||
launch/LogModel.cpp
|
||||
launch/LogModel.h
|
||||
launch/steps/PostLaunchCommand.cpp
|
||||
launch/steps/PostLaunchCommand.h
|
||||
launch/steps/PreLaunchCommand.cpp
|
||||
launch/steps/PreLaunchCommand.h
|
||||
launch/steps/TextPrint.cpp
|
||||
launch/steps/TextPrint.h
|
||||
launch/steps/Update.cpp
|
||||
launch/steps/Update.h
|
||||
launch/LaunchStep.cpp
|
||||
launch/LaunchStep.h
|
||||
launch/LaunchTask.cpp
|
||||
launch/LaunchTask.h
|
||||
launch/LogModel.cpp
|
||||
launch/LogModel.h
|
||||
)
|
||||
|
||||
# Old update system
|
||||
set(UPDATE_SOURCES
|
||||
updater/GoUpdate.h
|
||||
updater/GoUpdate.cpp
|
||||
updater/UpdateChecker.h
|
||||
updater/UpdateChecker.cpp
|
||||
updater/DownloadTask.h
|
||||
updater/DownloadTask.cpp
|
||||
updater/GoUpdate.h
|
||||
updater/GoUpdate.cpp
|
||||
updater/UpdateChecker.h
|
||||
updater/UpdateChecker.cpp
|
||||
updater/DownloadTask.h
|
||||
updater/DownloadTask.cpp
|
||||
)
|
||||
|
||||
add_unit_test(UpdateChecker
|
||||
SOURCES updater/UpdateChecker_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA updater/testdata
|
||||
)
|
||||
SOURCES updater/UpdateChecker_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA updater/testdata
|
||||
)
|
||||
|
||||
add_unit_test(DownloadTask
|
||||
SOURCES updater/DownloadTask_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA updater/testdata
|
||||
)
|
||||
SOURCES updater/DownloadTask_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA updater/testdata
|
||||
)
|
||||
|
||||
# Rarely used notifications
|
||||
set(NOTIFICATIONS_SOURCES
|
||||
# Notifications - short warning messages
|
||||
notifications/NotificationChecker.h
|
||||
notifications/NotificationChecker.cpp
|
||||
# Notifications - short warning messages
|
||||
notifications/NotificationChecker.h
|
||||
notifications/NotificationChecker.cpp
|
||||
)
|
||||
|
||||
# Backend for the news bar... there's usually no news.
|
||||
set(NEWS_SOURCES
|
||||
# News System
|
||||
news/NewsChecker.h
|
||||
news/NewsChecker.cpp
|
||||
news/NewsEntry.h
|
||||
news/NewsEntry.cpp
|
||||
# News System
|
||||
news/NewsChecker.h
|
||||
news/NewsChecker.cpp
|
||||
news/NewsEntry.h
|
||||
news/NewsEntry.cpp
|
||||
)
|
||||
|
||||
# Icon interface
|
||||
set(ICONS_SOURCES
|
||||
# News System
|
||||
icons/IIconList.h
|
||||
icons/IIconList.cpp
|
||||
# Icons System and related code
|
||||
icons/IIconList.h
|
||||
icons/IIconList.cpp
|
||||
icons/IconUtils.h
|
||||
icons/IconUtils.cpp
|
||||
)
|
||||
|
||||
# Minecraft services status checker
|
||||
set(STATUS_SOURCES
|
||||
# Status system
|
||||
status/StatusChecker.h
|
||||
status/StatusChecker.cpp
|
||||
# Status system
|
||||
status/StatusChecker.h
|
||||
status/StatusChecker.cpp
|
||||
)
|
||||
|
||||
# Support for Minecraft instances and launch
|
||||
set(MINECRAFT_SOURCES
|
||||
# Minecraft support
|
||||
minecraft/auth/AuthSession.h
|
||||
minecraft/auth/AuthSession.cpp
|
||||
minecraft/auth/MojangAccountList.h
|
||||
minecraft/auth/MojangAccountList.cpp
|
||||
minecraft/auth/MojangAccount.h
|
||||
minecraft/auth/MojangAccount.cpp
|
||||
minecraft/auth/YggdrasilTask.h
|
||||
minecraft/auth/YggdrasilTask.cpp
|
||||
minecraft/auth/flows/AuthenticateTask.h
|
||||
minecraft/auth/flows/AuthenticateTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/ValidateTask.h
|
||||
minecraft/auth/flows/ValidateTask.cpp
|
||||
minecraft/onesix/OneSixUpdate.h
|
||||
minecraft/onesix/OneSixUpdate.cpp
|
||||
minecraft/onesix/OneSixInstance.h
|
||||
minecraft/onesix/OneSixInstance.cpp
|
||||
minecraft/onesix/OneSixProfileStrategy.cpp
|
||||
minecraft/onesix/OneSixProfileStrategy.h
|
||||
minecraft/onesix/OneSixVersionFormat.cpp
|
||||
minecraft/onesix/OneSixVersionFormat.h
|
||||
minecraft/onesix/update/AssetUpdateTask.h
|
||||
minecraft/onesix/update/AssetUpdateTask.cpp
|
||||
minecraft/onesix/update/FMLLibrariesTask.cpp
|
||||
minecraft/onesix/update/FMLLibrariesTask.h
|
||||
minecraft/onesix/update/FoldersTask.cpp
|
||||
minecraft/onesix/update/FoldersTask.h
|
||||
minecraft/onesix/update/LibrariesTask.cpp
|
||||
minecraft/onesix/update/LibrariesTask.h
|
||||
minecraft/launch/ClaimAccount.cpp
|
||||
minecraft/launch/ClaimAccount.h
|
||||
minecraft/launch/CreateServerResourcePacksFolder.cpp
|
||||
minecraft/launch/CreateServerResourcePacksFolder.h
|
||||
minecraft/launch/ModMinecraftJar.cpp
|
||||
minecraft/launch/ModMinecraftJar.h
|
||||
minecraft/launch/DirectJavaLaunch.cpp
|
||||
minecraft/launch/DirectJavaLaunch.h
|
||||
minecraft/launch/ExtractNatives.cpp
|
||||
minecraft/launch/ExtractNatives.h
|
||||
minecraft/launch/LauncherPartLaunch.cpp
|
||||
minecraft/launch/LauncherPartLaunch.h
|
||||
minecraft/launch/PrintInstanceInfo.cpp
|
||||
minecraft/launch/PrintInstanceInfo.h
|
||||
minecraft/legacy/LegacyModList.h
|
||||
minecraft/legacy/LegacyModList.cpp
|
||||
minecraft/legacy/LegacyUpdate.h
|
||||
minecraft/legacy/LegacyUpdate.cpp
|
||||
minecraft/legacy/LegacyInstance.h
|
||||
minecraft/legacy/LegacyInstance.cpp
|
||||
minecraft/legacy/LwjglVersionList.h
|
||||
minecraft/legacy/LwjglVersionList.cpp
|
||||
minecraft/GradleSpecifier.h
|
||||
minecraft/MinecraftProfile.cpp
|
||||
minecraft/MinecraftProfile.h
|
||||
minecraft/MojangVersionFormat.cpp
|
||||
minecraft/MojangVersionFormat.h
|
||||
minecraft/JarMod.h
|
||||
minecraft/MinecraftInstance.cpp
|
||||
minecraft/MinecraftInstance.h
|
||||
minecraft/MinecraftVersion.cpp
|
||||
minecraft/MinecraftVersion.h
|
||||
minecraft/MinecraftVersionList.cpp
|
||||
minecraft/MinecraftVersionList.h
|
||||
minecraft/Rule.cpp
|
||||
minecraft/Rule.h
|
||||
minecraft/OpSys.cpp
|
||||
minecraft/OpSys.h
|
||||
minecraft/ParseUtils.cpp
|
||||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/ProfileStrategy.h
|
||||
minecraft/Library.cpp
|
||||
minecraft/Library.h
|
||||
minecraft/MojangDownloadInfo.h
|
||||
minecraft/VersionBuildError.h
|
||||
minecraft/VersionFile.cpp
|
||||
minecraft/VersionFile.h
|
||||
minecraft/ProfilePatch.h
|
||||
minecraft/VersionFilterData.h
|
||||
minecraft/VersionFilterData.cpp
|
||||
minecraft/Mod.h
|
||||
minecraft/Mod.cpp
|
||||
minecraft/ModList.h
|
||||
minecraft/ModList.cpp
|
||||
minecraft/World.h
|
||||
minecraft/World.cpp
|
||||
minecraft/WorldList.h
|
||||
minecraft/WorldList.cpp
|
||||
# Minecraft support
|
||||
minecraft/auth/AuthSession.h
|
||||
minecraft/auth/AuthSession.cpp
|
||||
minecraft/auth/MojangAccountList.h
|
||||
minecraft/auth/MojangAccountList.cpp
|
||||
minecraft/auth/MojangAccount.h
|
||||
minecraft/auth/MojangAccount.cpp
|
||||
minecraft/auth/YggdrasilTask.h
|
||||
minecraft/auth/YggdrasilTask.cpp
|
||||
minecraft/auth/flows/AuthenticateTask.h
|
||||
minecraft/auth/flows/AuthenticateTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/ValidateTask.h
|
||||
minecraft/auth/flows/ValidateTask.cpp
|
||||
minecraft/gameoptions/GameOptions.h
|
||||
minecraft/gameoptions/GameOptions.cpp
|
||||
minecraft/update/AssetUpdateTask.h
|
||||
minecraft/update/AssetUpdateTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.h
|
||||
minecraft/update/FoldersTask.cpp
|
||||
minecraft/update/FoldersTask.h
|
||||
minecraft/update/LibrariesTask.cpp
|
||||
minecraft/update/LibrariesTask.h
|
||||
minecraft/launch/ClaimAccount.cpp
|
||||
minecraft/launch/ClaimAccount.h
|
||||
minecraft/launch/CreateServerResourcePacksFolder.cpp
|
||||
minecraft/launch/CreateServerResourcePacksFolder.h
|
||||
minecraft/launch/ModMinecraftJar.cpp
|
||||
minecraft/launch/ModMinecraftJar.h
|
||||
minecraft/launch/DirectJavaLaunch.cpp
|
||||
minecraft/launch/DirectJavaLaunch.h
|
||||
minecraft/launch/ExtractNatives.cpp
|
||||
minecraft/launch/ExtractNatives.h
|
||||
minecraft/launch/LauncherPartLaunch.cpp
|
||||
minecraft/launch/LauncherPartLaunch.h
|
||||
minecraft/launch/PrintInstanceInfo.cpp
|
||||
minecraft/launch/PrintInstanceInfo.h
|
||||
minecraft/launch/ReconstructAssets.cpp
|
||||
minecraft/launch/ReconstructAssets.h
|
||||
minecraft/legacy/LegacyModList.h
|
||||
minecraft/legacy/LegacyModList.cpp
|
||||
minecraft/legacy/LegacyInstance.h
|
||||
minecraft/legacy/LegacyInstance.cpp
|
||||
minecraft/legacy/LegacyUpgradeTask.h
|
||||
minecraft/legacy/LegacyUpgradeTask.cpp
|
||||
minecraft/GradleSpecifier.h
|
||||
minecraft/MinecraftInstance.cpp
|
||||
minecraft/MinecraftInstance.h
|
||||
minecraft/LaunchProfile.cpp
|
||||
minecraft/LaunchProfile.h
|
||||
minecraft/Component.cpp
|
||||
minecraft/Component.h
|
||||
minecraft/ComponentList.cpp
|
||||
minecraft/ComponentList.h
|
||||
minecraft/ComponentUpdateTask.cpp
|
||||
minecraft/ComponentUpdateTask.h
|
||||
minecraft/MinecraftLoadAndCheck.h
|
||||
minecraft/MinecraftLoadAndCheck.cpp
|
||||
minecraft/MinecraftUpdate.h
|
||||
minecraft/MinecraftUpdate.cpp
|
||||
minecraft/MojangVersionFormat.cpp
|
||||
minecraft/MojangVersionFormat.h
|
||||
minecraft/Rule.cpp
|
||||
minecraft/Rule.h
|
||||
minecraft/OneSixVersionFormat.cpp
|
||||
minecraft/OneSixVersionFormat.h
|
||||
minecraft/OpSys.cpp
|
||||
minecraft/OpSys.h
|
||||
minecraft/ParseUtils.cpp
|
||||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/Library.cpp
|
||||
minecraft/Library.h
|
||||
minecraft/MojangDownloadInfo.h
|
||||
minecraft/VersionFile.cpp
|
||||
minecraft/VersionFile.h
|
||||
minecraft/VersionFilterData.h
|
||||
minecraft/VersionFilterData.cpp
|
||||
minecraft/Mod.h
|
||||
minecraft/Mod.cpp
|
||||
minecraft/SimpleModList.h
|
||||
minecraft/SimpleModList.cpp
|
||||
minecraft/World.h
|
||||
minecraft/World.cpp
|
||||
minecraft/WorldList.h
|
||||
minecraft/WorldList.cpp
|
||||
|
||||
# FTB
|
||||
minecraft/ftb/OneSixFTBInstance.h
|
||||
minecraft/ftb/OneSixFTBInstance.cpp
|
||||
minecraft/ftb/LegacyFTBInstance.h
|
||||
minecraft/ftb/LegacyFTBInstance.cpp
|
||||
minecraft/ftb/FTBProfileStrategy.h
|
||||
minecraft/ftb/FTBProfileStrategy.cpp
|
||||
minecraft/ftb/FTBInstanceProvider.cpp
|
||||
minecraft/ftb/FTBInstanceProvider.h
|
||||
minecraft/ftb/FTBPlugin.h
|
||||
minecraft/ftb/FTBPlugin.cpp
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
# Forge and all things forge related
|
||||
minecraft/forge/ForgeXzDownload.h
|
||||
minecraft/forge/ForgeXzDownload.cpp
|
||||
|
||||
# Forge and all things forge related
|
||||
minecraft/forge/ForgeVersion.h
|
||||
minecraft/forge/ForgeVersion.cpp
|
||||
minecraft/forge/ForgeVersionList.h
|
||||
minecraft/forge/ForgeVersionList.cpp
|
||||
minecraft/forge/ForgeXzDownload.h
|
||||
minecraft/forge/ForgeXzDownload.cpp
|
||||
minecraft/forge/LegacyForge.h
|
||||
minecraft/forge/LegacyForge.cpp
|
||||
minecraft/forge/ForgeInstaller.h
|
||||
minecraft/forge/ForgeInstaller.cpp
|
||||
|
||||
# Liteloader and related things
|
||||
minecraft/liteloader/LiteLoaderInstaller.h
|
||||
minecraft/liteloader/LiteLoaderInstaller.cpp
|
||||
minecraft/liteloader/LiteLoaderVersionList.h
|
||||
minecraft/liteloader/LiteLoaderVersionList.cpp
|
||||
minecraft/SkinUpload.cpp
|
||||
minecraft/SkinUpload.h
|
||||
)
|
||||
# Skin upload utilities
|
||||
minecraft/SkinUpload.cpp
|
||||
minecraft/SkinUpload.h
|
||||
)
|
||||
|
||||
add_unit_test(GradleSpecifier
|
||||
SOURCES minecraft/GradleSpecifier_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES minecraft/GradleSpecifier_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
add_unit_test(MojangVersionFormat
|
||||
SOURCES minecraft/MojangVersionFormat_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA minecraft/testdata
|
||||
)
|
||||
SOURCES minecraft/MojangVersionFormat_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA minecraft/testdata
|
||||
)
|
||||
|
||||
add_unit_test(Library
|
||||
SOURCES minecraft/Library_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES minecraft/Library_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
# FIXME: shares data with FileSystem test
|
||||
add_unit_test(ModList
|
||||
SOURCES minecraft/ModList_test.cpp
|
||||
DATA testdata
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
add_unit_test(SimpleModList
|
||||
SOURCES minecraft/SimpleModList_test.cpp
|
||||
DATA testdata
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
add_unit_test(ParseUtils
|
||||
SOURCES minecraft/ParseUtils_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES minecraft/ParseUtils_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
# the screenshots feature
|
||||
set(SCREENSHOTS_SOURCES
|
||||
screenshots/Screenshot.h
|
||||
screenshots/ImgurUpload.h
|
||||
screenshots/ImgurUpload.cpp
|
||||
screenshots/ImgurAlbumCreation.h
|
||||
screenshots/ImgurAlbumCreation.cpp
|
||||
screenshots/Screenshot.h
|
||||
screenshots/ImgurUpload.h
|
||||
screenshots/ImgurUpload.cpp
|
||||
screenshots/ImgurAlbumCreation.h
|
||||
screenshots/ImgurAlbumCreation.cpp
|
||||
)
|
||||
|
||||
set(TASKS_SOURCES
|
||||
# Tasks
|
||||
tasks/Task.h
|
||||
tasks/Task.cpp
|
||||
tasks/ThreadTask.h
|
||||
tasks/ThreadTask.cpp
|
||||
tasks/SequentialTask.h
|
||||
tasks/SequentialTask.cpp
|
||||
# Tasks
|
||||
tasks/Task.h
|
||||
tasks/Task.cpp
|
||||
tasks/SequentialTask.h
|
||||
tasks/SequentialTask.cpp
|
||||
)
|
||||
|
||||
set(SETTINGS_SOURCES
|
||||
# Settings
|
||||
settings/INIFile.cpp
|
||||
settings/INIFile.h
|
||||
settings/INISettingsObject.cpp
|
||||
settings/INISettingsObject.h
|
||||
settings/OverrideSetting.cpp
|
||||
settings/OverrideSetting.h
|
||||
settings/PassthroughSetting.cpp
|
||||
settings/PassthroughSetting.h
|
||||
settings/Setting.cpp
|
||||
settings/Setting.h
|
||||
settings/SettingsObject.cpp
|
||||
settings/SettingsObject.h
|
||||
# Settings
|
||||
settings/INIFile.cpp
|
||||
settings/INIFile.h
|
||||
settings/INISettingsObject.cpp
|
||||
settings/INISettingsObject.h
|
||||
settings/OverrideSetting.cpp
|
||||
settings/OverrideSetting.h
|
||||
settings/PassthroughSetting.cpp
|
||||
settings/PassthroughSetting.h
|
||||
settings/Setting.cpp
|
||||
settings/Setting.h
|
||||
settings/SettingsObject.cpp
|
||||
settings/SettingsObject.h
|
||||
)
|
||||
|
||||
add_unit_test(INIFile
|
||||
SOURCES settings/INIFile_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES settings/INIFile_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
set(JAVA_SOURCES
|
||||
# Java related code
|
||||
java/launch/CheckJava.cpp
|
||||
java/launch/CheckJava.h
|
||||
java/JavaChecker.h
|
||||
java/JavaChecker.cpp
|
||||
java/JavaCheckerJob.h
|
||||
java/JavaCheckerJob.cpp
|
||||
java/JavaInstall.h
|
||||
java/JavaInstall.cpp
|
||||
java/JavaInstallList.h
|
||||
java/JavaInstallList.cpp
|
||||
java/JavaUtils.h
|
||||
java/JavaUtils.cpp
|
||||
java/JavaVersion.h
|
||||
java/JavaVersion.cpp
|
||||
# Java related code
|
||||
java/launch/CheckJava.cpp
|
||||
java/launch/CheckJava.h
|
||||
java/JavaChecker.h
|
||||
java/JavaChecker.cpp
|
||||
java/JavaCheckerJob.h
|
||||
java/JavaCheckerJob.cpp
|
||||
java/JavaInstall.h
|
||||
java/JavaInstall.cpp
|
||||
java/JavaInstallList.h
|
||||
java/JavaInstallList.cpp
|
||||
java/JavaUtils.h
|
||||
java/JavaUtils.cpp
|
||||
java/JavaVersion.h
|
||||
java/JavaVersion.cpp
|
||||
)
|
||||
|
||||
add_unit_test(JavaVersion
|
||||
SOURCES java/JavaVersion_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
SOURCES java/JavaVersion_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
set(TRANSLATIONS_SOURCES
|
||||
translations/TranslationsModel.h
|
||||
translations/TranslationsModel.cpp
|
||||
translations/TranslationsModel.h
|
||||
translations/TranslationsModel.cpp
|
||||
translations/POTranslator.h
|
||||
translations/POTranslator.cpp
|
||||
)
|
||||
|
||||
set(TOOLS_SOURCES
|
||||
# Tools
|
||||
tools/BaseExternalTool.cpp
|
||||
tools/BaseExternalTool.h
|
||||
tools/BaseProfiler.cpp
|
||||
tools/BaseProfiler.h
|
||||
tools/JProfiler.cpp
|
||||
tools/JProfiler.h
|
||||
tools/JVisualVM.cpp
|
||||
tools/JVisualVM.h
|
||||
tools/MCEditTool.cpp
|
||||
tools/MCEditTool.h
|
||||
# Tools
|
||||
tools/BaseExternalTool.cpp
|
||||
tools/BaseExternalTool.h
|
||||
tools/BaseProfiler.cpp
|
||||
tools/BaseProfiler.h
|
||||
tools/JProfiler.cpp
|
||||
tools/JProfiler.h
|
||||
tools/JVisualVM.cpp
|
||||
tools/JVisualVM.h
|
||||
tools/MCEditTool.cpp
|
||||
tools/MCEditTool.h
|
||||
)
|
||||
|
||||
set(WONKO_SOURCES
|
||||
# Wonko
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.h
|
||||
wonko/format/WonkoFormatV1.cpp
|
||||
wonko/format/WonkoFormatV1.h
|
||||
wonko/format/WonkoFormat.cpp
|
||||
wonko/format/WonkoFormat.h
|
||||
wonko/BaseWonkoEntity.cpp
|
||||
wonko/BaseWonkoEntity.h
|
||||
wonko/WonkoVersionList.cpp
|
||||
wonko/WonkoVersionList.h
|
||||
wonko/WonkoVersion.cpp
|
||||
wonko/WonkoVersion.h
|
||||
wonko/WonkoIndex.cpp
|
||||
wonko/WonkoIndex.h
|
||||
wonko/WonkoUtil.cpp
|
||||
wonko/WonkoUtil.h
|
||||
wonko/WonkoReference.cpp
|
||||
wonko/WonkoReference.h
|
||||
set(META_SOURCES
|
||||
# Metadata sources
|
||||
meta/JsonFormat.cpp
|
||||
meta/JsonFormat.h
|
||||
meta/BaseEntity.cpp
|
||||
meta/BaseEntity.h
|
||||
meta/VersionList.cpp
|
||||
meta/VersionList.h
|
||||
meta/Version.cpp
|
||||
meta/Version.h
|
||||
meta/Index.cpp
|
||||
meta/Index.h
|
||||
)
|
||||
|
||||
add_unit_test(WonkoIndex
|
||||
SOURCES wonko/WonkoIndex_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
set(FTB_SOURCES
|
||||
modplatform/ftb/FtbPackFetchTask.h
|
||||
modplatform/ftb/FtbPackFetchTask.cpp
|
||||
modplatform/ftb/FtbPackInstallTask.h
|
||||
modplatform/ftb/FtbPackInstallTask.cpp
|
||||
|
||||
modplatform/ftb/FtbPrivatePackManager.h
|
||||
modplatform/ftb/FtbPrivatePackManager.cpp
|
||||
|
||||
modplatform/ftb/PackHelpers.h
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
# Flame
|
||||
modplatform/flame/PackManifest.h
|
||||
modplatform/flame/PackManifest.cpp
|
||||
modplatform/flame/FileResolvingTask.h
|
||||
modplatform/flame/FileResolvingTask.cpp
|
||||
)
|
||||
|
||||
add_unit_test(Index
|
||||
SOURCES meta/Index_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
################################ COMPILE ################################
|
||||
|
||||
@@ -465,34 +456,44 @@ add_unit_test(WonkoIndex
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
set(LOGIC_SOURCES
|
||||
${CORE_SOURCES}
|
||||
${PATHMATCHER_SOURCES}
|
||||
${NET_SOURCES}
|
||||
${LAUNCH_SOURCES}
|
||||
${UPDATE_SOURCES}
|
||||
${NOTIFICATIONS_SOURCES}
|
||||
${NEWS_SOURCES}
|
||||
${STATUS_SOURCES}
|
||||
${MINECRAFT_SOURCES}
|
||||
${SCREENSHOTS_SOURCES}
|
||||
${TASKS_SOURCES}
|
||||
${SETTINGS_SOURCES}
|
||||
${JAVA_SOURCES}
|
||||
${TRANSLATIONS_SOURCES}
|
||||
${TOOLS_SOURCES}
|
||||
${WONKO_SOURCES}
|
||||
${ICONS_SOURCES}
|
||||
${CORE_SOURCES}
|
||||
${PATHMATCHER_SOURCES}
|
||||
${NET_SOURCES}
|
||||
${LAUNCH_SOURCES}
|
||||
${UPDATE_SOURCES}
|
||||
${NOTIFICATIONS_SOURCES}
|
||||
${NEWS_SOURCES}
|
||||
${STATUS_SOURCES}
|
||||
${MINECRAFT_SOURCES}
|
||||
${SCREENSHOTS_SOURCES}
|
||||
${TASKS_SOURCES}
|
||||
${SETTINGS_SOURCES}
|
||||
${JAVA_SOURCES}
|
||||
${TRANSLATIONS_SOURCES}
|
||||
${TOOLS_SOURCES}
|
||||
${META_SOURCES}
|
||||
${ICONS_SOURCES}
|
||||
${FTB_SOURCES}
|
||||
${FLAME_SOURCES}
|
||||
)
|
||||
|
||||
message(STATUS "FOO! ${LOGIC_SOURCES}")
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
generate_export_header(MultiMC_logic)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_logic xz-embedded unpack200 systeminfo MultiMC_quazip ${NBT_NAME} ${ZLIB_LIBRARIES})
|
||||
|
||||
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
|
||||
target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
|
||||
|
||||
# Install it
|
||||
install(
|
||||
TARGETS MultiMC_logic
|
||||
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -27,453 +27,453 @@ namespace Commandline
|
||||
// commandline splitter
|
||||
QStringList splitArgs(QString args)
|
||||
{
|
||||
QStringList argv;
|
||||
QString current;
|
||||
bool escape = false;
|
||||
QChar inquotes;
|
||||
for (int i = 0; i < args.length(); i++)
|
||||
{
|
||||
QChar cchar = args.at(i);
|
||||
QStringList argv;
|
||||
QString current;
|
||||
bool escape = false;
|
||||
QChar inquotes;
|
||||
for (int i = 0; i < args.length(); i++)
|
||||
{
|
||||
QChar cchar = args.at(i);
|
||||
|
||||
// \ escaped
|
||||
if (escape)
|
||||
{
|
||||
current += cchar;
|
||||
escape = false;
|
||||
// in "quotes"
|
||||
}
|
||||
else if (!inquotes.isNull())
|
||||
{
|
||||
if (cchar == 0x5C)
|
||||
escape = true;
|
||||
else if (cchar == inquotes)
|
||||
inquotes = 0;
|
||||
else
|
||||
current += cchar;
|
||||
// otherwise
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cchar == 0x20)
|
||||
{
|
||||
if (!current.isEmpty())
|
||||
{
|
||||
argv << current;
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else if (cchar == 0x22 || cchar == 0x27)
|
||||
inquotes = cchar;
|
||||
else
|
||||
current += cchar;
|
||||
}
|
||||
}
|
||||
if (!current.isEmpty())
|
||||
argv << current;
|
||||
return argv;
|
||||
// \ escaped
|
||||
if (escape)
|
||||
{
|
||||
current += cchar;
|
||||
escape = false;
|
||||
// in "quotes"
|
||||
}
|
||||
else if (!inquotes.isNull())
|
||||
{
|
||||
if (cchar == '\\')
|
||||
escape = true;
|
||||
else if (cchar == inquotes)
|
||||
inquotes = 0;
|
||||
else
|
||||
current += cchar;
|
||||
// otherwise
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cchar == ' ')
|
||||
{
|
||||
if (!current.isEmpty())
|
||||
{
|
||||
argv << current;
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else if (cchar == '"' || cchar == '\'')
|
||||
inquotes = cchar;
|
||||
else
|
||||
current += cchar;
|
||||
}
|
||||
}
|
||||
if (!current.isEmpty())
|
||||
argv << current;
|
||||
return argv;
|
||||
}
|
||||
|
||||
Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle)
|
||||
{
|
||||
m_flagStyle = flagStyle;
|
||||
m_argStyle = argStyle;
|
||||
m_flagStyle = flagStyle;
|
||||
m_argStyle = argStyle;
|
||||
}
|
||||
|
||||
// styles setter/getter
|
||||
void Parser::setArgumentStyle(ArgumentStyle::Enum style)
|
||||
{
|
||||
m_argStyle = style;
|
||||
m_argStyle = style;
|
||||
}
|
||||
ArgumentStyle::Enum Parser::argumentStyle()
|
||||
{
|
||||
return m_argStyle;
|
||||
return m_argStyle;
|
||||
}
|
||||
|
||||
void Parser::setFlagStyle(FlagStyle::Enum style)
|
||||
{
|
||||
m_flagStyle = style;
|
||||
m_flagStyle = style;
|
||||
}
|
||||
FlagStyle::Enum Parser::flagStyle()
|
||||
{
|
||||
return m_flagStyle;
|
||||
return m_flagStyle;
|
||||
}
|
||||
|
||||
// setup methods
|
||||
void Parser::addSwitch(QString name, bool def)
|
||||
{
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
|
||||
OptionDef *param = new OptionDef;
|
||||
param->type = otSwitch;
|
||||
param->name = name;
|
||||
param->metavar = QString("<%1>").arg(name);
|
||||
param->def = def;
|
||||
OptionDef *param = new OptionDef;
|
||||
param->type = otSwitch;
|
||||
param->name = name;
|
||||
param->metavar = QString("<%1>").arg(name);
|
||||
param->def = def;
|
||||
|
||||
m_options[name] = param;
|
||||
m_params[name] = (CommonDef *)param;
|
||||
m_optionList.append(param);
|
||||
m_options[name] = param;
|
||||
m_params[name] = (CommonDef *)param;
|
||||
m_optionList.append(param);
|
||||
}
|
||||
|
||||
void Parser::addOption(QString name, QVariant def)
|
||||
{
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
|
||||
OptionDef *param = new OptionDef;
|
||||
param->type = otOption;
|
||||
param->name = name;
|
||||
param->metavar = QString("<%1>").arg(name);
|
||||
param->def = def;
|
||||
OptionDef *param = new OptionDef;
|
||||
param->type = otOption;
|
||||
param->name = name;
|
||||
param->metavar = QString("<%1>").arg(name);
|
||||
param->def = def;
|
||||
|
||||
m_options[name] = param;
|
||||
m_params[name] = (CommonDef *)param;
|
||||
m_optionList.append(param);
|
||||
m_options[name] = param;
|
||||
m_params[name] = (CommonDef *)param;
|
||||
m_optionList.append(param);
|
||||
}
|
||||
|
||||
void Parser::addArgument(QString name, bool required, QVariant def)
|
||||
{
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
if (m_params.contains(name))
|
||||
throw "Name not unique";
|
||||
|
||||
PositionalDef *param = new PositionalDef;
|
||||
param->name = name;
|
||||
param->def = def;
|
||||
param->required = required;
|
||||
param->metavar = name;
|
||||
PositionalDef *param = new PositionalDef;
|
||||
param->name = name;
|
||||
param->def = def;
|
||||
param->required = required;
|
||||
param->metavar = name;
|
||||
|
||||
m_positionals.append(param);
|
||||
m_params[name] = (CommonDef *)param;
|
||||
m_positionals.append(param);
|
||||
m_params[name] = (CommonDef *)param;
|
||||
}
|
||||
|
||||
void Parser::addDocumentation(QString name, QString doc, QString metavar)
|
||||
{
|
||||
if (!m_params.contains(name))
|
||||
throw "Name does not exist";
|
||||
if (!m_params.contains(name))
|
||||
throw "Name does not exist";
|
||||
|
||||
CommonDef *param = m_params[name];
|
||||
param->doc = doc;
|
||||
if (!metavar.isNull())
|
||||
param->metavar = metavar;
|
||||
CommonDef *param = m_params[name];
|
||||
param->doc = doc;
|
||||
if (!metavar.isNull())
|
||||
param->metavar = metavar;
|
||||
}
|
||||
|
||||
void Parser::addShortOpt(QString name, QChar flag)
|
||||
{
|
||||
if (!m_params.contains(name))
|
||||
throw "Name does not exist";
|
||||
if (!m_options.contains(name))
|
||||
throw "Name is not an Option or Swtich";
|
||||
if (!m_params.contains(name))
|
||||
throw "Name does not exist";
|
||||
if (!m_options.contains(name))
|
||||
throw "Name is not an Option or Swtich";
|
||||
|
||||
OptionDef *param = m_options[name];
|
||||
m_flags[flag] = param;
|
||||
param->flag = flag;
|
||||
OptionDef *param = m_options[name];
|
||||
m_flags[flag] = param;
|
||||
param->flag = flag;
|
||||
}
|
||||
|
||||
// help methods
|
||||
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
|
||||
{
|
||||
QStringList help;
|
||||
help << compileUsage(progName, useFlags) << "\r\n";
|
||||
QStringList help;
|
||||
help << compileUsage(progName, useFlags) << "\r\n";
|
||||
|
||||
// positionals
|
||||
if (!m_positionals.isEmpty())
|
||||
{
|
||||
help << "\r\n";
|
||||
help << "Positional arguments:\r\n";
|
||||
QListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *param = it2.next();
|
||||
help << " " << param->metavar;
|
||||
help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
|
||||
help << param->doc << "\r\n";
|
||||
}
|
||||
}
|
||||
// positionals
|
||||
if (!m_positionals.isEmpty())
|
||||
{
|
||||
help << "\r\n";
|
||||
help << "Positional arguments:\r\n";
|
||||
QListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *param = it2.next();
|
||||
help << " " << param->metavar;
|
||||
help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
|
||||
help << param->doc << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Options
|
||||
if (!m_optionList.isEmpty())
|
||||
{
|
||||
help << "\r\n";
|
||||
QString optPrefix, flagPrefix;
|
||||
getPrefix(optPrefix, flagPrefix);
|
||||
// Options
|
||||
if (!m_optionList.isEmpty())
|
||||
{
|
||||
help << "\r\n";
|
||||
QString optPrefix, flagPrefix;
|
||||
getPrefix(optPrefix, flagPrefix);
|
||||
|
||||
help << "Options & Switches:\r\n";
|
||||
QListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
help << " ";
|
||||
int nameLength = optPrefix.length() + option->name.length();
|
||||
if (!option->flag.isNull())
|
||||
{
|
||||
nameLength += 3 + flagPrefix.length();
|
||||
help << flagPrefix << option->flag << ", ";
|
||||
}
|
||||
help << optPrefix << option->name;
|
||||
if (option->type == otOption)
|
||||
{
|
||||
QString arg = QString("%1%2").arg(
|
||||
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
|
||||
nameLength += arg.length();
|
||||
help << arg;
|
||||
}
|
||||
help << " " << QString(helpIndent - nameLength - 1, ' ');
|
||||
help << option->doc << "\r\n";
|
||||
}
|
||||
}
|
||||
help << "Options & Switches:\r\n";
|
||||
QListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
help << " ";
|
||||
int nameLength = optPrefix.length() + option->name.length();
|
||||
if (!option->flag.isNull())
|
||||
{
|
||||
nameLength += 3 + flagPrefix.length();
|
||||
help << flagPrefix << option->flag << ", ";
|
||||
}
|
||||
help << optPrefix << option->name;
|
||||
if (option->type == otOption)
|
||||
{
|
||||
QString arg = QString("%1%2").arg(
|
||||
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
|
||||
nameLength += arg.length();
|
||||
help << arg;
|
||||
}
|
||||
help << " " << QString(helpIndent - nameLength - 1, ' ');
|
||||
help << option->doc << "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
return help.join("");
|
||||
return help.join("");
|
||||
}
|
||||
|
||||
QString Parser::compileUsage(QString progName, bool useFlags)
|
||||
{
|
||||
QStringList usage;
|
||||
usage << "Usage: " << progName;
|
||||
QStringList usage;
|
||||
usage << "Usage: " << progName;
|
||||
|
||||
QString optPrefix, flagPrefix;
|
||||
getPrefix(optPrefix, flagPrefix);
|
||||
QString optPrefix, flagPrefix;
|
||||
getPrefix(optPrefix, flagPrefix);
|
||||
|
||||
// options
|
||||
QListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
usage << " [";
|
||||
if (!option->flag.isNull() && useFlags)
|
||||
usage << flagPrefix << option->flag;
|
||||
else
|
||||
usage << optPrefix << option->name;
|
||||
if (option->type == otOption)
|
||||
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
|
||||
usage << "]";
|
||||
}
|
||||
// options
|
||||
QListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
usage << " [";
|
||||
if (!option->flag.isNull() && useFlags)
|
||||
usage << flagPrefix << option->flag;
|
||||
else
|
||||
usage << optPrefix << option->name;
|
||||
if (option->type == otOption)
|
||||
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
|
||||
usage << "]";
|
||||
}
|
||||
|
||||
// arguments
|
||||
QListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *param = it2.next();
|
||||
usage << " " << (param->required ? "<" : "[");
|
||||
usage << param->metavar;
|
||||
usage << (param->required ? ">" : "]");
|
||||
}
|
||||
// arguments
|
||||
QListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *param = it2.next();
|
||||
usage << " " << (param->required ? "<" : "[");
|
||||
usage << param->metavar;
|
||||
usage << (param->required ? ">" : "]");
|
||||
}
|
||||
|
||||
return usage.join("");
|
||||
return usage.join("");
|
||||
}
|
||||
|
||||
// parsing
|
||||
QHash<QString, QVariant> Parser::parse(QStringList argv)
|
||||
{
|
||||
QHash<QString, QVariant> map;
|
||||
QHash<QString, QVariant> map;
|
||||
|
||||
QStringListIterator it(argv);
|
||||
QString programName = it.next();
|
||||
QStringListIterator it(argv);
|
||||
QString programName = it.next();
|
||||
|
||||
QString optionPrefix;
|
||||
QString flagPrefix;
|
||||
QListIterator<PositionalDef *> positionals(m_positionals);
|
||||
QStringList expecting;
|
||||
QString optionPrefix;
|
||||
QString flagPrefix;
|
||||
QListIterator<PositionalDef *> positionals(m_positionals);
|
||||
QStringList expecting;
|
||||
|
||||
getPrefix(optionPrefix, flagPrefix);
|
||||
getPrefix(optionPrefix, flagPrefix);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
QString arg = it.next();
|
||||
while (it.hasNext())
|
||||
{
|
||||
QString arg = it.next();
|
||||
|
||||
if (!expecting.isEmpty())
|
||||
// we were expecting an argument
|
||||
{
|
||||
QString name = expecting.first();
|
||||
if (!expecting.isEmpty())
|
||||
// we were expecting an argument
|
||||
{
|
||||
QString name = expecting.first();
|
||||
/*
|
||||
if (map.contains(name))
|
||||
throw ParsingError(
|
||||
QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
|
||||
if (map.contains(name))
|
||||
throw ParsingError(
|
||||
QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
|
||||
*/
|
||||
map[name] = QVariant(arg);
|
||||
map[name] = QVariant(arg);
|
||||
|
||||
expecting.removeFirst();
|
||||
continue;
|
||||
}
|
||||
expecting.removeFirst();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith(optionPrefix))
|
||||
// we have an option
|
||||
{
|
||||
// qDebug("Found option %s", qPrintable(arg));
|
||||
if (arg.startsWith(optionPrefix))
|
||||
// we have an option
|
||||
{
|
||||
// qDebug("Found option %s", qPrintable(arg));
|
||||
|
||||
QString name = arg.mid(optionPrefix.length());
|
||||
QString equals;
|
||||
QString name = arg.mid(optionPrefix.length());
|
||||
QString equals;
|
||||
|
||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
||||
name.contains("="))
|
||||
{
|
||||
int i = name.indexOf("=");
|
||||
equals = name.mid(i + 1);
|
||||
name = name.left(i);
|
||||
}
|
||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
||||
name.contains("="))
|
||||
{
|
||||
int i = name.indexOf("=");
|
||||
equals = name.mid(i + 1);
|
||||
name = name.left(i);
|
||||
}
|
||||
|
||||
if (m_options.contains(name))
|
||||
{
|
||||
/*
|
||||
if (map.contains(name))
|
||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
||||
.arg(name, optionPrefix));
|
||||
if (m_options.contains(name))
|
||||
{
|
||||
/*
|
||||
if (map.contains(name))
|
||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
||||
.arg(name, optionPrefix));
|
||||
*/
|
||||
OptionDef *option = m_options[name];
|
||||
if (option->type == otSwitch)
|
||||
map[name] = true;
|
||||
else // if (option->type == otOption)
|
||||
{
|
||||
if (m_argStyle == ArgumentStyle::Space)
|
||||
expecting.append(name);
|
||||
else if (!equals.isNull())
|
||||
map[name] = equals;
|
||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
||||
expecting.append(name);
|
||||
else
|
||||
throw ParsingError(QString("Option %2%1 reqires an argument.")
|
||||
.arg(name, optionPrefix));
|
||||
}
|
||||
OptionDef *option = m_options[name];
|
||||
if (option->type == otSwitch)
|
||||
map[name] = true;
|
||||
else // if (option->type == otOption)
|
||||
{
|
||||
if (m_argStyle == ArgumentStyle::Space)
|
||||
expecting.append(name);
|
||||
else if (!equals.isNull())
|
||||
map[name] = equals;
|
||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
||||
expecting.append(name);
|
||||
else
|
||||
throw ParsingError(QString("Option %2%1 reqires an argument.")
|
||||
.arg(name, optionPrefix));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
|
||||
}
|
||||
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
|
||||
}
|
||||
|
||||
if (arg.startsWith(flagPrefix))
|
||||
// we have (a) flag(s)
|
||||
{
|
||||
// qDebug("Found flags %s", qPrintable(arg));
|
||||
if (arg.startsWith(flagPrefix))
|
||||
// we have (a) flag(s)
|
||||
{
|
||||
// qDebug("Found flags %s", qPrintable(arg));
|
||||
|
||||
QString flags = arg.mid(flagPrefix.length());
|
||||
QString equals;
|
||||
QString flags = arg.mid(flagPrefix.length());
|
||||
QString equals;
|
||||
|
||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
||||
flags.contains("="))
|
||||
{
|
||||
int i = flags.indexOf("=");
|
||||
equals = flags.mid(i + 1);
|
||||
flags = flags.left(i);
|
||||
}
|
||||
if ((m_argStyle == ArgumentStyle::Equals ||
|
||||
m_argStyle == ArgumentStyle::SpaceAndEquals) &&
|
||||
flags.contains("="))
|
||||
{
|
||||
int i = flags.indexOf("=");
|
||||
equals = flags.mid(i + 1);
|
||||
flags = flags.left(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < flags.length(); i++)
|
||||
{
|
||||
QChar flag = flags.at(i);
|
||||
for (int i = 0; i < flags.length(); i++)
|
||||
{
|
||||
QChar flag = flags.at(i);
|
||||
|
||||
if (!m_flags.contains(flag))
|
||||
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
|
||||
if (!m_flags.contains(flag))
|
||||
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
|
||||
|
||||
OptionDef *option = m_flags[flag];
|
||||
OptionDef *option = m_flags[flag];
|
||||
/*
|
||||
if (map.contains(option->name))
|
||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
||||
.arg(option->name, optionPrefix));
|
||||
if (map.contains(option->name))
|
||||
throw ParsingError(QString("Option %2%1 was given multiple times")
|
||||
.arg(option->name, optionPrefix));
|
||||
*/
|
||||
if (option->type == otSwitch)
|
||||
map[option->name] = true;
|
||||
else // if (option->type == otOption)
|
||||
{
|
||||
if (m_argStyle == ArgumentStyle::Space)
|
||||
expecting.append(option->name);
|
||||
else if (!equals.isNull())
|
||||
if (i == flags.length() - 1)
|
||||
map[option->name] = equals;
|
||||
else
|
||||
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
|
||||
"%1 not last flag in %4%3")
|
||||
.arg(option->name, flag, flags, flagPrefix));
|
||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
||||
expecting.append(option->name);
|
||||
else
|
||||
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
|
||||
.arg(option->name, flag, flagPrefix));
|
||||
}
|
||||
}
|
||||
if (option->type == otSwitch)
|
||||
map[option->name] = true;
|
||||
else // if (option->type == otOption)
|
||||
{
|
||||
if (m_argStyle == ArgumentStyle::Space)
|
||||
expecting.append(option->name);
|
||||
else if (!equals.isNull())
|
||||
if (i == flags.length() - 1)
|
||||
map[option->name] = equals;
|
||||
else
|
||||
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
|
||||
"%1 not last flag in %4%3")
|
||||
.arg(option->name, flag, flags, flagPrefix));
|
||||
else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
|
||||
expecting.append(option->name);
|
||||
else
|
||||
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
|
||||
.arg(option->name, flag, flagPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// must be a positional argument
|
||||
if (!positionals.hasNext())
|
||||
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
|
||||
// must be a positional argument
|
||||
if (!positionals.hasNext())
|
||||
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
|
||||
|
||||
PositionalDef *param = positionals.next();
|
||||
PositionalDef *param = positionals.next();
|
||||
|
||||
map[param->name] = arg;
|
||||
}
|
||||
map[param->name] = arg;
|
||||
}
|
||||
|
||||
// check if we're missing something
|
||||
if (!expecting.isEmpty())
|
||||
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
|
||||
expecting.join(QString(", ") + optionPrefix), optionPrefix));
|
||||
// check if we're missing something
|
||||
if (!expecting.isEmpty())
|
||||
throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
|
||||
expecting.join(QString(", ") + optionPrefix), optionPrefix));
|
||||
|
||||
while (positionals.hasNext())
|
||||
{
|
||||
PositionalDef *param = positionals.next();
|
||||
if (param->required)
|
||||
throw ParsingError(
|
||||
QString("Missing required positional argument '%1'").arg(param->name));
|
||||
else
|
||||
map[param->name] = param->def;
|
||||
}
|
||||
while (positionals.hasNext())
|
||||
{
|
||||
PositionalDef *param = positionals.next();
|
||||
if (param->required)
|
||||
throw ParsingError(
|
||||
QString("Missing required positional argument '%1'").arg(param->name));
|
||||
else
|
||||
map[param->name] = param->def;
|
||||
}
|
||||
|
||||
// fill out gaps
|
||||
QListIterator<OptionDef *> iter(m_optionList);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
OptionDef *option = iter.next();
|
||||
if (!map.contains(option->name))
|
||||
map[option->name] = option->def;
|
||||
}
|
||||
// fill out gaps
|
||||
QListIterator<OptionDef *> iter(m_optionList);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
OptionDef *option = iter.next();
|
||||
if (!map.contains(option->name))
|
||||
map[option->name] = option->def;
|
||||
}
|
||||
|
||||
return map;
|
||||
return map;
|
||||
}
|
||||
|
||||
// clear defs
|
||||
void Parser::clear()
|
||||
{
|
||||
m_flags.clear();
|
||||
m_params.clear();
|
||||
m_options.clear();
|
||||
m_flags.clear();
|
||||
m_params.clear();
|
||||
m_options.clear();
|
||||
|
||||
QMutableListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
it.remove();
|
||||
delete option;
|
||||
}
|
||||
QMutableListIterator<OptionDef *> it(m_optionList);
|
||||
while (it.hasNext())
|
||||
{
|
||||
OptionDef *option = it.next();
|
||||
it.remove();
|
||||
delete option;
|
||||
}
|
||||
|
||||
QMutableListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *arg = it2.next();
|
||||
it2.remove();
|
||||
delete arg;
|
||||
}
|
||||
QMutableListIterator<PositionalDef *> it2(m_positionals);
|
||||
while (it2.hasNext())
|
||||
{
|
||||
PositionalDef *arg = it2.next();
|
||||
it2.remove();
|
||||
delete arg;
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Parser::~Parser()
|
||||
{
|
||||
clear();
|
||||
clear();
|
||||
}
|
||||
|
||||
// getPrefix
|
||||
void Parser::getPrefix(QString &opt, QString &flag)
|
||||
{
|
||||
if (m_flagStyle == FlagStyle::Windows)
|
||||
opt = flag = "/";
|
||||
else if (m_flagStyle == FlagStyle::Unix)
|
||||
opt = flag = "-";
|
||||
// else if (m_flagStyle == FlagStyle::GNU)
|
||||
else
|
||||
{
|
||||
opt = "--";
|
||||
flag = "-";
|
||||
}
|
||||
if (m_flagStyle == FlagStyle::Windows)
|
||||
opt = flag = "/";
|
||||
else if (m_flagStyle == FlagStyle::Unix)
|
||||
opt = flag = "-";
|
||||
// else if (m_flagStyle == FlagStyle::GNU)
|
||||
else
|
||||
{
|
||||
opt = "--";
|
||||
flag = "-";
|
||||
}
|
||||
}
|
||||
|
||||
// ParsingError
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -51,13 +51,13 @@ namespace FlagStyle
|
||||
{
|
||||
enum Enum
|
||||
{
|
||||
GNU, /**< --option and -o (GNU Style) */
|
||||
Unix, /**< -option and -o (Unix Style) */
|
||||
Windows, /**< /option and /o (Windows Style) */
|
||||
GNU, /**< --option and -o (GNU Style) */
|
||||
Unix, /**< -option and -o (Unix Style) */
|
||||
Windows, /**< /option and /o (Windows Style) */
|
||||
#ifdef Q_OS_WIN32
|
||||
Default = Windows
|
||||
Default = Windows
|
||||
#else
|
||||
Default = GNU
|
||||
Default = GNU
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@@ -69,13 +69,13 @@ namespace ArgumentStyle
|
||||
{
|
||||
enum Enum
|
||||
{
|
||||
Space, /**< --option=value */
|
||||
Equals, /**< --option value */
|
||||
SpaceAndEquals, /**< --option[= ]value */
|
||||
Space, /**< --option=value */
|
||||
Equals, /**< --option value */
|
||||
SpaceAndEquals, /**< --option[= ]value */
|
||||
#ifdef Q_OS_WIN32
|
||||
Default = Equals
|
||||
Default = Equals
|
||||
#else
|
||||
Default = SpaceAndEquals
|
||||
Default = SpaceAndEquals
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@@ -86,7 +86,7 @@ enum Enum
|
||||
class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
ParsingError(const QString &what);
|
||||
ParsingError(const QString &what);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -95,158 +95,158 @@ public:
|
||||
class MULTIMC_LOGIC_EXPORT Parser
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Parser constructor
|
||||
* @param flagStyle the FlagStyle to use in this Parser
|
||||
* @param argStyle the ArgumentStyle to use in this Parser
|
||||
*/
|
||||
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
|
||||
ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
|
||||
/**
|
||||
* @brief Parser constructor
|
||||
* @param flagStyle the FlagStyle to use in this Parser
|
||||
* @param argStyle the ArgumentStyle to use in this Parser
|
||||
*/
|
||||
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
|
||||
ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
|
||||
|
||||
/**
|
||||
* @brief set the flag style
|
||||
* @param style
|
||||
*/
|
||||
void setFlagStyle(FlagStyle::Enum style);
|
||||
/**
|
||||
* @brief set the flag style
|
||||
* @param style
|
||||
*/
|
||||
void setFlagStyle(FlagStyle::Enum style);
|
||||
|
||||
/**
|
||||
* @brief get the flag style
|
||||
* @return
|
||||
*/
|
||||
FlagStyle::Enum flagStyle();
|
||||
/**
|
||||
* @brief get the flag style
|
||||
* @return
|
||||
*/
|
||||
FlagStyle::Enum flagStyle();
|
||||
|
||||
/**
|
||||
* @brief set the argument style
|
||||
* @param style
|
||||
*/
|
||||
void setArgumentStyle(ArgumentStyle::Enum style);
|
||||
/**
|
||||
* @brief set the argument style
|
||||
* @param style
|
||||
*/
|
||||
void setArgumentStyle(ArgumentStyle::Enum style);
|
||||
|
||||
/**
|
||||
* @brief get the argument style
|
||||
* @return
|
||||
*/
|
||||
ArgumentStyle::Enum argumentStyle();
|
||||
/**
|
||||
* @brief get the argument style
|
||||
* @return
|
||||
*/
|
||||
ArgumentStyle::Enum argumentStyle();
|
||||
|
||||
/**
|
||||
* @brief define a boolean switch
|
||||
* @param name the parameter name
|
||||
* @param def the default value
|
||||
*/
|
||||
void addSwitch(QString name, bool def = false);
|
||||
/**
|
||||
* @brief define a boolean switch
|
||||
* @param name the parameter name
|
||||
* @param def the default value
|
||||
*/
|
||||
void addSwitch(QString name, bool def = false);
|
||||
|
||||
/**
|
||||
* @brief define an option that takes an additional argument
|
||||
* @param name the parameter name
|
||||
* @param def the default value
|
||||
*/
|
||||
void addOption(QString name, QVariant def = QVariant());
|
||||
/**
|
||||
* @brief define an option that takes an additional argument
|
||||
* @param name the parameter name
|
||||
* @param def the default value
|
||||
*/
|
||||
void addOption(QString name, QVariant def = QVariant());
|
||||
|
||||
/**
|
||||
* @brief define a positional argument
|
||||
* @param name the parameter name
|
||||
* @param required wether this argument is required
|
||||
* @param def the default value
|
||||
*/
|
||||
void addArgument(QString name, bool required = true, QVariant def = QVariant());
|
||||
/**
|
||||
* @brief define a positional argument
|
||||
* @param name the parameter name
|
||||
* @param required wether this argument is required
|
||||
* @param def the default value
|
||||
*/
|
||||
void addArgument(QString name, bool required = true, QVariant def = QVariant());
|
||||
|
||||
/**
|
||||
* @brief adds a flag to an existing parameter
|
||||
* @param name the (existing) parameter name
|
||||
* @param flag the flag character
|
||||
* @see addSwitch addArgument addOption
|
||||
* Note: any one parameter can only have one flag
|
||||
*/
|
||||
void addShortOpt(QString name, QChar flag);
|
||||
/**
|
||||
* @brief adds a flag to an existing parameter
|
||||
* @param name the (existing) parameter name
|
||||
* @param flag the flag character
|
||||
* @see addSwitch addArgument addOption
|
||||
* Note: any one parameter can only have one flag
|
||||
*/
|
||||
void addShortOpt(QString name, QChar flag);
|
||||
|
||||
/**
|
||||
* @brief adds documentation to a Parameter
|
||||
* @param name the parameter name
|
||||
* @param metavar a string to be displayed as placeholder for the value
|
||||
* @param doc a QString containing the documentation
|
||||
* Note: on positional arguments, metavar replaces the name as displayed.
|
||||
* on options , metavar replaces the value placeholder
|
||||
*/
|
||||
void addDocumentation(QString name, QString doc, QString metavar = QString());
|
||||
/**
|
||||
* @brief adds documentation to a Parameter
|
||||
* @param name the parameter name
|
||||
* @param metavar a string to be displayed as placeholder for the value
|
||||
* @param doc a QString containing the documentation
|
||||
* Note: on positional arguments, metavar replaces the name as displayed.
|
||||
* on options , metavar replaces the value placeholder
|
||||
*/
|
||||
void addDocumentation(QString name, QString doc, QString metavar = QString());
|
||||
|
||||
/**
|
||||
* @brief generate a help message
|
||||
* @param progName the program name to use in the help message
|
||||
* @param helpIndent how much the parameter documentation should be indented
|
||||
* @param flagsInUsage whether we should use flags instead of options in the usage
|
||||
* @return a help message
|
||||
*/
|
||||
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
|
||||
/**
|
||||
* @brief generate a help message
|
||||
* @param progName the program name to use in the help message
|
||||
* @param helpIndent how much the parameter documentation should be indented
|
||||
* @param flagsInUsage whether we should use flags instead of options in the usage
|
||||
* @return a help message
|
||||
*/
|
||||
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
|
||||
|
||||
/**
|
||||
* @brief generate a short usage message
|
||||
* @param progName the program name to use in the usage message
|
||||
* @param useFlags whether we should use flags instead of options
|
||||
* @return a usage message
|
||||
*/
|
||||
QString compileUsage(QString progName, bool useFlags = true);
|
||||
/**
|
||||
* @brief generate a short usage message
|
||||
* @param progName the program name to use in the usage message
|
||||
* @param useFlags whether we should use flags instead of options
|
||||
* @return a usage message
|
||||
*/
|
||||
QString compileUsage(QString progName, bool useFlags = true);
|
||||
|
||||
/**
|
||||
* @brief parse
|
||||
* @param argv a QStringList containing the program ARGV
|
||||
* @return a QHash mapping argument names to their values
|
||||
*/
|
||||
QHash<QString, QVariant> parse(QStringList argv);
|
||||
/**
|
||||
* @brief parse
|
||||
* @param argv a QStringList containing the program ARGV
|
||||
* @return a QHash mapping argument names to their values
|
||||
*/
|
||||
QHash<QString, QVariant> parse(QStringList argv);
|
||||
|
||||
/**
|
||||
* @brief clear all definitions
|
||||
*/
|
||||
void clear();
|
||||
/**
|
||||
* @brief clear all definitions
|
||||
*/
|
||||
void clear();
|
||||
|
||||
~Parser();
|
||||
~Parser();
|
||||
|
||||
private:
|
||||
FlagStyle::Enum m_flagStyle;
|
||||
ArgumentStyle::Enum m_argStyle;
|
||||
FlagStyle::Enum m_flagStyle;
|
||||
ArgumentStyle::Enum m_argStyle;
|
||||
|
||||
enum OptionType
|
||||
{
|
||||
otSwitch,
|
||||
otOption
|
||||
};
|
||||
enum OptionType
|
||||
{
|
||||
otSwitch,
|
||||
otOption
|
||||
};
|
||||
|
||||
// Important: the common part MUST BE COMMON ON ALL THREE structs
|
||||
struct CommonDef
|
||||
{
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
};
|
||||
// Important: the common part MUST BE COMMON ON ALL THREE structs
|
||||
struct CommonDef
|
||||
{
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
};
|
||||
|
||||
struct OptionDef
|
||||
{
|
||||
// common
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
// option
|
||||
OptionType type;
|
||||
QChar flag;
|
||||
};
|
||||
struct OptionDef
|
||||
{
|
||||
// common
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
// option
|
||||
OptionType type;
|
||||
QChar flag;
|
||||
};
|
||||
|
||||
struct PositionalDef
|
||||
{
|
||||
// common
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
// positional
|
||||
bool required;
|
||||
};
|
||||
struct PositionalDef
|
||||
{
|
||||
// common
|
||||
QString name;
|
||||
QString doc;
|
||||
QString metavar;
|
||||
QVariant def;
|
||||
// positional
|
||||
bool required;
|
||||
};
|
||||
|
||||
QHash<QString, OptionDef *> m_options;
|
||||
QHash<QChar, OptionDef *> m_flags;
|
||||
QHash<QString, CommonDef *> m_params;
|
||||
QList<PositionalDef *> m_positionals;
|
||||
QList<OptionDef *> m_optionList;
|
||||
QHash<QString, OptionDef *> m_options;
|
||||
QHash<QChar, OptionDef *> m_flags;
|
||||
QHash<QString, CommonDef *> m_params;
|
||||
QList<PositionalDef *> m_positionals;
|
||||
QList<OptionDef *> m_optionList;
|
||||
|
||||
void getPrefix(QString &opt, QString &flag);
|
||||
void getPrefix(QString &opt, QString &flag);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,32 +4,32 @@ template <typename T>
|
||||
class DefaultVariable
|
||||
{
|
||||
public:
|
||||
DefaultVariable(const T & value)
|
||||
{
|
||||
defaultValue = value;
|
||||
}
|
||||
DefaultVariable<T> & operator =(const T & value)
|
||||
{
|
||||
currentValue = value;
|
||||
is_default = currentValue == defaultValue;
|
||||
is_explicit = true;
|
||||
return *this;
|
||||
}
|
||||
operator const T &() const
|
||||
{
|
||||
return is_default ? defaultValue : currentValue;
|
||||
}
|
||||
bool isDefault() const
|
||||
{
|
||||
return is_default;
|
||||
}
|
||||
bool isExplicit() const
|
||||
{
|
||||
return is_explicit;
|
||||
}
|
||||
DefaultVariable(const T & value)
|
||||
{
|
||||
defaultValue = value;
|
||||
}
|
||||
DefaultVariable<T> & operator =(const T & value)
|
||||
{
|
||||
currentValue = value;
|
||||
is_default = currentValue == defaultValue;
|
||||
is_explicit = true;
|
||||
return *this;
|
||||
}
|
||||
operator const T &() const
|
||||
{
|
||||
return is_default ? defaultValue : currentValue;
|
||||
}
|
||||
bool isDefault() const
|
||||
{
|
||||
return is_default;
|
||||
}
|
||||
bool isExplicit() const
|
||||
{
|
||||
return is_explicit;
|
||||
}
|
||||
private:
|
||||
T currentValue;
|
||||
T defaultValue;
|
||||
bool is_default = true;
|
||||
bool is_explicit = false;
|
||||
T currentValue;
|
||||
T defaultValue;
|
||||
bool is_default = true;
|
||||
bool is_explicit = false;
|
||||
};
|
||||
|
||||
@@ -3,23 +3,24 @@
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include "tasks/Task.h"
|
||||
#include "wonko/WonkoIndex.h"
|
||||
#include "meta/Index.h"
|
||||
#include "FileSystem.h"
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
class Env::Private
|
||||
struct Env::Private
|
||||
{
|
||||
public:
|
||||
QNetworkAccessManager m_qnam;
|
||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||
std::shared_ptr<IIconList> m_iconlist;
|
||||
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
|
||||
shared_qobject_ptr<WonkoIndex> m_wonkoIndex;
|
||||
QString m_wonkoRootUrl;
|
||||
QNetworkAccessManager m_qnam;
|
||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||
std::shared_ptr<IIconList> m_iconlist;
|
||||
shared_qobject_ptr<Meta::Index> m_metadataIndex;
|
||||
QString m_jarsPath;
|
||||
QSet<QString> m_features;
|
||||
};
|
||||
|
||||
static Env * instance;
|
||||
@@ -30,175 +31,179 @@ static Env * instance;
|
||||
|
||||
Env::Env()
|
||||
{
|
||||
d = new Private();
|
||||
d = new Private();
|
||||
}
|
||||
|
||||
Env::~Env()
|
||||
{
|
||||
delete d;
|
||||
delete d;
|
||||
}
|
||||
|
||||
Env& Env::Env::getInstance()
|
||||
{
|
||||
if(!instance)
|
||||
{
|
||||
instance = new Env();
|
||||
}
|
||||
return *instance;
|
||||
if(!instance)
|
||||
{
|
||||
instance = new Env();
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void Env::dispose()
|
||||
{
|
||||
delete instance;
|
||||
instance = nullptr;
|
||||
delete instance;
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
shared_qobject_ptr< HttpMetaCache > Env::metacache()
|
||||
{
|
||||
return d->m_metacache;
|
||||
return d->m_metacache;
|
||||
}
|
||||
|
||||
QNetworkAccessManager& Env::qnam() const
|
||||
{
|
||||
return d->m_qnam;
|
||||
return d->m_qnam;
|
||||
}
|
||||
|
||||
std::shared_ptr<IIconList> Env::icons()
|
||||
{
|
||||
return d->m_iconlist;
|
||||
return d->m_iconlist;
|
||||
}
|
||||
|
||||
void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
|
||||
{
|
||||
d->m_iconlist = iconlist;
|
||||
d->m_iconlist = iconlist;
|
||||
}
|
||||
|
||||
BaseVersionPtr Env::getVersion(QString component, QString version)
|
||||
shared_qobject_ptr<Meta::Index> Env::metadataIndex()
|
||||
{
|
||||
auto list = getVersionList(component);
|
||||
if(!list)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return list->findVersion(version);
|
||||
}
|
||||
|
||||
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
|
||||
{
|
||||
auto iter = d->m_versionLists.find(component);
|
||||
if(iter != d->m_versionLists.end())
|
||||
{
|
||||
return *iter;
|
||||
}
|
||||
//return std::make_shared<NullVersionList>();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
|
||||
{
|
||||
d->m_versionLists[name] = vlist;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<WonkoIndex> Env::wonkoIndex()
|
||||
{
|
||||
if (!d->m_wonkoIndex)
|
||||
{
|
||||
d->m_wonkoIndex.reset(new WonkoIndex());
|
||||
}
|
||||
return d->m_wonkoIndex;
|
||||
if (!d->m_metadataIndex)
|
||||
{
|
||||
d->m_metadataIndex.reset(new Meta::Index());
|
||||
}
|
||||
return d->m_metadataIndex;
|
||||
}
|
||||
|
||||
|
||||
void Env::initHttpMetaCache()
|
||||
{
|
||||
auto &m_metacache = d->m_metacache;
|
||||
m_metacache.reset(new HttpMetaCache("metacache"));
|
||||
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
||||
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
|
||||
m_metacache->addBase("versions", QDir("versions").absolutePath());
|
||||
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
||||
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir::currentPath());
|
||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
|
||||
m_metacache->Load();
|
||||
auto &m_metacache = d->m_metacache;
|
||||
m_metacache.reset(new HttpMetaCache("metacache"));
|
||||
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
|
||||
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
|
||||
m_metacache->addBase("versions", QDir("versions").absolutePath());
|
||||
m_metacache->addBase("libraries", QDir("libraries").absolutePath());
|
||||
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
|
||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir::currentPath());
|
||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
||||
m_metacache->Load();
|
||||
}
|
||||
|
||||
void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password)
|
||||
{
|
||||
// Set the application proxy settings.
|
||||
if (proxyTypeStr == "SOCKS5")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "HTTP")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "None")
|
||||
{
|
||||
// If we have no proxy set, set no proxy and return.
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have "Default" selected, set Qt to use the system proxy settings.
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
}
|
||||
// Set the application proxy settings.
|
||||
if (proxyTypeStr == "SOCKS5")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "HTTP")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
|
||||
}
|
||||
else if (proxyTypeStr == "None")
|
||||
{
|
||||
// If we have no proxy set, set no proxy and return.
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we have "Default" selected, set Qt to use the system proxy settings.
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
}
|
||||
|
||||
qDebug() << "Detecting proxy settings...";
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
|
||||
d->m_qnam.setProxy(proxy);
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
{
|
||||
qDebug() << "Using no proxy is an option!";
|
||||
return;
|
||||
}
|
||||
switch (proxy.type())
|
||||
{
|
||||
case QNetworkProxy::DefaultProxy:
|
||||
proxyDesc = "Default proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::Socks5Proxy:
|
||||
proxyDesc = "Socks5 proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpProxy:
|
||||
proxyDesc = "HTTP proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpCachingProxy:
|
||||
proxyDesc = "HTTP caching: ";
|
||||
break;
|
||||
case QNetworkProxy::FtpCachingProxy:
|
||||
proxyDesc = "FTP caching: ";
|
||||
break;
|
||||
default:
|
||||
proxyDesc = "DERP proxy: ";
|
||||
break;
|
||||
}
|
||||
proxyDesc += QString("%3@%1:%2 pass %4")
|
||||
.arg(proxy.hostName())
|
||||
.arg(proxy.port())
|
||||
.arg(proxy.user())
|
||||
.arg(proxy.password());
|
||||
qDebug() << proxyDesc;
|
||||
qDebug() << "Detecting proxy settings...";
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
|
||||
d->m_qnam.setProxy(proxy);
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
{
|
||||
qDebug() << "Using no proxy is an option!";
|
||||
return;
|
||||
}
|
||||
switch (proxy.type())
|
||||
{
|
||||
case QNetworkProxy::DefaultProxy:
|
||||
proxyDesc = "Default proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::Socks5Proxy:
|
||||
proxyDesc = "Socks5 proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpProxy:
|
||||
proxyDesc = "HTTP proxy: ";
|
||||
break;
|
||||
case QNetworkProxy::HttpCachingProxy:
|
||||
proxyDesc = "HTTP caching: ";
|
||||
break;
|
||||
case QNetworkProxy::FtpCachingProxy:
|
||||
proxyDesc = "FTP caching: ";
|
||||
break;
|
||||
default:
|
||||
proxyDesc = "DERP proxy: ";
|
||||
break;
|
||||
}
|
||||
proxyDesc += QString("%3@%1:%2 pass %4")
|
||||
.arg(proxy.hostName())
|
||||
.arg(proxy.port())
|
||||
.arg(proxy.user())
|
||||
.arg(proxy.password());
|
||||
qDebug() << proxyDesc;
|
||||
}
|
||||
|
||||
QString Env::wonkoRootUrl() const
|
||||
QString Env::getJarsPath()
|
||||
{
|
||||
return d->m_wonkoRootUrl;
|
||||
if(d->m_jarsPath.isEmpty())
|
||||
{
|
||||
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
|
||||
}
|
||||
return d->m_jarsPath;
|
||||
}
|
||||
|
||||
void Env::setWonkoRootUrl(const QString& url)
|
||||
void Env::setJarsPath(const QString& path)
|
||||
{
|
||||
d->m_wonkoRootUrl = url;
|
||||
d->m_jarsPath = path;
|
||||
}
|
||||
|
||||
#include "Env.moc"
|
||||
void Env::enableFeature(const QString& featureName, bool state)
|
||||
{
|
||||
if(state)
|
||||
{
|
||||
d->m_features.insert(featureName);
|
||||
}
|
||||
else
|
||||
{
|
||||
d->m_features.remove(featureName);
|
||||
}
|
||||
}
|
||||
|
||||
bool Env::isFeatureEnabled(const QString& featureName) const
|
||||
{
|
||||
return d->m_features.contains(featureName);
|
||||
}
|
||||
|
||||
void Env::getEnabledFeatures(QSet<QString>& features) const
|
||||
{
|
||||
features = d->m_features;
|
||||
}
|
||||
|
||||
void Env::setEnabledFeatures(const QSet<QString>& features) const
|
||||
{
|
||||
d->m_features = features;
|
||||
}
|
||||
|
||||
@@ -13,51 +13,53 @@ class QNetworkAccessManager;
|
||||
class HttpMetaCache;
|
||||
class BaseVersionList;
|
||||
class BaseVersion;
|
||||
class WonkoIndex;
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
class Index;
|
||||
}
|
||||
|
||||
#if defined(ENV)
|
||||
#undef ENV
|
||||
#undef ENV
|
||||
#endif
|
||||
#define ENV (Env::getInstance())
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Env
|
||||
{
|
||||
friend class MultiMC;
|
||||
friend class MultiMC;
|
||||
private:
|
||||
class Private;
|
||||
Env();
|
||||
~Env();
|
||||
static void dispose();
|
||||
struct Private;
|
||||
Env();
|
||||
~Env();
|
||||
static void dispose();
|
||||
public:
|
||||
static Env& getInstance();
|
||||
static Env& getInstance();
|
||||
|
||||
QNetworkAccessManager &qnam() const;
|
||||
QNetworkAccessManager &qnam() const;
|
||||
|
||||
shared_qobject_ptr<HttpMetaCache> metacache();
|
||||
shared_qobject_ptr<HttpMetaCache> metacache();
|
||||
|
||||
std::shared_ptr<IIconList> icons();
|
||||
std::shared_ptr<IIconList> icons();
|
||||
|
||||
/// init the cache. FIXME: possible future hook point
|
||||
void initHttpMetaCache();
|
||||
/// init the cache. FIXME: possible future hook point
|
||||
void initHttpMetaCache();
|
||||
|
||||
/// Updates the application proxy settings from the settings object.
|
||||
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
|
||||
/// Updates the application proxy settings from the settings object.
|
||||
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
|
||||
|
||||
/// get a version list by name
|
||||
std::shared_ptr<BaseVersionList> getVersionList(QString component);
|
||||
void registerIconList(std::shared_ptr<IIconList> iconlist);
|
||||
|
||||
/// get a version by list name and version name
|
||||
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
|
||||
shared_qobject_ptr<Meta::Index> metadataIndex();
|
||||
|
||||
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
|
||||
QString getJarsPath();
|
||||
void setJarsPath(const QString & path);
|
||||
|
||||
void registerIconList(std::shared_ptr<IIconList> iconlist);
|
||||
|
||||
shared_qobject_ptr<WonkoIndex> wonkoIndex();
|
||||
|
||||
QString wonkoRootUrl() const;
|
||||
void setWonkoRootUrl(const QString &url);
|
||||
bool isFeatureEnabled(const QString & featureName) const;
|
||||
void enableFeature(const QString & featureName, bool state = true);
|
||||
void getEnabledFeatures(QSet<QString> & features) const;
|
||||
void setEnabledFeatures(const QSet<QString> & features) const;
|
||||
|
||||
protected:
|
||||
Private * d;
|
||||
Private * d;
|
||||
};
|
||||
|
||||
@@ -11,24 +11,24 @@
|
||||
class MULTIMC_LOGIC_EXPORT Exception : public std::exception
|
||||
{
|
||||
public:
|
||||
Exception(const QString &message) : std::exception(), m_message(message)
|
||||
{
|
||||
qCritical() << "Exception:" << message;
|
||||
}
|
||||
Exception(const Exception &other)
|
||||
: std::exception(), m_message(other.cause())
|
||||
{
|
||||
}
|
||||
virtual ~Exception() noexcept {}
|
||||
const char *what() const noexcept
|
||||
{
|
||||
return m_message.toLatin1().constData();
|
||||
}
|
||||
QString cause() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
Exception(const QString &message) : std::exception(), m_message(message)
|
||||
{
|
||||
qCritical() << "Exception:" << message;
|
||||
}
|
||||
Exception(const Exception &other)
|
||||
: std::exception(), m_message(other.cause())
|
||||
{
|
||||
}
|
||||
virtual ~Exception() noexcept {}
|
||||
const char *what() const noexcept
|
||||
{
|
||||
return m_message.toLatin1().constData();
|
||||
}
|
||||
QString cause() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
QString m_message;
|
||||
};
|
||||
|
||||
43
api/logic/ExponentialSeries.h
Normal file
43
api/logic/ExponentialSeries.h
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
template <typename T>
|
||||
inline void clamp(T& current, T min, T max)
|
||||
{
|
||||
if (current < min)
|
||||
{
|
||||
current = min;
|
||||
}
|
||||
else if(current > max)
|
||||
{
|
||||
current = max;
|
||||
}
|
||||
}
|
||||
|
||||
// List of numbers from min to max. Next is exponent times bigger than previous.
|
||||
|
||||
class ExponentialSeries
|
||||
{
|
||||
public:
|
||||
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
|
||||
{
|
||||
m_current = m_min = min;
|
||||
m_max = max;
|
||||
m_exponent = exponent;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_current = m_min;
|
||||
}
|
||||
unsigned operator()()
|
||||
{
|
||||
unsigned retval = m_current;
|
||||
m_current *= m_exponent;
|
||||
clamp(m_current, m_min, m_max);
|
||||
return retval;
|
||||
}
|
||||
unsigned m_current;
|
||||
unsigned m_min;
|
||||
unsigned m_max;
|
||||
unsigned m_exponent;
|
||||
};
|
||||
@@ -3,263 +3,271 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <sys/utime.h>
|
||||
#include <winnls.h>
|
||||
#include <shobjidl.h>
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
namespace FS {
|
||||
|
||||
void ensureExists(const QDir &dir)
|
||||
{
|
||||
if (!QDir().mkpath(dir.absolutePath()))
|
||||
{
|
||||
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
|
||||
dir.absolutePath() + ")");
|
||||
}
|
||||
if (!QDir().mkpath(dir.absolutePath()))
|
||||
{
|
||||
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
|
||||
dir.absolutePath() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
void write(const QString &filename, const QByteArray &data)
|
||||
{
|
||||
ensureExists(QFileInfo(filename).dir());
|
||||
QSaveFile file(filename);
|
||||
if (!file.open(QSaveFile::WriteOnly))
|
||||
{
|
||||
throw FileSystemException("Couldn't open " + filename + " for writing: " +
|
||||
file.errorString());
|
||||
}
|
||||
if (data.size() != file.write(data))
|
||||
{
|
||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
if (!file.commit())
|
||||
{
|
||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
ensureExists(QFileInfo(filename).dir());
|
||||
QSaveFile file(filename);
|
||||
if (!file.open(QSaveFile::WriteOnly))
|
||||
{
|
||||
throw FileSystemException("Couldn't open " + filename + " for writing: " +
|
||||
file.errorString());
|
||||
}
|
||||
if (data.size() != file.write(data))
|
||||
{
|
||||
throw FileSystemException("Error writing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
if (!file.commit())
|
||||
{
|
||||
throw FileSystemException("Error while committing data to " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray read(const QString &filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
||||
file.errorString());
|
||||
}
|
||||
const qint64 size = file.size();
|
||||
QByteArray data(int(size), 0);
|
||||
const qint64 ret = file.read(data.data(), size);
|
||||
if (ret == -1 || ret != size)
|
||||
{
|
||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
return data;
|
||||
QFile file(filename);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
{
|
||||
throw FileSystemException("Unable to open " + filename + " for reading: " +
|
||||
file.errorString());
|
||||
}
|
||||
const qint64 size = file.size();
|
||||
QByteArray data(int(size), 0);
|
||||
const qint64 ret = file.read(data.data(), size);
|
||||
if (ret == -1 || ret != size)
|
||||
{
|
||||
throw FileSystemException("Error reading data from " + filename + ": " +
|
||||
file.errorString());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
bool updateTimestamp(const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadWrite))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const quint64 size = file.size();
|
||||
file.seek(size);
|
||||
file.write( QByteArray(1, '0') );
|
||||
file.resize(size);
|
||||
return true;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
std::wstring filename_utf_16 = filename.toStdWString();
|
||||
return (_wutime64(filename_utf_16.c_str(), nullptr) == 0);
|
||||
#else
|
||||
QByteArray filenameBA = QFile::encodeName(filename);
|
||||
return (utime(filenameBA.data(), nullptr) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ensureFilePathExists(QString filenamepath)
|
||||
{
|
||||
QFileInfo a(filenamepath);
|
||||
QDir dir;
|
||||
QString ensuredPath = a.path();
|
||||
bool success = dir.mkpath(ensuredPath);
|
||||
return success;
|
||||
QFileInfo a(filenamepath);
|
||||
QDir dir;
|
||||
QString ensuredPath = a.path();
|
||||
bool success = dir.mkpath(ensuredPath);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ensureFolderPathExists(QString foldernamepath)
|
||||
{
|
||||
QFileInfo a(foldernamepath);
|
||||
QDir dir;
|
||||
QString ensuredPath = a.filePath();
|
||||
bool success = dir.mkpath(ensuredPath);
|
||||
return success;
|
||||
QFileInfo a(foldernamepath);
|
||||
QDir dir;
|
||||
QString ensuredPath = a.filePath();
|
||||
bool success = dir.mkpath(ensuredPath);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool copy::operator()(const QString &offset)
|
||||
{
|
||||
//NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
m_followSymlinks = true;
|
||||
#endif
|
||||
//NOTE always deep copy on windows. the alternatives are too messy.
|
||||
#if defined Q_OS_WIN32
|
||||
m_followSymlinks = true;
|
||||
#endif
|
||||
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
|
||||
QFileInfo currentSrc(src);
|
||||
if (!currentSrc.exists())
|
||||
return false;
|
||||
QFileInfo currentSrc(src);
|
||||
if (!currentSrc.exists())
|
||||
return false;
|
||||
|
||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
||||
{
|
||||
qDebug() << "creating symlink" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
||||
}
|
||||
else if(currentSrc.isFile())
|
||||
{
|
||||
qDebug() << "copying file" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::copy(src, dst);
|
||||
}
|
||||
else if(currentSrc.isDir())
|
||||
{
|
||||
qDebug() << "recursing" << offset;
|
||||
if (!ensureFolderPathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
QDir currentDir(src);
|
||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
{
|
||||
auto inner_offset = PathCombine(offset, f);
|
||||
// ignore and skip stuff that matches the blacklist.
|
||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!operator()(inner_offset))
|
||||
{
|
||||
qWarning() << "Failed to copy" << inner_offset;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if(!m_followSymlinks && currentSrc.isSymLink())
|
||||
{
|
||||
qDebug() << "creating symlink" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::link(currentSrc.symLinkTarget(), dst);
|
||||
}
|
||||
else if(currentSrc.isFile())
|
||||
{
|
||||
qDebug() << "copying file" << src << " - " << dst;
|
||||
if (!ensureFilePathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
return QFile::copy(src, dst);
|
||||
}
|
||||
else if(currentSrc.isDir())
|
||||
{
|
||||
qDebug() << "recursing" << offset;
|
||||
if (!ensureFolderPathExists(dst))
|
||||
{
|
||||
qWarning() << "Cannot create path!";
|
||||
return false;
|
||||
}
|
||||
QDir currentDir(src);
|
||||
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
|
||||
{
|
||||
auto inner_offset = PathCombine(offset, f);
|
||||
// ignore and skip stuff that matches the blacklist.
|
||||
if(m_blacklist && m_blacklist->matches(inner_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!operator()(inner_offset))
|
||||
{
|
||||
qWarning() << "Failed to copy" << inner_offset;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#endif
|
||||
bool deletePath(QString path)
|
||||
{
|
||||
bool OK = true;
|
||||
QDir dir(path);
|
||||
bool OK = true;
|
||||
QDir dir(path);
|
||||
|
||||
if (!dir.exists())
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
||||
QDir::AllDirs | QDir::Files,
|
||||
QDir::DirsFirst);
|
||||
if (!dir.exists())
|
||||
{
|
||||
return OK;
|
||||
}
|
||||
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
|
||||
QDir::AllDirs | QDir::Files,
|
||||
QDir::DirsFirst);
|
||||
|
||||
for(auto & info: allEntries)
|
||||
{
|
||||
for(auto & info: allEntries)
|
||||
{
|
||||
#if defined Q_OS_WIN32
|
||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
||||
auto wString = nativePath.toStdWString();
|
||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
{
|
||||
if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= dir.rmdir(info.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
|
||||
auto wString = nativePath.toStdWString();
|
||||
DWORD dwAttrs = GetFileAttributesW(wString.c_str());
|
||||
// Windows: check for junctions, reparse points and other nasty things of that sort
|
||||
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
{
|
||||
if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= dir.rmdir(info.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
#else
|
||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
||||
if(info.isSymLink())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
// We do not trust Qt with reparse points, but do trust it with unix symlinks.
|
||||
if(info.isSymLink())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
#endif
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= deletePath(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
OK = false;
|
||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
OK &= dir.rmdir(dir.absolutePath());
|
||||
return OK;
|
||||
else if (info.isDir())
|
||||
{
|
||||
OK &= deletePath(info.absoluteFilePath());
|
||||
}
|
||||
else if (info.isFile())
|
||||
{
|
||||
OK &= QFile::remove(info.absoluteFilePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
OK = false;
|
||||
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
OK &= dir.rmdir(dir.absolutePath());
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
QString PathCombine(QString path1, QString path2)
|
||||
QString PathCombine(const QString & path1, const QString & path2)
|
||||
{
|
||||
if(!path1.size())
|
||||
return path2;
|
||||
if(!path2.size())
|
||||
return path1;
|
||||
if(!path1.size())
|
||||
return path2;
|
||||
if(!path2.size())
|
||||
return path1;
|
||||
return QDir::cleanPath(path1 + QDir::separator() + path2);
|
||||
}
|
||||
|
||||
QString PathCombine(QString path1, QString path2, QString path3)
|
||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
|
||||
{
|
||||
return PathCombine(PathCombine(path1, path2), path3);
|
||||
return PathCombine(PathCombine(path1, path2), path3);
|
||||
}
|
||||
|
||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
|
||||
{
|
||||
return PathCombine(PathCombine(path1, path2, path3), path4);
|
||||
}
|
||||
|
||||
QString AbsolutePath(QString path)
|
||||
{
|
||||
return QFileInfo(path).absolutePath();
|
||||
return QFileInfo(path).absolutePath();
|
||||
}
|
||||
|
||||
QString ResolveExecutable(QString path)
|
||||
{
|
||||
if (path.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
if(!path.contains('/'))
|
||||
{
|
||||
path = QStandardPaths::findExecutable(path);
|
||||
}
|
||||
QFileInfo pathInfo(path);
|
||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return pathInfo.absoluteFilePath();
|
||||
if (path.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
if(!path.contains('/'))
|
||||
{
|
||||
path = QStandardPaths::findExecutable(path);
|
||||
}
|
||||
QFileInfo pathInfo(path);
|
||||
if(!pathInfo.exists() || !pathInfo.isExecutable())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return pathInfo.absoluteFilePath();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,187 +278,175 @@ QString ResolveExecutable(QString path)
|
||||
*/
|
||||
QString NormalizePath(QString path)
|
||||
{
|
||||
QDir a = QDir::currentPath();
|
||||
QString currentAbsolute = a.absolutePath();
|
||||
QDir a = QDir::currentPath();
|
||||
QString currentAbsolute = a.absolutePath();
|
||||
|
||||
QDir b(path);
|
||||
QString newAbsolute = b.absolutePath();
|
||||
QDir b(path);
|
||||
QString newAbsolute = b.absolutePath();
|
||||
|
||||
if (newAbsolute.startsWith(currentAbsolute))
|
||||
{
|
||||
return a.relativeFilePath(newAbsolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
return newAbsolute;
|
||||
}
|
||||
if (newAbsolute.startsWith(currentAbsolute))
|
||||
{
|
||||
return a.relativeFilePath(newAbsolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
return newAbsolute;
|
||||
}
|
||||
}
|
||||
|
||||
QString badFilenameChars = "\"\\/?<>:*|!";
|
||||
QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
|
||||
|
||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||
{
|
||||
for (int i = 0; i < string.length(); i++)
|
||||
{
|
||||
if (badFilenameChars.contains(string[i]))
|
||||
{
|
||||
string[i] = replaceWith;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
for (int i = 0; i < string.length(); i++)
|
||||
{
|
||||
if (badFilenameChars.contains(string[i]))
|
||||
{
|
||||
string[i] = replaceWith;
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
QString DirNameFromString(QString string, QString inDir)
|
||||
{
|
||||
int num = 0;
|
||||
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
||||
QString dirName;
|
||||
do
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirName = baseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirName = baseName + QString::number(num);;
|
||||
}
|
||||
int num = 0;
|
||||
QString baseName = RemoveInvalidFilenameChars(string, '-');
|
||||
QString dirName;
|
||||
do
|
||||
{
|
||||
if(num == 0)
|
||||
{
|
||||
dirName = baseName;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirName = baseName + QString::number(num);
|
||||
}
|
||||
|
||||
// If it's over 9000
|
||||
if (num > 9000)
|
||||
return "";
|
||||
num++;
|
||||
} while (QFileInfo(PathCombine(inDir, dirName)).exists());
|
||||
return dirName;
|
||||
// If it's over 9000
|
||||
if (num > 9000)
|
||||
return "";
|
||||
num++;
|
||||
} while (QFileInfo(PathCombine(inDir, dirName)).exists());
|
||||
return dirName;
|
||||
}
|
||||
|
||||
// Does the folder path contain any '!'? If yes, return true, otherwise false.
|
||||
// (This is a problem for Java)
|
||||
bool checkProblemticPathJava(QDir folder)
|
||||
{
|
||||
QString pathfoldername = folder.absolutePath();
|
||||
return pathfoldername.contains("!", Qt::CaseInsensitive);
|
||||
QString pathfoldername = folder.absolutePath();
|
||||
return pathfoldername.contains("!", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
// Win32 crap
|
||||
#if defined Q_OS_WIN
|
||||
|
||||
#include <windows.h>
|
||||
#include <winnls.h>
|
||||
#include <shobjidl.h>
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
#include <shlobj.h>
|
||||
|
||||
bool called_coinit = false;
|
||||
|
||||
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
|
||||
{
|
||||
HRESULT hres;
|
||||
HRESULT hres;
|
||||
|
||||
if (!called_coinit)
|
||||
{
|
||||
hres = CoInitialize(NULL);
|
||||
called_coinit = true;
|
||||
if (!called_coinit)
|
||||
{
|
||||
hres = CoInitialize(NULL);
|
||||
called_coinit = true;
|
||||
|
||||
if (!SUCCEEDED(hres))
|
||||
{
|
||||
qWarning("Failed to initialize COM. Error 0x%08X", hres);
|
||||
return hres;
|
||||
}
|
||||
}
|
||||
if (!SUCCEEDED(hres))
|
||||
{
|
||||
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
||||
return hres;
|
||||
}
|
||||
}
|
||||
|
||||
IShellLink *link;
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
||||
(LPVOID *)&link);
|
||||
IShellLink *link;
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
||||
(LPVOID *)&link);
|
||||
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
IPersistFile *persistFile;
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
IPersistFile *persistFile;
|
||||
|
||||
link->SetPath(targetPath);
|
||||
link->SetArguments(args);
|
||||
link->SetPath(targetPath);
|
||||
link->SetArguments(args);
|
||||
|
||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
WCHAR wstr[MAX_PATH];
|
||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
WCHAR wstr[MAX_PATH];
|
||||
|
||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
||||
|
||||
hres = persistFile->Save(wstr, TRUE);
|
||||
persistFile->Release();
|
||||
}
|
||||
link->Release();
|
||||
}
|
||||
return hres;
|
||||
hres = persistFile->Save(wstr, TRUE);
|
||||
persistFile->Release();
|
||||
}
|
||||
link->Release();
|
||||
}
|
||||
return hres;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QString getDesktopDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
||||
QString icon)
|
||||
QString icon)
|
||||
{
|
||||
#if defined Q_OS_LINUX
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
|
||||
QFile f(location);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
QFile f(location);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
QString argstring;
|
||||
if (!args.empty())
|
||||
argstring = " '" + args.join("' '") + "'";
|
||||
QString argstring;
|
||||
if (!args.empty())
|
||||
argstring = " '" + args.join("' '") + "'";
|
||||
|
||||
stream << "[Desktop Entry]"
|
||||
<< "\n";
|
||||
stream << "Type=Application"
|
||||
<< "\n";
|
||||
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
|
||||
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
stream << "[Desktop Entry]"
|
||||
<< "\n";
|
||||
stream << "Type=Application"
|
||||
<< "\n";
|
||||
stream << "TryExec=" << dest.toLocal8Bit() << "\n";
|
||||
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
|
||||
stream << "Name=" << name.toLocal8Bit() << "\n";
|
||||
stream << "Icon=" << icon.toLocal8Bit() << "\n";
|
||||
|
||||
stream.flush();
|
||||
f.close();
|
||||
stream.flush();
|
||||
f.close();
|
||||
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
||||
QFileDevice::ExeOther);
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
||||
QFileDevice::ExeOther);
|
||||
|
||||
return true;
|
||||
return true;
|
||||
#elif defined Q_OS_WIN
|
||||
// TODO: Fix
|
||||
// QFile file(PathCombine(location, name + ".lnk"));
|
||||
// WCHAR *file_w;
|
||||
// WCHAR *dest_w;
|
||||
// WCHAR *args_w;
|
||||
// file.fileName().toWCharArray(file_w);
|
||||
// dest.toWCharArray(dest_w);
|
||||
// TODO: Fix
|
||||
// QFile file(PathCombine(location, name + ".lnk"));
|
||||
// WCHAR *file_w;
|
||||
// WCHAR *dest_w;
|
||||
// WCHAR *args_w;
|
||||
// file.fileName().toWCharArray(file_w);
|
||||
// dest.toWCharArray(dest_w);
|
||||
|
||||
// QString argStr;
|
||||
// for (int i = 0; i < args.count(); i++)
|
||||
// {
|
||||
// argStr.append(args[i]);
|
||||
// argStr.append(" ");
|
||||
// }
|
||||
// argStr.toWCharArray(args_w);
|
||||
// QString argStr;
|
||||
// for (int i = 0; i < args.count(); i++)
|
||||
// {
|
||||
// argStr.append(args[i]);
|
||||
// argStr.append(" ");
|
||||
// }
|
||||
// argStr.toWCharArray(args_w);
|
||||
|
||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
||||
return false;
|
||||
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
|
||||
return false;
|
||||
#else
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace FS
|
||||
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
|
||||
{
|
||||
public:
|
||||
FileSystemException(const QString &message) : Exception(message) {}
|
||||
FileSystemException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -48,34 +48,34 @@ MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
|
||||
class MULTIMC_LOGIC_EXPORT copy
|
||||
{
|
||||
public:
|
||||
copy(const QString & src, const QString & dst)
|
||||
{
|
||||
m_src = src;
|
||||
m_dst = dst;
|
||||
}
|
||||
copy & followSymlinks(const bool follow)
|
||||
{
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy & blacklist(const IPathMatcher * filter)
|
||||
{
|
||||
m_blacklist = filter;
|
||||
return *this;
|
||||
}
|
||||
bool operator()()
|
||||
{
|
||||
return operator()(QString());
|
||||
}
|
||||
copy(const QString & src, const QString & dst)
|
||||
{
|
||||
m_src = src;
|
||||
m_dst = dst;
|
||||
}
|
||||
copy & followSymlinks(const bool follow)
|
||||
{
|
||||
m_followSymlinks = follow;
|
||||
return *this;
|
||||
}
|
||||
copy & blacklist(const IPathMatcher * filter)
|
||||
{
|
||||
m_blacklist = filter;
|
||||
return *this;
|
||||
}
|
||||
bool operator()()
|
||||
{
|
||||
return operator()(QString());
|
||||
}
|
||||
|
||||
private:
|
||||
bool operator()(const QString &offset);
|
||||
bool operator()(const QString &offset);
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher * m_blacklist = nullptr;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher * m_blacklist = nullptr;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -83,8 +83,9 @@ private:
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
|
||||
|
||||
|
||||
@@ -7,155 +7,155 @@
|
||||
|
||||
class FileSystemTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
const QString bothSlash = "/foo/";
|
||||
const QString trailingSlash = "foo/";
|
||||
const QString leadingSlash = "/foo";
|
||||
const QString bothSlash = "/foo/";
|
||||
const QString trailingSlash = "foo/";
|
||||
const QString leadingSlash = "/foo";
|
||||
|
||||
private
|
||||
slots:
|
||||
void test_pathCombine()
|
||||
{
|
||||
QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash));
|
||||
QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash));
|
||||
QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash));
|
||||
void test_pathCombine()
|
||||
{
|
||||
QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash));
|
||||
QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash));
|
||||
QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash));
|
||||
|
||||
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash));
|
||||
QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash));
|
||||
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash));
|
||||
}
|
||||
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash));
|
||||
QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash));
|
||||
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash));
|
||||
}
|
||||
|
||||
void test_PathCombine1_data()
|
||||
{
|
||||
QTest::addColumn<QString>("result");
|
||||
QTest::addColumn<QString>("path1");
|
||||
QTest::addColumn<QString>("path2");
|
||||
void test_PathCombine1_data()
|
||||
{
|
||||
QTest::addColumn<QString>("result");
|
||||
QTest::addColumn<QString>("path1");
|
||||
QTest::addColumn<QString>("path2");
|
||||
|
||||
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
|
||||
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
|
||||
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
|
||||
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
|
||||
#if defined(Q_OS_WIN)
|
||||
QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc";
|
||||
QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl";
|
||||
QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
|
||||
QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc";
|
||||
QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl";
|
||||
QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void test_PathCombine1()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
void test_PathCombine1()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
|
||||
QCOMPARE(FS::PathCombine(path1, path2), result);
|
||||
}
|
||||
QCOMPARE(FS::PathCombine(path1, path2), result);
|
||||
}
|
||||
|
||||
void test_PathCombine2_data()
|
||||
{
|
||||
QTest::addColumn<QString>("result");
|
||||
QTest::addColumn<QString>("path1");
|
||||
QTest::addColumn<QString>("path2");
|
||||
QTest::addColumn<QString>("path3");
|
||||
void test_PathCombine2_data()
|
||||
{
|
||||
QTest::addColumn<QString>("result");
|
||||
QTest::addColumn<QString>("path1");
|
||||
QTest::addColumn<QString>("path2");
|
||||
QTest::addColumn<QString>("path3");
|
||||
|
||||
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
|
||||
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
|
||||
QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
|
||||
QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
|
||||
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
|
||||
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
|
||||
QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
|
||||
QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
|
||||
#if defined(Q_OS_WIN)
|
||||
QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl";
|
||||
QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
|
||||
QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
|
||||
QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
|
||||
QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl";
|
||||
QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
|
||||
QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
|
||||
QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void test_PathCombine2()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
QFETCH(QString, path3);
|
||||
void test_PathCombine2()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
QFETCH(QString, path3);
|
||||
|
||||
QCOMPARE(FS::PathCombine(path1, path2, path3), result);
|
||||
}
|
||||
QCOMPARE(FS::PathCombine(path1, path2, path3), result);
|
||||
}
|
||||
|
||||
void test_copy()
|
||||
{
|
||||
QString folder = QFINDTESTDATA("data/test_folder");
|
||||
auto f = [&folder]()
|
||||
{
|
||||
QTemporaryDir tempDir;
|
||||
tempDir.setAutoRemove(true);
|
||||
qDebug() << "From:" << folder << "To:" << tempDir.path();
|
||||
void test_copy()
|
||||
{
|
||||
QString folder = QFINDTESTDATA("data/test_folder");
|
||||
auto f = [&folder]()
|
||||
{
|
||||
QTemporaryDir tempDir;
|
||||
tempDir.setAutoRemove(true);
|
||||
qDebug() << "From:" << folder << "To:" << tempDir.path();
|
||||
|
||||
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
|
||||
qDebug() << tempDir.path();
|
||||
qDebug() << target_dir.path();
|
||||
FS::copy c(folder, target_dir.path());
|
||||
c();
|
||||
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
|
||||
qDebug() << tempDir.path();
|
||||
qDebug() << target_dir.path();
|
||||
FS::copy c(folder, target_dir.path());
|
||||
c();
|
||||
|
||||
for(auto entry: target_dir.entryList())
|
||||
{
|
||||
qDebug() << entry;
|
||||
}
|
||||
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
||||
QVERIFY(target_dir.entryList().contains("assets"));
|
||||
};
|
||||
for(auto entry: target_dir.entryList())
|
||||
{
|
||||
qDebug() << entry;
|
||||
}
|
||||
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
|
||||
QVERIFY(target_dir.entryList().contains("assets"));
|
||||
};
|
||||
|
||||
// first try variant without trailing /
|
||||
QVERIFY(!folder.endsWith('/'));
|
||||
f();
|
||||
// first try variant without trailing /
|
||||
QVERIFY(!folder.endsWith('/'));
|
||||
f();
|
||||
|
||||
// then variant with trailing /
|
||||
folder.append('/');
|
||||
QVERIFY(folder.endsWith('/'));
|
||||
f();
|
||||
}
|
||||
// then variant with trailing /
|
||||
folder.append('/');
|
||||
QVERIFY(folder.endsWith('/'));
|
||||
f();
|
||||
}
|
||||
|
||||
void test_getDesktop()
|
||||
{
|
||||
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||
}
|
||||
void test_getDesktop()
|
||||
{
|
||||
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
|
||||
}
|
||||
|
||||
// this is only valid on linux
|
||||
// FIXME: implement on windows, OSX, then test.
|
||||
#if defined(Q_OS_LINUX)
|
||||
void test_createShortcut_data()
|
||||
{
|
||||
QTest::addColumn<QString>("location");
|
||||
QTest::addColumn<QString>("dest");
|
||||
QTest::addColumn<QStringList>("args");
|
||||
QTest::addColumn<QString>("name");
|
||||
QTest::addColumn<QString>("iconLocation");
|
||||
QTest::addColumn<QByteArray>("result");
|
||||
void test_createShortcut_data()
|
||||
{
|
||||
QTest::addColumn<QString>("location");
|
||||
QTest::addColumn<QString>("dest");
|
||||
QTest::addColumn<QStringList>("args");
|
||||
QTest::addColumn<QString>("name");
|
||||
QTest::addColumn<QString>("iconLocation");
|
||||
QTest::addColumn<QByteArray>("result");
|
||||
|
||||
QTest::newRow("unix") << QDir::currentPath()
|
||||
<< "asdfDest"
|
||||
<< (QStringList() << "arg1" << "arg2")
|
||||
<< "asdf"
|
||||
<< QString()
|
||||
#if defined(Q_OS_LINUX)
|
||||
<< MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix")
|
||||
#elif defined(Q_OS_WIN)
|
||||
<< QByteArray()
|
||||
#endif
|
||||
;
|
||||
}
|
||||
QTest::newRow("unix") << QDir::currentPath()
|
||||
<< "asdfDest"
|
||||
<< (QStringList() << "arg1" << "arg2")
|
||||
<< "asdf"
|
||||
<< QString()
|
||||
#if defined(Q_OS_LINUX)
|
||||
<< MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix")
|
||||
#elif defined(Q_OS_WIN)
|
||||
<< QByteArray()
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
void test_createShortcut()
|
||||
{
|
||||
QFETCH(QString, location);
|
||||
QFETCH(QString, dest);
|
||||
QFETCH(QStringList, args);
|
||||
QFETCH(QString, name);
|
||||
QFETCH(QString, iconLocation);
|
||||
QFETCH(QByteArray, result);
|
||||
void test_createShortcut()
|
||||
{
|
||||
QFETCH(QString, location);
|
||||
QFETCH(QString, dest);
|
||||
QFETCH(QStringList, args);
|
||||
QFETCH(QString, name);
|
||||
QFETCH(QString, iconLocation);
|
||||
QFETCH(QByteArray, result);
|
||||
|
||||
QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation));
|
||||
QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result));
|
||||
QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation));
|
||||
QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result));
|
||||
|
||||
//QDir().remove(location);
|
||||
}
|
||||
//QDir().remove(location);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
31
api/logic/Filter.cpp
Normal file
31
api/logic/Filter.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "Filter.h"
|
||||
|
||||
Filter::~Filter(){}
|
||||
|
||||
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
|
||||
ContainsFilter::~ContainsFilter(){}
|
||||
bool ContainsFilter::accepts(const QString& value)
|
||||
{
|
||||
return value.contains(pattern);
|
||||
}
|
||||
|
||||
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
|
||||
ExactFilter::~ExactFilter(){}
|
||||
bool ExactFilter::accepts(const QString& value)
|
||||
{
|
||||
return value == pattern;
|
||||
}
|
||||
|
||||
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
|
||||
:invert(invert)
|
||||
{
|
||||
pattern.setPattern(regexp);
|
||||
pattern.optimize();
|
||||
}
|
||||
RegexpFilter::~RegexpFilter(){}
|
||||
bool RegexpFilter::accepts(const QString& value)
|
||||
{
|
||||
auto match = pattern.match(value);
|
||||
bool matched = match.hasMatch();
|
||||
return invert ? (!matched) : (matched);
|
||||
}
|
||||
44
api/logic/Filter.h
Normal file
44
api/logic/Filter.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Filter
|
||||
{
|
||||
public:
|
||||
virtual ~Filter();
|
||||
virtual bool accepts(const QString & value) = 0;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ContainsFilter(const QString &pattern);
|
||||
virtual ~ContainsFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ExactFilter(const QString &pattern);
|
||||
virtual ~ExactFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter
|
||||
{
|
||||
public:
|
||||
RegexpFilter(const QString ®exp, bool invert);
|
||||
virtual ~RegexpFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QRegularExpression pattern;
|
||||
bool invert = false;
|
||||
};
|
||||
@@ -1,356 +0,0 @@
|
||||
#include "FolderInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "NullInstance.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUuid>
|
||||
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
struct WatchLock
|
||||
{
|
||||
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
|
||||
: m_watcher(watcher), m_instDir(instDir)
|
||||
{
|
||||
m_watcher->removePath(m_instDir);
|
||||
}
|
||||
~WatchLock()
|
||||
{
|
||||
m_watcher->addPath(m_instDir);
|
||||
}
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QString m_instDir;
|
||||
};
|
||||
|
||||
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
|
||||
: BaseInstanceProvider(settings)
|
||||
{
|
||||
m_instDir = instDir;
|
||||
if (!QDir::current().exists(m_instDir))
|
||||
{
|
||||
QDir::current().mkpath(m_instDir);
|
||||
}
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
|
||||
m_watcher->addPath(m_instDir);
|
||||
}
|
||||
|
||||
QList< InstanceId > FolderInstanceProvider::discoverInstances()
|
||||
{
|
||||
QList<InstanceId> out;
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
QFileInfo dirInfo(subDir);
|
||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
// if it is a symlink, ignore it if it goes to the instance folder
|
||||
if(dirInfo.isSymLink())
|
||||
{
|
||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||
QFileInfo instDirInfo(m_instDir);
|
||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
||||
{
|
||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto id = dirInfo.fileName();
|
||||
out.append(id);
|
||||
qDebug() << "Found instance ID" << id;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
|
||||
{
|
||||
if(!m_groupsLoaded)
|
||||
{
|
||||
loadGroupList();
|
||||
}
|
||||
|
||||
auto instanceRoot = FS::PathCombine(m_instDir, id);
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
|
||||
InstancePtr inst;
|
||||
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
QString inst_type = instanceSettings->get("InstanceType").toString();
|
||||
|
||||
if (inst_type == "OneSix" || inst_type == "Nostalgia")
|
||||
{
|
||||
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else if (inst_type == "Legacy")
|
||||
{
|
||||
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
inst->init();
|
||||
inst->setProvider(this);
|
||||
auto iter = groupMap.find(id);
|
||||
if (iter != groupMap.end())
|
||||
{
|
||||
inst->setGroupInitial((*iter));
|
||||
}
|
||||
connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged);
|
||||
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
|
||||
return inst;
|
||||
}
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
|
||||
{
|
||||
return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup);
|
||||
}
|
||||
|
||||
#include "InstanceCreationTask.h"
|
||||
Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
|
||||
{
|
||||
return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup);
|
||||
}
|
||||
|
||||
#include "InstanceCopyTask.h"
|
||||
Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
|
||||
{
|
||||
return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves);
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::saveGroupList()
|
||||
{
|
||||
WatchLock foo(m_watcher, m_instDir);
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
|
||||
{
|
||||
QString id = iter.key();
|
||||
QString group = iter.value();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
|
||||
if (!reverseGroupMap.count(group))
|
||||
{
|
||||
QSet<QString> set;
|
||||
set.insert(id);
|
||||
reverseGroupMap[group] = set;
|
||||
}
|
||||
else
|
||||
{
|
||||
QSet<QString> &set = reverseGroupMap[group];
|
||||
set.insert(id);
|
||||
}
|
||||
}
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||
QJsonObject groupsArr;
|
||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
||||
{
|
||||
auto list = iter.value();
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
}
|
||||
groupObj.insert("instances", instanceArr);
|
||||
groupsArr.insert(name, groupObj);
|
||||
}
|
||||
toplevel.insert("groups", groupsArr);
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
{
|
||||
FS::write(groupFileName, doc.toJson());
|
||||
}
|
||||
catch(FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::loadGroupList()
|
||||
{
|
||||
QSet<QString> groupSet;
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if (!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QByteArray jsonData;
|
||||
try
|
||||
{
|
||||
jsonData = FS::read(groupFileName);
|
||||
}
|
||||
catch (FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
// if the json was bad, fail
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||
.arg(error.errorString(), QString::number(error.offset))
|
||||
.toUtf8();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the root of the json wasn't an object, fail
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches, otherwise fail.
|
||||
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
|
||||
return;
|
||||
|
||||
// Get the groups. if it's not an object, fail
|
||||
if (!rootObj.value("groups").isObject())
|
||||
{
|
||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
groupMap.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
||||
{
|
||||
QString groupName = iter.key();
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list should "
|
||||
"be an object.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
|
||||
iter2++)
|
||||
{
|
||||
groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
emit groupsChanged(groupSet);
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::groupChanged()
|
||||
{
|
||||
// save the groups. save all of them.
|
||||
auto instance = (BaseInstance *) QObject::sender();
|
||||
auto id = instance->id();
|
||||
groupMap[id] = instance->group();
|
||||
emit groupsChanged({instance->group()});
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
|
||||
void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
|
||||
{
|
||||
Q_UNUSED(path);
|
||||
emit instancesChanged();
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
QString newInstDir = value.toString();
|
||||
if(newInstDir != m_instDir)
|
||||
{
|
||||
if(m_groupsLoaded)
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
m_instDir = newInstDir;
|
||||
m_groupsLoaded = false;
|
||||
emit instancesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString FolderInstanceProvider::getStagedInstancePath()
|
||||
{
|
||||
QString key = QUuid::createUuid().toString();
|
||||
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
|
||||
QDir rootPath(m_instDir);
|
||||
auto path = FS::PathCombine(m_instDir, relPath);
|
||||
if(!rootPath.mkpath(relPath))
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
|
||||
const QString& groupName)
|
||||
{
|
||||
if(!path.contains(keyPath))
|
||||
{
|
||||
qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath;
|
||||
return false;
|
||||
}
|
||||
QDir dir;
|
||||
QString instID = FS::DirNameFromString(instanceName, m_instDir);
|
||||
{
|
||||
WatchLock lock(m_watcher, m_instDir);
|
||||
if(!dir.rename(path, FS::PathCombine(m_instDir, instID)))
|
||||
{
|
||||
destroyStagingPath(keyPath);
|
||||
return false;
|
||||
}
|
||||
groupMap[instID] = groupName;
|
||||
emit groupsChanged({groupName});
|
||||
emit instancesChanged();
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
|
||||
{
|
||||
return FS::deletePath(keyPath);
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include <QMap>
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
|
||||
|
||||
public:
|
||||
/// used by InstanceList to @return a list of plausible IDs to probe for
|
||||
QList<InstanceId> discoverInstances() override;
|
||||
|
||||
/// used by InstanceList to (re)load an instance with the given @id.
|
||||
InstancePtr loadInstance(const InstanceId& id) override;
|
||||
|
||||
|
||||
// create instance in this provider
|
||||
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
// copy instance to this provider
|
||||
Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves);
|
||||
|
||||
// import zipped instance into this provider
|
||||
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
/**
|
||||
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
QString getStagedInstancePath() override;
|
||||
/**
|
||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override;
|
||||
/**
|
||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool destroyStagingPath(const QString & keyPath) override;
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
private slots:
|
||||
void instanceDirContentsChanged(const QString &path);
|
||||
void groupChanged();
|
||||
|
||||
private: /* methods */
|
||||
void loadGroupList() override;
|
||||
void saveGroupList() override;
|
||||
|
||||
private: /* data */
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QMap<QString, QString> groupMap;
|
||||
bool m_groupsLoaded = false;
|
||||
};
|
||||
@@ -4,112 +4,112 @@
|
||||
|
||||
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
|
||||
{
|
||||
if (compressedBytes.size() == 0)
|
||||
{
|
||||
uncompressedBytes = compressedBytes;
|
||||
return true;
|
||||
}
|
||||
if (compressedBytes.size() == 0)
|
||||
{
|
||||
uncompressedBytes = compressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned uncompLength = compressedBytes.size();
|
||||
uncompressedBytes.clear();
|
||||
uncompressedBytes.resize(uncompLength);
|
||||
unsigned uncompLength = compressedBytes.size();
|
||||
uncompressedBytes.clear();
|
||||
uncompressedBytes.resize(uncompLength);
|
||||
|
||||
z_stream strm;
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
strm.next_in = (Bytef *)compressedBytes.data();
|
||||
strm.avail_in = compressedBytes.size();
|
||||
z_stream strm;
|
||||
memset(&strm, 0, sizeof(strm));
|
||||
strm.next_in = (Bytef *)compressedBytes.data();
|
||||
strm.avail_in = compressedBytes.size();
|
||||
|
||||
bool done = false;
|
||||
bool done = false;
|
||||
|
||||
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = Z_OK;
|
||||
int err = Z_OK;
|
||||
|
||||
while (!done)
|
||||
{
|
||||
// If our output buffer is too small
|
||||
if (strm.total_out >= uncompLength)
|
||||
{
|
||||
uncompressedBytes.resize(uncompLength * 2);
|
||||
uncompLength *= 2;
|
||||
}
|
||||
while (!done)
|
||||
{
|
||||
// If our output buffer is too small
|
||||
if (strm.total_out >= uncompLength)
|
||||
{
|
||||
uncompressedBytes.resize(uncompLength * 2);
|
||||
uncompLength *= 2;
|
||||
}
|
||||
|
||||
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
|
||||
strm.avail_out = uncompLength - strm.total_out;
|
||||
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
|
||||
strm.avail_out = uncompLength - strm.total_out;
|
||||
|
||||
// Inflate another chunk.
|
||||
err = inflate(&strm, Z_SYNC_FLUSH);
|
||||
if (err == Z_STREAM_END)
|
||||
done = true;
|
||||
else if (err != Z_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Inflate another chunk.
|
||||
err = inflate(&strm, Z_SYNC_FLUSH);
|
||||
if (err == Z_STREAM_END)
|
||||
done = true;
|
||||
else if (err != Z_OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (inflateEnd(&strm) != Z_OK || !done)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (inflateEnd(&strm) != Z_OK || !done)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uncompressedBytes.resize(strm.total_out);
|
||||
return true;
|
||||
uncompressedBytes.resize(strm.total_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
|
||||
{
|
||||
if (uncompressedBytes.size() == 0)
|
||||
{
|
||||
compressedBytes = uncompressedBytes;
|
||||
return true;
|
||||
}
|
||||
if (uncompressedBytes.size() == 0)
|
||||
{
|
||||
compressedBytes = uncompressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned compLength = std::min(uncompressedBytes.size(), 16);
|
||||
compressedBytes.clear();
|
||||
compressedBytes.resize(compLength);
|
||||
unsigned compLength = std::min(uncompressedBytes.size(), 16);
|
||||
compressedBytes.clear();
|
||||
compressedBytes.resize(compLength);
|
||||
|
||||
z_stream zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
z_stream zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
zs.next_in = (Bytef*)uncompressedBytes.data();
|
||||
zs.avail_in = uncompressedBytes.size();
|
||||
zs.next_in = (Bytef*)uncompressedBytes.data();
|
||||
zs.avail_in = uncompressedBytes.size();
|
||||
|
||||
int ret;
|
||||
compressedBytes.resize(uncompressedBytes.size());
|
||||
int ret;
|
||||
compressedBytes.resize(uncompressedBytes.size());
|
||||
|
||||
unsigned offset = 0;
|
||||
unsigned temp = 0;
|
||||
do
|
||||
{
|
||||
auto remaining = compressedBytes.size() - offset;
|
||||
if(remaining < 1)
|
||||
{
|
||||
compressedBytes.resize(compressedBytes.size() * 2);
|
||||
}
|
||||
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
|
||||
temp = zs.avail_out = compressedBytes.size() - offset;
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
offset += temp - zs.avail_out;
|
||||
} while (ret == Z_OK);
|
||||
unsigned offset = 0;
|
||||
unsigned temp = 0;
|
||||
do
|
||||
{
|
||||
auto remaining = compressedBytes.size() - offset;
|
||||
if(remaining < 1)
|
||||
{
|
||||
compressedBytes.resize(compressedBytes.size() * 2);
|
||||
}
|
||||
zs.next_out = (Bytef *) (compressedBytes.data() + offset);
|
||||
temp = zs.avail_out = compressedBytes.size() - offset;
|
||||
ret = deflate(&zs, Z_FINISH);
|
||||
offset += temp - zs.avail_out;
|
||||
} while (ret == Z_OK);
|
||||
|
||||
compressedBytes.resize(offset);
|
||||
compressedBytes.resize(offset);
|
||||
|
||||
if (deflateEnd(&zs) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (deflateEnd(&zs) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
class MULTIMC_LOGIC_EXPORT GZip
|
||||
{
|
||||
public:
|
||||
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
|
||||
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
|
||||
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
|
||||
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,50 +6,50 @@
|
||||
|
||||
void fib(int &prev, int &cur)
|
||||
{
|
||||
auto ret = prev + cur;
|
||||
prev = cur;
|
||||
cur = ret;
|
||||
auto ret = prev + cur;
|
||||
prev = cur;
|
||||
cur = ret;
|
||||
}
|
||||
|
||||
class GZipTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
private
|
||||
slots:
|
||||
|
||||
void test_Through()
|
||||
{
|
||||
// test up to 10 MB
|
||||
static const int size = 10 * 1024 * 1024;
|
||||
QByteArray random;
|
||||
QByteArray compressed;
|
||||
QByteArray decompressed;
|
||||
std::default_random_engine eng((std::random_device())());
|
||||
std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max());
|
||||
void test_Through()
|
||||
{
|
||||
// test up to 10 MB
|
||||
static const int size = 10 * 1024 * 1024;
|
||||
QByteArray random;
|
||||
QByteArray compressed;
|
||||
QByteArray decompressed;
|
||||
std::default_random_engine eng((std::random_device())());
|
||||
std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max());
|
||||
|
||||
// initialize random buffer
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
random.append((char)idis(eng));
|
||||
}
|
||||
// initialize random buffer
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
random.append((char)idis(eng));
|
||||
}
|
||||
|
||||
// initialize fibonacci
|
||||
int prev = 1;
|
||||
int cur = 1;
|
||||
// initialize fibonacci
|
||||
int prev = 1;
|
||||
int cur = 1;
|
||||
|
||||
// test if fibonacci long random buffers pass through GZip
|
||||
do
|
||||
{
|
||||
QByteArray copy = random;
|
||||
copy.resize(cur);
|
||||
compressed.clear();
|
||||
decompressed.clear();
|
||||
QVERIFY(GZip::zip(copy, compressed));
|
||||
QVERIFY(GZip::unzip(compressed, decompressed));
|
||||
QCOMPARE(decompressed, copy);
|
||||
fib(prev, cur);
|
||||
} while (cur < size);
|
||||
}
|
||||
// test if fibonacci long random buffers pass through GZip
|
||||
do
|
||||
{
|
||||
QByteArray copy = random;
|
||||
copy.resize(cur);
|
||||
compressed.clear();
|
||||
decompressed.clear();
|
||||
QVERIFY(GZip::zip(copy, compressed));
|
||||
QVERIFY(GZip::unzip(compressed, decompressed));
|
||||
QCOMPARE(decompressed, copy);
|
||||
fib(prev, cur);
|
||||
} while (cur < size);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(GZipTest)
|
||||
|
||||
@@ -1,68 +1,56 @@
|
||||
#include "InstanceCopyTask.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
#include "NullInstance.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_origInstance = origInstance;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
m_origInstance = origInstance;
|
||||
|
||||
if(!copySaves)
|
||||
{
|
||||
// FIXME: get this from the original instance type...
|
||||
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
|
||||
matcherReal->caseSensitive(false);
|
||||
m_matcher.reset(matcherReal);
|
||||
}
|
||||
if(!copySaves)
|
||||
{
|
||||
// FIXME: get this from the original instance type...
|
||||
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
|
||||
matcherReal->caseSensitive(false);
|
||||
m_matcher.reset(matcherReal);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceCopyTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||
m_stagingPath = m_target->getStagedInstancePath();
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
||||
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
|
||||
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
|
||||
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
|
||||
|
||||
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
|
||||
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
|
||||
m_copyFutureWatcher.setFuture(m_copyFuture);
|
||||
}
|
||||
|
||||
void InstanceCopyTask::copyFinished()
|
||||
{
|
||||
auto successful = m_copyFuture.result();
|
||||
if(!successful)
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance folder copy failed."));
|
||||
return;
|
||||
}
|
||||
// FIXME: shouldn't this be able to report errors?
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
auto successful = m_copyFuture.result();
|
||||
if(!successful)
|
||||
{
|
||||
emitFailed(tr("Instance folder copy failed."));
|
||||
return;
|
||||
}
|
||||
// FIXME: shouldn't this be able to report errors?
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
// FIXME: and this too? errors???
|
||||
m_origInstance->copy(instanceSettings, m_stagingPath);
|
||||
|
||||
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
||||
inst->setName(m_instName);
|
||||
inst->setIconKey(m_instIcon);
|
||||
m_target->commitStagedInstance(m_stagingPath, m_stagingPath, m_instName, m_instGroup);
|
||||
emitSucceeded();
|
||||
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
|
||||
inst->setName(m_instName);
|
||||
inst->setIconKey(m_instIcon);
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void InstanceCopyTask::copyAborted()
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance folder copy has been aborted."));
|
||||
return;
|
||||
emitFailed(tr("Instance folder copy has been aborted."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,34 +9,23 @@
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "InstanceTask.h"
|
||||
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup, bool copySaves);
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
void copyFinished();
|
||||
void copyAborted();
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
void copyFinished();
|
||||
void copyAborted();
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
BaseInstanceProvider * m_target = nullptr;
|
||||
InstancePtr m_origInstance;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
QString m_stagingPath;
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
std::unique_ptr<IPathMatcher> m_matcher;
|
||||
InstancePtr m_origInstance;
|
||||
QFuture<bool> m_copyFuture;
|
||||
QFutureWatcher<bool> m_copyFutureWatcher;
|
||||
std::unique_ptr<IPathMatcher> m_matcher;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,46 +1,31 @@
|
||||
#include "InstanceCreationTask.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
//FIXME: remove this
|
||||
#include "minecraft/MinecraftVersion.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
|
||||
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
|
||||
const QString& instName, const QString& instIcon, const QString& instGroup)
|
||||
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
m_version = version;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
void InstanceCreationTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
|
||||
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
|
||||
if(!minecraftVersion)
|
||||
{
|
||||
emitFailed(tr("The supplied version is not a Minecraft version."));
|
||||
return ;
|
||||
}
|
||||
|
||||
QString stagingPath = m_target->getStagedInstancePath();
|
||||
QDir rootDir(stagingPath);
|
||||
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg"));
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath));
|
||||
inst->setIntendedVersionId(m_version->descriptor());
|
||||
inst->setName(m_instName);
|
||||
inst->setIconKey(m_instIcon);
|
||||
inst->init();
|
||||
m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
|
||||
emitSucceeded();
|
||||
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
|
||||
{
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
instanceSettings->suspendSave();
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = inst.getComponentList();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
||||
inst.setName(m_instName);
|
||||
inst.setIconKey(m_instIcon);
|
||||
instanceSettings->resumeSave();
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -6,26 +6,18 @@
|
||||
#include <QUrl>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "InstanceTask.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
explicit InstanceCreationTask(BaseVersionPtr version);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
BaseInstanceProvider * m_target;
|
||||
BaseVersionPtr m_version;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
BaseVersionPtr m_version;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,164 +1,411 @@
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Env.h"
|
||||
#include "MMCZip.h"
|
||||
#include "NullInstance.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "icons/IIconList.h"
|
||||
#include "icons/IconUtils.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
|
||||
const QString &instName, const QString &instIcon, const QString &instGroup)
|
||||
// FIXME: this does not belong here, it's Minecraft/Flame specific
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "modplatform/flame/PackManifest.h"
|
||||
#include "Json.h"
|
||||
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
m_sourceUrl = sourceUrl;
|
||||
}
|
||||
|
||||
void InstanceImportTask::executeTask()
|
||||
{
|
||||
InstancePtr newInstance;
|
||||
InstancePtr newInstance;
|
||||
|
||||
if (m_sourceUrl.isLocalFile())
|
||||
{
|
||||
m_archivePath = m_sourceUrl.toLocalFile();
|
||||
extractAndTweak();
|
||||
}
|
||||
else
|
||||
{
|
||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||
m_downloadRequired = true;
|
||||
if (m_sourceUrl.isLocalFile())
|
||||
{
|
||||
m_archivePath = m_sourceUrl.toLocalFile();
|
||||
processZipPack();
|
||||
}
|
||||
else
|
||||
{
|
||||
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
|
||||
m_downloadRequired = true;
|
||||
|
||||
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
||||
auto entry = ENV.metacache()->resolveEntry("general", path);
|
||||
entry->setStale(true);
|
||||
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||
m_archivePath = entry->getFullPath();
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
|
||||
auto entry = ENV.metacache()->resolveEntry("general", path);
|
||||
entry->setStale(true);
|
||||
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
|
||||
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
|
||||
m_archivePath = entry->getFullPath();
|
||||
auto job = m_filesNetJob.get();
|
||||
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
|
||||
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
|
||||
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadSucceeded()
|
||||
{
|
||||
extractAndTweak();
|
||||
processZipPack();
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadFailed(QString reason)
|
||||
{
|
||||
emitFailed(reason);
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current / 2, total);
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
static QFileInfo findRecursive(const QString &dir, const QString &name)
|
||||
void InstanceImportTask::processZipPack()
|
||||
{
|
||||
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
|
||||
{
|
||||
if (info.isFile() && info.fileName() == name)
|
||||
{
|
||||
return info;
|
||||
}
|
||||
else if (info.isDir())
|
||||
{
|
||||
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
|
||||
if (res.isFile() && res.exists())
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QFileInfo();
|
||||
}
|
||||
setStatus(tr("Extracting modpack"));
|
||||
QDir extractDir(m_stagingPath);
|
||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||
|
||||
void InstanceImportTask::extractAndTweak()
|
||||
{
|
||||
setStatus(tr("Extracting modpack"));
|
||||
m_stagingPath = m_target->getStagedInstancePath();
|
||||
QDir extractDir(m_stagingPath);
|
||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||
// open the zip and find relevant files in it
|
||||
m_packZip.reset(new QuaZip(m_archivePath));
|
||||
if (!m_packZip->open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Unable to open supplied modpack zip file."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath());
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
QStringList blacklist = {"instance.cfg", "manifest.json"};
|
||||
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||
QString root;
|
||||
if(!mmcFound.isNull())
|
||||
{
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << mmcFound;
|
||||
root = mmcFound;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
else if(!flameFound.isNull())
|
||||
{
|
||||
// process as Flame pack
|
||||
qDebug() << "Flame:" << flameFound;
|
||||
root = flameFound;
|
||||
m_modpackType = ModpackType::Flame;
|
||||
}
|
||||
|
||||
if(m_modpackType == ModpackType::Unknown)
|
||||
{
|
||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we extract just the pack
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
}
|
||||
|
||||
void InstanceImportTask::extractFinished()
|
||||
{
|
||||
if (m_extractFuture.result().isEmpty())
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Failed to extract modpack"));
|
||||
return;
|
||||
}
|
||||
QDir extractDir(m_stagingPath);
|
||||
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
|
||||
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Archive does not contain instance.cfg"));
|
||||
return;
|
||||
}
|
||||
m_packZip.reset();
|
||||
if (m_extractFuture.result().isEmpty())
|
||||
{
|
||||
emitFailed(tr("Failed to extract modpack"));
|
||||
return;
|
||||
}
|
||||
QDir extractDir(m_stagingPath);
|
||||
|
||||
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
qDebug() << "Fixing permissions for extracted pack files...";
|
||||
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||
while (it.hasNext())
|
||||
{
|
||||
auto filepath = it.next();
|
||||
QFileInfo file(filepath);
|
||||
auto permissions = QFile::permissions(filepath);
|
||||
auto origPermissions = permissions;
|
||||
if(file.isDir())
|
||||
{
|
||||
// Folder +rwx for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||
}
|
||||
else
|
||||
{
|
||||
// File +rw for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||
}
|
||||
if(origPermissions != permissions)
|
||||
{
|
||||
if(!QFile::setPermissions(filepath, permissions))
|
||||
{
|
||||
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Fixed" << filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString actualDir = instanceCfgFile.absolutePath();
|
||||
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
|
||||
|
||||
// reset time played on import... because packs.
|
||||
instance.resetTimePlayed();
|
||||
|
||||
// set a new nice name
|
||||
instance.setName(m_instName);
|
||||
|
||||
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||
if (m_instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(m_instIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_instIcon = instance.iconKey();
|
||||
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
|
||||
if (QFile::exists(importIconPath))
|
||||
{
|
||||
// import icon
|
||||
auto iconList = ENV.icons();
|
||||
if (iconList->iconFileExists(m_instIcon))
|
||||
{
|
||||
iconList->deleteIcon(m_instIcon);
|
||||
}
|
||||
iconList->installIcons({importIconPath});
|
||||
}
|
||||
}
|
||||
if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup))
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Unable to commit instance"));
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
switch(m_modpackType)
|
||||
{
|
||||
case ModpackType::Flame:
|
||||
processFlame();
|
||||
return;
|
||||
case ModpackType::MultiMC:
|
||||
processMultiMC();
|
||||
return;
|
||||
case ModpackType::Unknown:
|
||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceImportTask::extractAborted()
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
return;
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
return;
|
||||
}
|
||||
|
||||
void InstanceImportTask::processFlame()
|
||||
{
|
||||
const static QMap<QString,QString> forgemap = {
|
||||
{"1.2.5", "3.4.9.171"},
|
||||
{"1.4.2", "6.0.1.355"},
|
||||
{"1.4.7", "6.6.2.534"},
|
||||
{"1.5.2", "7.8.1.737"}
|
||||
};
|
||||
Flame::Manifest pack;
|
||||
try
|
||||
{
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
|
||||
Flame::loadManifest(pack, configPath);
|
||||
QFile::remove(configPath);
|
||||
}
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
if(!pack.overrides.isEmpty())
|
||||
{
|
||||
QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
|
||||
if (QFile::exists(overridePath))
|
||||
{
|
||||
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
|
||||
if (!QFile::rename(overridePath, mcPath))
|
||||
{
|
||||
emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
|
||||
}
|
||||
}
|
||||
|
||||
QString forgeVersion;
|
||||
for(auto &loader: pack.minecraft.modLoaders)
|
||||
{
|
||||
auto id = loader.id;
|
||||
if(id.startsWith("forge-"))
|
||||
{
|
||||
id.remove("forge-");
|
||||
forgeVersion = id;
|
||||
continue;
|
||||
}
|
||||
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
||||
}
|
||||
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto mcVersion = pack.minecraft.version;
|
||||
// Hack to correct some 'special sauce'...
|
||||
if(mcVersion.endsWith('.'))
|
||||
{
|
||||
mcVersion.remove(QRegExp("[.]+$"));
|
||||
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
|
||||
}
|
||||
auto components = instance.getComponentList();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", mcVersion, true);
|
||||
if(!forgeVersion.isEmpty())
|
||||
{
|
||||
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
|
||||
if(forgeVersion == "recommended")
|
||||
{
|
||||
if(forgemap.contains(mcVersion))
|
||||
{
|
||||
forgeVersion = forgemap[mcVersion];
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion));
|
||||
}
|
||||
}
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||
}
|
||||
if (m_instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(m_instIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pack.name.contains("Direwolf20"))
|
||||
{
|
||||
instance.setIconKey("steve");
|
||||
}
|
||||
else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast"))
|
||||
{
|
||||
instance.setIconKey("ftb_logo");
|
||||
}
|
||||
else
|
||||
{
|
||||
// default to something other than the MultiMC default to distinguish these
|
||||
instance.setIconKey("flame");
|
||||
}
|
||||
}
|
||||
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
||||
QFileInfo jarmodsInfo(jarmodsPath);
|
||||
if(jarmodsInfo.isDir())
|
||||
{
|
||||
// install all the jar mods
|
||||
qDebug() << "Found jarmods:";
|
||||
QDir jarmodsDir(jarmodsPath);
|
||||
QStringList jarMods;
|
||||
for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
|
||||
{
|
||||
qDebug() << info.fileName();
|
||||
jarMods.push_back(info.absoluteFilePath());
|
||||
}
|
||||
auto profile = instance.getComponentList();
|
||||
profile->installJarMods(jarMods);
|
||||
// nuke the original files
|
||||
FS::deletePath(jarmodsPath);
|
||||
}
|
||||
instance.setName(m_instName);
|
||||
m_modIdResolver.reset(new Flame::FileResolvingTask(pack));
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
|
||||
{
|
||||
auto results = m_modIdResolver->getResults();
|
||||
m_filesNetJob.reset(new NetJob(tr("Mod download")));
|
||||
for(auto result: results.files)
|
||||
{
|
||||
QString filename = result.fileName;
|
||||
if(!result.required)
|
||||
{
|
||||
filename += ".disabled";
|
||||
}
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
||||
auto path = FS::PathCombine(m_stagingPath , relpath);
|
||||
|
||||
switch(result.type)
|
||||
{
|
||||
case Flame::File::Type::Folder:
|
||||
{
|
||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
||||
}
|
||||
case Flame::File::Type::SingleFile:
|
||||
case Flame::File::Type::Mod:
|
||||
{
|
||||
qDebug() << "Will download" << result.url << "to" << path;
|
||||
auto dl = Net::Download::makeFile(result.url, path);
|
||||
m_filesNetJob->addNetAction(dl);
|
||||
break;
|
||||
}
|
||||
case Flame::File::Type::Modpack:
|
||||
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
|
||||
break;
|
||||
case Flame::File::Type::Cmod2:
|
||||
case Flame::File::Type::Ctoc:
|
||||
case Flame::File::Type::Unknown:
|
||||
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_modIdResolver.reset();
|
||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
m_filesNetJob.reset();
|
||||
emitSucceeded();
|
||||
}
|
||||
);
|
||||
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
m_filesNetJob.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
setStatus(tr("Downloading mods..."));
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
|
||||
{
|
||||
m_modIdResolver.reset();
|
||||
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
|
||||
});
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
|
||||
{
|
||||
setStatus(status);
|
||||
});
|
||||
m_modIdResolver->start();
|
||||
}
|
||||
|
||||
void InstanceImportTask::processMultiMC()
|
||||
{
|
||||
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
|
||||
// reset time played on import... because packs.
|
||||
instance.resetTimePlayed();
|
||||
|
||||
// set a new nice name
|
||||
instance.setName(m_instName);
|
||||
|
||||
// if the icon was specified by user, use that. otherwise pull icon from the pack
|
||||
if (m_instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(m_instIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_instIcon = instance.iconKey();
|
||||
|
||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
||||
if (!importIconPath.isNull() && QFile::exists(importIconPath))
|
||||
{
|
||||
// import icon
|
||||
auto iconList = ENV.icons();
|
||||
if (iconList->iconFileExists(m_instIcon))
|
||||
{
|
||||
iconList->deleteIcon(m_instIcon);
|
||||
}
|
||||
iconList->installIcons({importIconPath});
|
||||
}
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -1,47 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "InstanceTask.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
|
||||
class QuaZip;
|
||||
namespace Flame
|
||||
{
|
||||
Q_OBJECT
|
||||
class FileResolvingTask;
|
||||
}
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
explicit InstanceImportTask(const QUrl sourceUrl);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
void extractAndTweak();
|
||||
void processZipPack();
|
||||
void processMultiMC();
|
||||
void processFlame();
|
||||
|
||||
private slots:
|
||||
void downloadSucceeded();
|
||||
void downloadFailed(QString reason);
|
||||
void downloadProgressChanged(qint64 current, qint64 total);
|
||||
void extractFinished();
|
||||
void extractAborted();
|
||||
void downloadSucceeded();
|
||||
void downloadFailed(QString reason);
|
||||
void downloadProgressChanged(qint64 current, qint64 total);
|
||||
void extractFinished();
|
||||
void extractAborted();
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
NetJobPtr m_filesNetJob;
|
||||
QUrl m_sourceUrl;
|
||||
BaseInstanceProvider * m_target;
|
||||
QString m_archivePath;
|
||||
bool m_downloadRequired = false;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
QString m_stagingPath;
|
||||
QFuture<QStringList> m_extractFuture;
|
||||
QFutureWatcher<QStringList> m_extractFutureWatcher;
|
||||
NetJobPtr m_filesNetJob;
|
||||
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
|
||||
QUrl m_sourceUrl;
|
||||
QString m_archivePath;
|
||||
bool m_downloadRequired = false;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<QStringList> m_extractFuture;
|
||||
QFutureWatcher<QStringList> m_extractFutureWatcher;
|
||||
enum class ModpackType{
|
||||
Unknown,
|
||||
MultiMC,
|
||||
Flame
|
||||
} m_modpackType = ModpackType::Unknown;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,88 +21,145 @@
|
||||
#include <QList>
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class BaseInstance;
|
||||
class QDir;
|
||||
class InstanceTask;
|
||||
using InstanceId = QString;
|
||||
using GroupId = QString;
|
||||
using InstanceLocator = std::pair<InstancePtr, int>;
|
||||
|
||||
enum class InstCreateError
|
||||
{
|
||||
NoCreateError = 0,
|
||||
NoSuchVersion,
|
||||
UnknownCreateError,
|
||||
InstExists,
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
enum class GroupsState
|
||||
{
|
||||
NotLoaded,
|
||||
Steady,
|
||||
Dirty
|
||||
};
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
|
||||
public:
|
||||
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
enum AdditionalRoles
|
||||
{
|
||||
GroupRole = Qt::UserRole,
|
||||
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
|
||||
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
|
||||
};
|
||||
/*!
|
||||
* \brief Error codes returned by functions in the InstanceList class.
|
||||
* NoError Indicates that no error occurred.
|
||||
* UnknownError indicates that an unspecified error occurred.
|
||||
*/
|
||||
enum InstListError
|
||||
{
|
||||
NoError = 0,
|
||||
UnknownError
|
||||
};
|
||||
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
|
||||
|
||||
InstancePtr at(int i) const
|
||||
{
|
||||
return m_instances.at(i);
|
||||
}
|
||||
enum AdditionalRoles
|
||||
{
|
||||
GroupRole = Qt::UserRole,
|
||||
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
|
||||
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
|
||||
};
|
||||
/*!
|
||||
* \brief Error codes returned by functions in the InstanceList class.
|
||||
* NoError Indicates that no error occurred.
|
||||
* UnknownError indicates that an unspecified error occurred.
|
||||
*/
|
||||
enum InstListError
|
||||
{
|
||||
NoError = 0,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
int count() const
|
||||
{
|
||||
return m_instances.count();
|
||||
}
|
||||
InstancePtr at(int i) const
|
||||
{
|
||||
return m_instances.at(i);
|
||||
}
|
||||
|
||||
InstListError loadList(bool complete = false);
|
||||
int count() const
|
||||
{
|
||||
return m_instances.count();
|
||||
}
|
||||
|
||||
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
|
||||
void addInstanceProvider(BaseInstanceProvider * provider);
|
||||
InstListError loadList();
|
||||
void saveNow();
|
||||
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
QStringList getGroups();
|
||||
|
||||
void deleteGroup(const QString & name);
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
QStringList getGroups();
|
||||
GroupId getInstanceGroup(const InstanceId & id) const;
|
||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||
|
||||
void deleteGroup(const GroupId & name);
|
||||
void deleteInstance(const InstanceId & id);
|
||||
|
||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||
Task * wrapInstanceTask(InstanceTask * task);
|
||||
|
||||
/**
|
||||
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
QString getStagedInstancePath();
|
||||
|
||||
/**
|
||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
|
||||
|
||||
/**
|
||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool destroyStagingPath(const QString & keyPath);
|
||||
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
void dataIsInvalid();
|
||||
void instancesChanged();
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
void groupsPublished(QSet<QString>);
|
||||
void providerUpdated();
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
void providerUpdated();
|
||||
void instanceDirContentsChanged(const QString &path);
|
||||
|
||||
private:
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
void suspendWatch();
|
||||
void resumeWatch();
|
||||
void add(const QList<InstancePtr> &list);
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
void suspendWatch();
|
||||
void resumeWatch();
|
||||
void add(const QList<InstancePtr> &list);
|
||||
void loadGroupList();
|
||||
void saveGroupList();
|
||||
QList<InstanceId> discoverInstances();
|
||||
InstancePtr loadInstance(const InstanceId& id);
|
||||
|
||||
protected:
|
||||
int m_watchLevel = 0;
|
||||
QSet<BaseInstanceProvider *> m_updatedProviders;
|
||||
QString m_instDir;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
|
||||
private:
|
||||
int m_watchLevel = 0;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QMap<InstanceId, GroupId> m_groupMap;
|
||||
QSet<InstanceId> instanceSet;
|
||||
bool m_groupsLoaded = false;
|
||||
bool m_instancesProbed = false;
|
||||
};
|
||||
|
||||
9
api/logic/InstanceTask.cpp
Normal file
9
api/logic/InstanceTask.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "InstanceTask.h"
|
||||
|
||||
InstanceTask::InstanceTask()
|
||||
{
|
||||
}
|
||||
|
||||
InstanceTask::~InstanceTask()
|
||||
{
|
||||
}
|
||||
53
api/logic/InstanceTask.h
Normal file
53
api/logic/InstanceTask.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceTask();
|
||||
virtual ~InstanceTask();
|
||||
|
||||
void setParentSettings(SettingsObjectPtr settings)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
}
|
||||
|
||||
void setStagingPath(const QString &stagingPath)
|
||||
{
|
||||
m_stagingPath = stagingPath;
|
||||
}
|
||||
|
||||
void setName(const QString &name)
|
||||
{
|
||||
m_instName = name;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_instName;
|
||||
}
|
||||
|
||||
void setIcon(const QString &icon)
|
||||
{
|
||||
m_instIcon = icon;
|
||||
}
|
||||
|
||||
void setGroup(const QString &group)
|
||||
{
|
||||
m_instGroup = group;
|
||||
}
|
||||
QString group() const
|
||||
{
|
||||
return m_instGroup;
|
||||
}
|
||||
|
||||
protected: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
QString m_stagingPath;
|
||||
};
|
||||
@@ -11,262 +11,262 @@ namespace Json
|
||||
{
|
||||
void write(const QJsonDocument &doc, const QString &filename)
|
||||
{
|
||||
FS::write(filename, doc.toJson());
|
||||
FS::write(filename, doc.toJson());
|
||||
}
|
||||
void write(const QJsonObject &object, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(object), filename);
|
||||
write(QJsonDocument(object), filename);
|
||||
}
|
||||
void write(const QJsonArray &array, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(array), filename);
|
||||
write(QJsonDocument(array), filename);
|
||||
}
|
||||
|
||||
QByteArray toBinary(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toBinaryData();
|
||||
return QJsonDocument(obj).toBinaryData();
|
||||
}
|
||||
QByteArray toBinary(const QJsonArray &array)
|
||||
{
|
||||
return QJsonDocument(array).toBinaryData();
|
||||
return QJsonDocument(array).toBinaryData();
|
||||
}
|
||||
QByteArray toText(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
QByteArray toText(const QJsonArray &array)
|
||||
{
|
||||
return QJsonDocument(array).toJson(QJsonDocument::Compact);
|
||||
return QJsonDocument(array).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
static bool isBinaryJson(const QByteArray &data)
|
||||
{
|
||||
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
|
||||
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
|
||||
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
|
||||
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
|
||||
}
|
||||
QJsonDocument requireDocument(const QByteArray &data, const QString &what)
|
||||
{
|
||||
if (isBinaryJson(data))
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromBinaryData(data);
|
||||
if (doc.isNull())
|
||||
{
|
||||
throw JsonException(what + ": Invalid JSON (binary JSON detected)");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
else
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
if (isBinaryJson(data))
|
||||
{
|
||||
QJsonDocument doc = QJsonDocument::fromBinaryData(data);
|
||||
if (doc.isNull())
|
||||
{
|
||||
throw JsonException(what + ": Invalid JSON (binary JSON detected)");
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
else
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
throw JsonException(what + ": Error parsing JSON: " + error.errorString());
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
QJsonDocument requireDocument(const QString &filename, const QString &what)
|
||||
{
|
||||
return requireDocument(FS::read(filename), what);
|
||||
return requireDocument(FS::read(filename), what);
|
||||
}
|
||||
QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
|
||||
{
|
||||
if (!doc.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return doc.object();
|
||||
if (!doc.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return doc.object();
|
||||
}
|
||||
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
|
||||
{
|
||||
if (!doc.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return doc.array();
|
||||
if (!doc.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return doc.array();
|
||||
}
|
||||
|
||||
void writeString(QJsonObject &to, const QString &key, const QString &value)
|
||||
{
|
||||
if (!value.isEmpty())
|
||||
{
|
||||
to.insert(key, value);
|
||||
}
|
||||
if (!value.isEmpty())
|
||||
{
|
||||
to.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
|
||||
{
|
||||
if (!values.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
for(auto value: values)
|
||||
{
|
||||
array.append(value);
|
||||
}
|
||||
to.insert(key, array);
|
||||
}
|
||||
if (!values.isEmpty())
|
||||
{
|
||||
QJsonArray array;
|
||||
for(auto value: values)
|
||||
{
|
||||
array.append(value);
|
||||
}
|
||||
to.insert(key, array);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url)
|
||||
{
|
||||
return QJsonValue(url.toString(QUrl::FullyEncoded));
|
||||
return QJsonValue(url.toString(QUrl::FullyEncoded));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QByteArray>(const QByteArray &data)
|
||||
{
|
||||
return QJsonValue(QString::fromLatin1(data.toHex()));
|
||||
return QJsonValue(QString::fromLatin1(data.toHex()));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
|
||||
{
|
||||
return QJsonValue(datetime.toString(Qt::ISODate));
|
||||
return QJsonValue(datetime.toString(Qt::ISODate));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDir>(const QDir &dir)
|
||||
{
|
||||
return QDir::current().relativeFilePath(dir.absolutePath());
|
||||
return QDir::current().relativeFilePath(dir.absolutePath());
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUuid>(const QUuid &uuid)
|
||||
{
|
||||
return uuid.toString();
|
||||
return uuid.toString();
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QVariant>(const QVariant &variant)
|
||||
{
|
||||
return QJsonValue::fromVariant(variant);
|
||||
return QJsonValue::fromVariant(variant);
|
||||
}
|
||||
|
||||
|
||||
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
// ensure that the string can be safely cast to Latin1
|
||||
if (string != QString::fromLatin1(string.toLatin1()))
|
||||
{
|
||||
throw JsonException(what + " is not encodable as Latin1");
|
||||
}
|
||||
return QByteArray::fromHex(string.toLatin1());
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
// ensure that the string can be safely cast to Latin1
|
||||
if (string != QString::fromLatin1(string.toLatin1()))
|
||||
{
|
||||
throw JsonException(what + " is not encodable as Latin1");
|
||||
}
|
||||
return QByteArray::fromHex(string.toLatin1());
|
||||
}
|
||||
|
||||
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return value.toArray();
|
||||
if (!value.isArray())
|
||||
{
|
||||
throw JsonException(what + " is not an array");
|
||||
}
|
||||
return value.toArray();
|
||||
}
|
||||
|
||||
|
||||
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isString())
|
||||
{
|
||||
throw JsonException(what + " is not a string");
|
||||
}
|
||||
return value.toString();
|
||||
if (!value.isString())
|
||||
{
|
||||
throw JsonException(what + " is not a string");
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isBool())
|
||||
{
|
||||
throw JsonException(what + " is not a bool");
|
||||
}
|
||||
return value.toBool();
|
||||
if (!value.isBool())
|
||||
{
|
||||
throw JsonException(what + " is not a bool");
|
||||
}
|
||||
return value.toBool();
|
||||
}
|
||||
|
||||
template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isDouble())
|
||||
{
|
||||
throw JsonException(what + " is not a double");
|
||||
}
|
||||
return value.toDouble();
|
||||
if (!value.isDouble())
|
||||
{
|
||||
throw JsonException(what + " is not a double");
|
||||
}
|
||||
return value.toDouble();
|
||||
}
|
||||
|
||||
template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const double doubl = requireIsType<double>(value, what);
|
||||
if (fmod(doubl, 1) != 0)
|
||||
{
|
||||
throw JsonException(what + " is not an integer");
|
||||
}
|
||||
return int(doubl);
|
||||
const double doubl = requireIsType<double>(value, what);
|
||||
if (fmod(doubl, 1) != 0)
|
||||
{
|
||||
throw JsonException(what + " is not an integer");
|
||||
}
|
||||
return int(doubl);
|
||||
}
|
||||
|
||||
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
|
||||
if (!datetime.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a ISO formatted date/time value");
|
||||
}
|
||||
return datetime;
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
|
||||
if (!datetime.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a ISO formatted date/time value");
|
||||
}
|
||||
return datetime;
|
||||
}
|
||||
|
||||
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
if (string.isEmpty())
|
||||
{
|
||||
return QUrl();
|
||||
}
|
||||
const QUrl url = QUrl(string, QUrl::StrictMode);
|
||||
if (!url.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a correctly formatted URL");
|
||||
}
|
||||
return url;
|
||||
const QString string = ensureIsType<QString>(value, what);
|
||||
if (string.isEmpty())
|
||||
{
|
||||
return QUrl();
|
||||
}
|
||||
const QUrl url = QUrl(string, QUrl::StrictMode);
|
||||
if (!url.isValid())
|
||||
{
|
||||
throw JsonException(what + " is not a correctly formatted URL");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
// FIXME: does not handle invalid characters!
|
||||
return QDir::current().absoluteFilePath(string);
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
// FIXME: does not handle invalid characters!
|
||||
return QDir::current().absoluteFilePath(string);
|
||||
}
|
||||
|
||||
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QUuid uuid = QUuid(string);
|
||||
if (uuid.toString() != string) // converts back => valid
|
||||
{
|
||||
throw JsonException(what + " is not a valid UUID");
|
||||
}
|
||||
return uuid;
|
||||
const QString string = requireIsType<QString>(value, what);
|
||||
const QUuid uuid = QUuid(string);
|
||||
if (uuid.toString() != string) // converts back => valid
|
||||
{
|
||||
throw JsonException(what + " is not a valid UUID");
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (!value.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return value.toObject();
|
||||
if (!value.isObject())
|
||||
{
|
||||
throw JsonException(what + " is not an object");
|
||||
}
|
||||
return value.toObject();
|
||||
}
|
||||
|
||||
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value.toVariant();
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value.toVariant();
|
||||
}
|
||||
|
||||
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value;
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
160
api/logic/Json.h
160
api/logic/Json.h
@@ -19,7 +19,7 @@ namespace Json
|
||||
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
|
||||
{
|
||||
public:
|
||||
JsonException(const QString &message) : Exception(message) {}
|
||||
JsonException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/// @throw FileSystemException
|
||||
@@ -51,7 +51,7 @@ void writeStringList(QJsonObject & to, const QString &key, const QStringList &va
|
||||
template<typename T>
|
||||
QJsonValue toJson(const T &t)
|
||||
{
|
||||
return QJsonValue(t);
|
||||
return QJsonValue(t);
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url);
|
||||
@@ -69,12 +69,12 @@ QJsonValue toJson<QVariant>(const QVariant &variant);
|
||||
template<typename T>
|
||||
QJsonArray toJsonArray(const QList<T> &container)
|
||||
{
|
||||
QJsonArray array;
|
||||
for (const T item : container)
|
||||
{
|
||||
array.append(toJson<T>(item));
|
||||
}
|
||||
return array;
|
||||
QJsonArray array;
|
||||
for (const T item : container)
|
||||
{
|
||||
array.append(toJson<T>(item));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
////////////////// READING ////////////////////
|
||||
@@ -115,119 +115,119 @@ template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value
|
||||
template <typename T>
|
||||
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
|
||||
{
|
||||
if (value.isUndefined() || value.isNull())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
try
|
||||
{
|
||||
return requireIsType<T>(value, what);
|
||||
}
|
||||
catch (JsonException &)
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
if (value.isUndefined() || value.isNull())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
try
|
||||
{
|
||||
return requireIsType<T>(value, what);
|
||||
}
|
||||
catch (const JsonException &)
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
}
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return requireIsType<T>(parent.value(key), localWhat);
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return requireIsType<T>(parent.value(key), localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsType<T>(parent.value(key), default_, localWhat);
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsType<T>(parent.value(key), default_, localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
|
||||
{
|
||||
const QJsonArray array = requireArray(doc);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, "Document"));
|
||||
}
|
||||
return out;
|
||||
const QJsonArray array = requireArray(doc);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, "Document"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
|
||||
{
|
||||
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, what));
|
||||
}
|
||||
return out;
|
||||
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
|
||||
QVector<T> out;
|
||||
for (const QJsonValue val : array)
|
||||
{
|
||||
out.append(requireIsType<T>(val, what));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
|
||||
{
|
||||
if (value.isUndefined())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(value, what);
|
||||
if (value.isUndefined())
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(value, what);
|
||||
}
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), localWhat);
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
throw JsonException(localWhat + "s parent does not contain " + localWhat);
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), localWhat);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
|
||||
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
|
||||
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
|
||||
{
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
|
||||
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
|
||||
if (!parent.contains(key))
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
|
||||
}
|
||||
|
||||
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
|
||||
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
|
||||
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(value, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(value, default_, what); \
|
||||
} \
|
||||
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(parent, key, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(parent, key, default_, what); \
|
||||
}
|
||||
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(value, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(value, default_, what); \
|
||||
} \
|
||||
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
|
||||
{ \
|
||||
return requireIsType<TYPE>(parent, key, what); \
|
||||
} \
|
||||
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
|
||||
{ \
|
||||
return ensureIsType<TYPE>(parent, key, default_, what); \
|
||||
}
|
||||
|
||||
JSON_HELPERFUNCTIONS(Array, QJsonArray)
|
||||
JSON_HELPERFUNCTIONS(Object, QJsonObject)
|
||||
|
||||
@@ -4,157 +4,157 @@
|
||||
|
||||
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
|
||||
{
|
||||
// QProcess has a strange interface... let's map a lot of those into a few.
|
||||
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
||||
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
|
||||
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
|
||||
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
|
||||
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
|
||||
// QProcess has a strange interface... let's map a lot of those into a few.
|
||||
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
|
||||
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
|
||||
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
|
||||
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
|
||||
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
|
||||
}
|
||||
|
||||
LoggedProcess::~LoggedProcess()
|
||||
{
|
||||
if(m_is_detachable)
|
||||
{
|
||||
setProcessState(QProcess::NotRunning);
|
||||
}
|
||||
if(m_is_detachable)
|
||||
{
|
||||
setProcessState(QProcess::NotRunning);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList reprocess(const QByteArray & data, QString & leftover)
|
||||
{
|
||||
QString str = leftover + QString::fromLocal8Bit(data);
|
||||
QString str = leftover + QString::fromLocal8Bit(data);
|
||||
|
||||
str.remove('\r');
|
||||
QStringList lines = str.split("\n");
|
||||
leftover = lines.takeLast();
|
||||
return lines;
|
||||
str.remove('\r');
|
||||
QStringList lines = str.split("\n");
|
||||
leftover = lines.takeLast();
|
||||
return lines;
|
||||
}
|
||||
|
||||
void LoggedProcess::on_stdErr()
|
||||
{
|
||||
auto lines = reprocess(readAllStandardError(), m_err_leftover);
|
||||
emit log(lines, MessageLevel::StdErr);
|
||||
auto lines = reprocess(readAllStandardError(), m_err_leftover);
|
||||
emit log(lines, MessageLevel::StdErr);
|
||||
}
|
||||
|
||||
void LoggedProcess::on_stdOut()
|
||||
{
|
||||
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
|
||||
emit log(lines, MessageLevel::StdOut);
|
||||
auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
|
||||
emit log(lines, MessageLevel::StdOut);
|
||||
}
|
||||
|
||||
void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
|
||||
{
|
||||
// save the exit code
|
||||
m_exit_code = exit_code;
|
||||
// save the exit code
|
||||
m_exit_code = exit_code;
|
||||
|
||||
// Flush console window
|
||||
if (!m_err_leftover.isEmpty())
|
||||
{
|
||||
emit log({m_err_leftover}, MessageLevel::StdErr);
|
||||
m_err_leftover.clear();
|
||||
}
|
||||
if (!m_out_leftover.isEmpty())
|
||||
{
|
||||
emit log({m_err_leftover}, MessageLevel::StdOut);
|
||||
m_out_leftover.clear();
|
||||
}
|
||||
// Flush console window
|
||||
if (!m_err_leftover.isEmpty())
|
||||
{
|
||||
emit log({m_err_leftover}, MessageLevel::StdErr);
|
||||
m_err_leftover.clear();
|
||||
}
|
||||
if (!m_out_leftover.isEmpty())
|
||||
{
|
||||
emit log({m_err_leftover}, MessageLevel::StdOut);
|
||||
m_out_leftover.clear();
|
||||
}
|
||||
|
||||
// based on state, send signals
|
||||
if (!m_is_aborting)
|
||||
{
|
||||
if (status == QProcess::NormalExit)
|
||||
{
|
||||
//: Message displayed on instance exit
|
||||
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC);
|
||||
changeState(LoggedProcess::Finished);
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Message displayed on instance crashed
|
||||
if(exit_code == -1)
|
||||
emit log({tr("Process crashed.")}, MessageLevel::MultiMC);
|
||||
else
|
||||
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC);
|
||||
changeState(LoggedProcess::Crashed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Message displayed after the instance exits due to kill request
|
||||
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
|
||||
changeState(LoggedProcess::Aborted);
|
||||
}
|
||||
// based on state, send signals
|
||||
if (!m_is_aborting)
|
||||
{
|
||||
if (status == QProcess::NormalExit)
|
||||
{
|
||||
//: Message displayed on instance exit
|
||||
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC);
|
||||
changeState(LoggedProcess::Finished);
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Message displayed on instance crashed
|
||||
if(exit_code == -1)
|
||||
emit log({tr("Process crashed.")}, MessageLevel::MultiMC);
|
||||
else
|
||||
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC);
|
||||
changeState(LoggedProcess::Crashed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//: Message displayed after the instance exits due to kill request
|
||||
emit log({tr("Process was killed by user.")}, MessageLevel::Error);
|
||||
changeState(LoggedProcess::Aborted);
|
||||
}
|
||||
}
|
||||
|
||||
void LoggedProcess::on_error(QProcess::ProcessError error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
case QProcess::FailedToStart:
|
||||
{
|
||||
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
|
||||
changeState(LoggedProcess::FailedToStart);
|
||||
break;
|
||||
}
|
||||
// we'll just ignore those... never needed them
|
||||
case QProcess::Crashed:
|
||||
case QProcess::ReadError:
|
||||
case QProcess::Timedout:
|
||||
case QProcess::UnknownError:
|
||||
case QProcess::WriteError:
|
||||
break;
|
||||
}
|
||||
switch(error)
|
||||
{
|
||||
case QProcess::FailedToStart:
|
||||
{
|
||||
emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
|
||||
changeState(LoggedProcess::FailedToStart);
|
||||
break;
|
||||
}
|
||||
// we'll just ignore those... never needed them
|
||||
case QProcess::Crashed:
|
||||
case QProcess::ReadError:
|
||||
case QProcess::Timedout:
|
||||
case QProcess::UnknownError:
|
||||
case QProcess::WriteError:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LoggedProcess::kill()
|
||||
{
|
||||
m_is_aborting = true;
|
||||
QProcess::kill();
|
||||
m_is_aborting = true;
|
||||
QProcess::kill();
|
||||
}
|
||||
|
||||
int LoggedProcess::exitCode() const
|
||||
{
|
||||
return m_exit_code;
|
||||
return m_exit_code;
|
||||
}
|
||||
|
||||
void LoggedProcess::changeState(LoggedProcess::State state)
|
||||
{
|
||||
if(state == m_state)
|
||||
return;
|
||||
m_state = state;
|
||||
emit stateChanged(m_state);
|
||||
if(state == m_state)
|
||||
return;
|
||||
m_state = state;
|
||||
emit stateChanged(m_state);
|
||||
}
|
||||
|
||||
LoggedProcess::State LoggedProcess::state() const
|
||||
{
|
||||
return m_state;
|
||||
return m_state;
|
||||
}
|
||||
|
||||
void LoggedProcess::on_stateChange(QProcess::ProcessState state)
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case QProcess::NotRunning:
|
||||
break; // let's not - there are too many that handle this already.
|
||||
case QProcess::Starting:
|
||||
{
|
||||
if(m_state != LoggedProcess::NotRunning)
|
||||
{
|
||||
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
|
||||
}
|
||||
changeState(LoggedProcess::Starting);
|
||||
return;
|
||||
}
|
||||
case QProcess::Running:
|
||||
{
|
||||
if(m_state != LoggedProcess::Starting)
|
||||
{
|
||||
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
|
||||
}
|
||||
changeState(LoggedProcess::Running);
|
||||
return;
|
||||
}
|
||||
}
|
||||
switch(state)
|
||||
{
|
||||
case QProcess::NotRunning:
|
||||
break; // let's not - there are too many that handle this already.
|
||||
case QProcess::Starting:
|
||||
{
|
||||
if(m_state != LoggedProcess::NotRunning)
|
||||
{
|
||||
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
|
||||
}
|
||||
changeState(LoggedProcess::Starting);
|
||||
return;
|
||||
}
|
||||
case QProcess::Running:
|
||||
{
|
||||
if(m_state != LoggedProcess::Starting)
|
||||
{
|
||||
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
|
||||
}
|
||||
changeState(LoggedProcess::Running);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
@@ -172,5 +172,5 @@ qint64 LoggedProcess::processId() const
|
||||
|
||||
void LoggedProcess::setDetachable(bool detachable)
|
||||
{
|
||||
m_is_detachable = detachable;
|
||||
m_is_detachable = detachable;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,54 +27,54 @@ class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
NotRunning,
|
||||
Starting,
|
||||
FailedToStart,
|
||||
Running,
|
||||
Finished,
|
||||
Crashed,
|
||||
Aborted
|
||||
};
|
||||
enum State
|
||||
{
|
||||
NotRunning,
|
||||
Starting,
|
||||
FailedToStart,
|
||||
Running,
|
||||
Finished,
|
||||
Crashed,
|
||||
Aborted
|
||||
};
|
||||
|
||||
public:
|
||||
explicit LoggedProcess(QObject* parent = 0);
|
||||
virtual ~LoggedProcess();
|
||||
explicit LoggedProcess(QObject* parent = 0);
|
||||
virtual ~LoggedProcess();
|
||||
|
||||
State state() const;
|
||||
int exitCode() const;
|
||||
qint64 processId() const;
|
||||
State state() const;
|
||||
int exitCode() const;
|
||||
qint64 processId() const;
|
||||
|
||||
void setDetachable(bool detachable);
|
||||
void setDetachable(bool detachable);
|
||||
|
||||
signals:
|
||||
void log(QStringList lines, MessageLevel::Enum level);
|
||||
void stateChanged(LoggedProcess::State state);
|
||||
void log(QStringList lines, MessageLevel::Enum level);
|
||||
void stateChanged(LoggedProcess::State state);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief kill the process - equivalent to kill -9
|
||||
*/
|
||||
void kill();
|
||||
/**
|
||||
* @brief kill the process - equivalent to kill -9
|
||||
*/
|
||||
void kill();
|
||||
|
||||
|
||||
private slots:
|
||||
void on_stdErr();
|
||||
void on_stdOut();
|
||||
void on_exit(int exit_code, QProcess::ExitStatus status);
|
||||
void on_error(QProcess::ProcessError error);
|
||||
void on_stateChange(QProcess::ProcessState);
|
||||
void on_stdErr();
|
||||
void on_stdOut();
|
||||
void on_exit(int exit_code, QProcess::ExitStatus status);
|
||||
void on_error(QProcess::ProcessError error);
|
||||
void on_stateChange(QProcess::ProcessState);
|
||||
|
||||
private:
|
||||
void changeState(LoggedProcess::State state);
|
||||
void changeState(LoggedProcess::State state);
|
||||
|
||||
private:
|
||||
QString m_err_leftover;
|
||||
QString m_out_leftover;
|
||||
bool m_killed = false;
|
||||
State m_state = NotRunning;
|
||||
int m_exit_code = 0;
|
||||
bool m_is_aborting = false;
|
||||
bool m_is_detachable = false;
|
||||
QString m_err_leftover;
|
||||
QString m_out_leftover;
|
||||
bool m_killed = false;
|
||||
State m_state = NotRunning;
|
||||
int m_exit_code = 0;
|
||||
bool m_is_aborting = false;
|
||||
bool m_is_detachable = false;
|
||||
};
|
||||
|
||||
@@ -3,74 +3,74 @@
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
static inline QChar getNextChar(const QString &s, int location)
|
||||
{
|
||||
return (location < s.length()) ? s.at(location) : QChar();
|
||||
return (location < s.length()) ? s.at(location) : QChar();
|
||||
}
|
||||
|
||||
/// TAKEN FROM Qt, because it doesn't expose it intelligently
|
||||
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
|
||||
{
|
||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
||||
{
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
QChar c2 = getNextChar(s2, l2);
|
||||
while (c2.isSpace())
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
|
||||
{
|
||||
// skip spaces, tabs and 0's
|
||||
QChar c1 = getNextChar(s1, l1);
|
||||
while (c1.isSpace())
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
QChar c2 = getNextChar(s2, l2);
|
||||
while (c2.isSpace())
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
if (c1.isDigit() && c2.isDigit())
|
||||
{
|
||||
while (c1.digitValue() == 0)
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
while (c2.digitValue() == 0)
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
if (c1.isDigit() && c2.isDigit())
|
||||
{
|
||||
while (c1.digitValue() == 0)
|
||||
c1 = getNextChar(s1, ++l1);
|
||||
while (c2.digitValue() == 0)
|
||||
c2 = getNextChar(s2, ++l2);
|
||||
|
||||
int lookAheadLocation1 = l1;
|
||||
int lookAheadLocation2 = l2;
|
||||
int currentReturnValue = 0;
|
||||
// find the last digit, setting currentReturnValue as we go if it isn't equal
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2;
|
||||
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
|
||||
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
|
||||
{
|
||||
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
|
||||
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
|
||||
if (!is1ADigit && !is2ADigit)
|
||||
break;
|
||||
if (!is1ADigit)
|
||||
return -1;
|
||||
if (!is2ADigit)
|
||||
return 1;
|
||||
if (currentReturnValue == 0)
|
||||
{
|
||||
if (lookAhead1 < lookAhead2)
|
||||
{
|
||||
currentReturnValue = -1;
|
||||
}
|
||||
else if (lookAhead1 > lookAhead2)
|
||||
{
|
||||
currentReturnValue = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentReturnValue != 0)
|
||||
return currentReturnValue;
|
||||
}
|
||||
if (cs == Qt::CaseInsensitive)
|
||||
{
|
||||
if (!c1.isLower())
|
||||
c1 = c1.toLower();
|
||||
if (!c2.isLower())
|
||||
c2 = c2.toLower();
|
||||
}
|
||||
int r = QString::localeAwareCompare(c1, c2);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r > 0)
|
||||
return 1;
|
||||
}
|
||||
// The two strings are the same (02 == 2) so fall back to the normal sort
|
||||
return QString::compare(s1, s2, cs);
|
||||
int lookAheadLocation1 = l1;
|
||||
int lookAheadLocation2 = l2;
|
||||
int currentReturnValue = 0;
|
||||
// find the last digit, setting currentReturnValue as we go if it isn't equal
|
||||
for (QChar lookAhead1 = c1, lookAhead2 = c2;
|
||||
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
|
||||
lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
|
||||
lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
|
||||
{
|
||||
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
|
||||
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
|
||||
if (!is1ADigit && !is2ADigit)
|
||||
break;
|
||||
if (!is1ADigit)
|
||||
return -1;
|
||||
if (!is2ADigit)
|
||||
return 1;
|
||||
if (currentReturnValue == 0)
|
||||
{
|
||||
if (lookAhead1 < lookAhead2)
|
||||
{
|
||||
currentReturnValue = -1;
|
||||
}
|
||||
else if (lookAhead1 > lookAhead2)
|
||||
{
|
||||
currentReturnValue = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentReturnValue != 0)
|
||||
return currentReturnValue;
|
||||
}
|
||||
if (cs == Qt::CaseInsensitive)
|
||||
{
|
||||
if (!c1.isLower())
|
||||
c1 = c1.toLower();
|
||||
if (!c2.isLower())
|
||||
c2 = c2.toLower();
|
||||
}
|
||||
int r = QString::localeAwareCompare(c1, c2);
|
||||
if (r < 0)
|
||||
return -1;
|
||||
if (r > 0)
|
||||
return 1;
|
||||
}
|
||||
// The two strings are the same (02 == 2) so fall back to the normal sort
|
||||
return QString::compare(s1, s2, cs);
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
|
||||
namespace Strings
|
||||
{
|
||||
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
|
||||
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
|
||||
}
|
||||
|
||||
@@ -1,491 +1,255 @@
|
||||
/*
|
||||
Copyright (C) 2010 Roberto Pompermaier
|
||||
Copyright (C) 2005-2014 Sergey A. Tachenov
|
||||
|
||||
Parts of this file were part of QuaZIP.
|
||||
|
||||
QuaZIP is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QuaZIP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
See COPYING file for the full LGPL text.
|
||||
|
||||
Original ZIP package is copyrighted by Gilles Vollant and contributors,
|
||||
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
|
||||
*/
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <quazip.h>
|
||||
#include <JlCompress.h>
|
||||
#include <quazipdir.h>
|
||||
#include <quazipfile.h>
|
||||
#include <JlCompress.h>
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
bool copyData(QIODevice &inFile, QIODevice &outFile)
|
||||
// ours
|
||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter)
|
||||
{
|
||||
while (!inFile.atEnd())
|
||||
{
|
||||
char buf[4096];
|
||||
qint64 readLen = inFile.read(buf, 4096);
|
||||
if (readLen <= 0)
|
||||
return false;
|
||||
if (outFile.write(buf, readLen) != readLen)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
return JlCompress::extractDir(fileCompressed, dir);
|
||||
}
|
||||
|
||||
bool compressFile(QuaZip *zip, QString fileName, QString fileDest)
|
||||
{
|
||||
if (!zip)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend &&
|
||||
zip->getMode() != QuaZip::mdAdd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile inFile;
|
||||
inFile.setFileName(fileName);
|
||||
if (!inFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipFile outFile(zip);
|
||||
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!copyData(inFile, outFile) || outFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outFile.close();
|
||||
if (outFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
inFile.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QString>& added, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
|
||||
{
|
||||
if (!zip) return false;
|
||||
if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir directory(dir);
|
||||
if (!directory.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir origDirectory(origDir);
|
||||
if (dir != origDir)
|
||||
{
|
||||
QString internalDirName = origDirectory.relativeFilePath(dir);
|
||||
if(!blacklist || !blacklist->covers(internalDirName))
|
||||
{
|
||||
QuaZipFile dirZipFile(zip);
|
||||
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
|
||||
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
dirZipFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
|
||||
for (auto file: files)
|
||||
{
|
||||
if(!file.isDir())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(!compressSubDir(zip,file.absoluteFilePath(),origDir, added, prefix, blacklist))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
files = directory.entryInfoList(QDir::Files);
|
||||
for (auto file: files)
|
||||
{
|
||||
if(!file.isFile())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(file.absoluteFilePath()==zip->getZipName())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
|
||||
if(blacklist && blacklist->covers(filename))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(prefix.size())
|
||||
{
|
||||
filename = FS::PathCombine(prefix, filename);
|
||||
}
|
||||
added.insert(filename);
|
||||
if (!compressFile(zip,file.absoluteFilePath(),filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
std::function<bool(QString)> filter)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
|
||||
QuaZipFile fileInsideMod(&modZip);
|
||||
QuaZipFile zipOutFile(into);
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
||||
{
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
if (!filter(filename))
|
||||
{
|
||||
qDebug() << "Skipping file " << filename << " from "
|
||||
<< from.fileName() << " - filtered";
|
||||
continue;
|
||||
}
|
||||
if (contained.contains(filename))
|
||||
{
|
||||
qDebug() << "Skipping already contained file " << filename << " from "
|
||||
<< from.fileName();
|
||||
continue;
|
||||
}
|
||||
contained.insert(filename);
|
||||
|
||||
if (!fileInsideMod.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||
|
||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " in the jar";
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!copyData(fileInsideMod, zipOutFile))
|
||||
{
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||
return false;
|
||||
}
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
}
|
||||
return true;
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
|
||||
QuaZipFile fileInsideMod(&modZip);
|
||||
QuaZipFile zipOutFile(into);
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
||||
{
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
if (filter && !filter(filename))
|
||||
{
|
||||
qDebug() << "Skipping file " << filename << " from "
|
||||
<< from.fileName() << " - filtered";
|
||||
continue;
|
||||
}
|
||||
if (contained.contains(filename))
|
||||
{
|
||||
qDebug() << "Skipping already contained file " << filename << " from "
|
||||
<< from.fileName();
|
||||
continue;
|
||||
}
|
||||
contained.insert(filename);
|
||||
|
||||
if (!fileInsideMod.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " from " << from.fileName();
|
||||
return false;
|
||||
}
|
||||
|
||||
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
|
||||
|
||||
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
|
||||
{
|
||||
qCritical() << "Failed to open " << filename << " in the jar";
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
|
||||
{
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
qCritical() << "Failed to copy data of " << filename << " into the jar";
|
||||
return false;
|
||||
}
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
|
||||
{
|
||||
QuaZip zipOut(targetJarPath);
|
||||
if (!zipOut.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||
return false;
|
||||
}
|
||||
// Files already added to the jar.
|
||||
// These files will be skipped.
|
||||
QSet<QString> addedFiles;
|
||||
QuaZip zipOut(targetJarPath);
|
||||
if (!zipOut.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to open the minecraft.jar for modding";
|
||||
return false;
|
||||
}
|
||||
// Files already added to the jar.
|
||||
// These files will be skipped.
|
||||
QSet<QString> addedFiles;
|
||||
|
||||
// Modify the jar
|
||||
QListIterator<Mod> i(mods);
|
||||
// Modify the jar
|
||||
QListIterator<Mod> i(mods);
|
||||
i.toBack();
|
||||
while (i.hasPrevious())
|
||||
{
|
||||
const Mod &mod = i.previous();
|
||||
// do not merge disabled mods.
|
||||
if (!mod.enabled())
|
||||
continue;
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
if (!compressFile(&zipOut, filename.absoluteFilePath(),
|
||||
filename.fileName()))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
addedFiles.insert(filename.fileName());
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
auto filename = mod.filename();
|
||||
QString what_to_zip = filename.absoluteFilePath();
|
||||
QDir dir(what_to_zip);
|
||||
dir.cdUp();
|
||||
QString parent_dir = dir.absolutePath();
|
||||
if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
||||
<< filename.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
{
|
||||
const Mod &mod = i.previous();
|
||||
// do not merge disabled mods.
|
||||
if (!mod.enabled())
|
||||
continue;
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||
{
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod.filename();
|
||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
addedFiles.insert(filename.fileName());
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod.filename();
|
||||
QString what_to_zip = filename.absoluteFilePath();
|
||||
QDir dir(what_to_zip);
|
||||
dir.cdUp();
|
||||
QString parent_dir = dir.absolutePath();
|
||||
if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
||||
<< filename.absoluteFilePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we do not continue launching when something is missing or undefined...
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
return false;
|
||||
}
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");}))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to insert minecraft.jar contents.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0)
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// Recompress the jar
|
||||
zipOut.close();
|
||||
if (zipOut.getZipError() != 0)
|
||||
{
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to finalize minecraft.jar!";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::noFilter(QString)
|
||||
// ours
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::metaInfFilter(QString key)
|
||||
{
|
||||
if(key.contains("META-INF"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
|
||||
{
|
||||
QuaZip zip(zipFile);
|
||||
QDir().mkpath(QFileInfo(zipFile).absolutePath());
|
||||
if(!zip.open(QuaZip::mdCreate))
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
QSet<QString> added;
|
||||
if (!compressSubDir(&zip, dir, dir, added, prefix, blacklist))
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
zip.close();
|
||||
if(zip.getZipError()!=0)
|
||||
{
|
||||
QFile::remove(zipFile);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
return root;
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
return root;
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFolderOfFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
{
|
||||
result.append(root);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
findFilesInZip(zip, what, result, root + fileName);
|
||||
}
|
||||
return !result.isEmpty();
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
{
|
||||
if(fileName == what)
|
||||
{
|
||||
result.append(root);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
findFilesInZip(zip, what, result, root + fileName);
|
||||
}
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
bool removeFile(QStringList listFile)
|
||||
{
|
||||
bool ret = true;
|
||||
for (int i = 0; i < listFile.count(); i++)
|
||||
{
|
||||
ret &= QFile::remove(listFile.at(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
|
||||
{
|
||||
if(!zip)
|
||||
return false;
|
||||
|
||||
if (zip->getMode() != QuaZip::mdUnzip)
|
||||
return false;
|
||||
|
||||
if (!fileName.isEmpty())
|
||||
zip->setCurrentFile(fileName);
|
||||
|
||||
QuaZipFile inFile(zip);
|
||||
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
|
||||
return false;
|
||||
|
||||
// Controllo esistenza cartella file risultato
|
||||
QDir curDir;
|
||||
if (fileDest.endsWith('/'))
|
||||
{
|
||||
if (!curDir.mkpath(fileDest))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QuaZipFileInfo64 info;
|
||||
if (!zip->getCurrentFileInfo(&info))
|
||||
return false;
|
||||
|
||||
QFile::Permissions srcPerm = info.getPermissions();
|
||||
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
|
||||
{
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
QFile(fileDest).setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile outFile;
|
||||
outFile.setFileName(fileDest);
|
||||
if (!outFile.open(QIODevice::WriteOnly))
|
||||
return false;
|
||||
|
||||
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
outFile.close();
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
outFile.close();
|
||||
|
||||
inFile.close();
|
||||
if (inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
outFile.setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
||||
{
|
||||
QDir directory(target);
|
||||
QStringList extracted;
|
||||
if (!zip->goToFirstFile())
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
do
|
||||
{
|
||||
QString name = zip->getCurrentFileName();
|
||||
if(!name.startsWith(subdir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
name.remove(0, subdir.size());
|
||||
QString absFilePath = directory.absoluteFilePath(name);
|
||||
if(name.isEmpty())
|
||||
{
|
||||
absFilePath += "/";
|
||||
}
|
||||
if (!extractFile(zip, "", absFilePath))
|
||||
{
|
||||
removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
} while (zip->goToNextFile());
|
||||
return extracted;
|
||||
QDir directory(target);
|
||||
QStringList extracted;
|
||||
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
|
||||
if (!zip->goToFirstFile())
|
||||
{
|
||||
qWarning() << "Failed to seek to first file in zip";
|
||||
return QStringList();
|
||||
}
|
||||
do
|
||||
{
|
||||
QString name = zip->getCurrentFileName();
|
||||
if(!name.startsWith(subdir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
name.remove(0, subdir.size());
|
||||
QString absFilePath = directory.absoluteFilePath(name);
|
||||
if(name.isEmpty())
|
||||
{
|
||||
absFilePath += "/";
|
||||
}
|
||||
if (!JlCompress::extractFile(zip, "", absFilePath))
|
||||
{
|
||||
qWarning() << "Failed to extract file" << name << "to" << absFilePath;
|
||||
JlCompress::removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
qDebug() << "Extracted file" << name;
|
||||
} while (zip->goToNextFile());
|
||||
return extracted;
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
@@ -1,88 +1,71 @@
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "SeparatorPrefixTree.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QuaZip;
|
||||
#include <JlCompress.h>
|
||||
|
||||
namespace MMCZip
|
||||
{
|
||||
|
||||
/**
|
||||
* Compress a subdirectory.
|
||||
* \param parentZip Opened zip containing the parent directory.
|
||||
* \param dir The full path to the directory to pack.
|
||||
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
|
||||
* \param recursive Whether to pack sub-directories as well or only files.
|
||||
* \return true if success, false otherwise.
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
|
||||
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
|
||||
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
const JlCompress::FilterFunction filter = nullptr);
|
||||
|
||||
/**
|
||||
* Compress a whole directory.
|
||||
* \param fileCompressed The name of the archive.
|
||||
* \param dir The directory to compress.
|
||||
* \param recursive Whether to pack the subdirectories as well, or just regular files.
|
||||
* \return true if success, false otherwise.
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
|
||||
|
||||
/// filter function for @mergeZipFiles - passthrough
|
||||
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
*/
|
||||
QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
|
||||
|
||||
/// filter function for @mergeZipFiles - ignores METAINF
|
||||
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
* If a file is found in a path, no deeper paths are searched
|
||||
*
|
||||
* \return true if anything was found
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
|
||||
|
||||
/**
|
||||
* Merge two zip files, using a filter function
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
|
||||
/**
|
||||
* Extract a subdirectory from an archive
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
|
||||
/**
|
||||
* Extract a whole archive.
|
||||
*
|
||||
* \param fileCompressed The name of the archive.
|
||||
* \param dir The directory to extract to, the current directory if left empty.
|
||||
* \return The list of the full paths of the files extracted, empty on failure.
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
|
||||
|
||||
/**
|
||||
* Extract a whole archive.
|
||||
*
|
||||
* \param fileCompressed The name of the archive.
|
||||
* \param dir The directory to extract to, the current directory if
|
||||
* left empty.
|
||||
* \return The list of the full paths of the files extracted, empty on failure.
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
|
||||
|
||||
/**
|
||||
* Find a single file in archive by file name (not path)
|
||||
*
|
||||
* \return the path prefix where the file is
|
||||
*/
|
||||
QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
* If a file is found in a path, no deeper paths are searched
|
||||
*
|
||||
* \return true if anything was found
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
|
||||
|
||||
/**
|
||||
* Extract a single file to a destination
|
||||
*
|
||||
* \return true if it succeeds
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
|
||||
|
||||
/**
|
||||
* Extract a subdirectory from an archive
|
||||
*/
|
||||
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
|
||||
}
|
||||
|
||||
@@ -2,35 +2,35 @@
|
||||
|
||||
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
|
||||
{
|
||||
if (levelName == "MultiMC")
|
||||
return MessageLevel::MultiMC;
|
||||
else if (levelName == "Debug")
|
||||
return MessageLevel::Debug;
|
||||
else if (levelName == "Info")
|
||||
return MessageLevel::Info;
|
||||
else if (levelName == "Message")
|
||||
return MessageLevel::Message;
|
||||
else if (levelName == "Warning")
|
||||
return MessageLevel::Warning;
|
||||
else if (levelName == "Error")
|
||||
return MessageLevel::Error;
|
||||
else if (levelName == "Fatal")
|
||||
return MessageLevel::Fatal;
|
||||
// Skip PrePost, it's not exposed to !![]!
|
||||
// Also skip StdErr and StdOut
|
||||
else
|
||||
return MessageLevel::Unknown;
|
||||
if (levelName == "MultiMC")
|
||||
return MessageLevel::MultiMC;
|
||||
else if (levelName == "Debug")
|
||||
return MessageLevel::Debug;
|
||||
else if (levelName == "Info")
|
||||
return MessageLevel::Info;
|
||||
else if (levelName == "Message")
|
||||
return MessageLevel::Message;
|
||||
else if (levelName == "Warning")
|
||||
return MessageLevel::Warning;
|
||||
else if (levelName == "Error")
|
||||
return MessageLevel::Error;
|
||||
else if (levelName == "Fatal")
|
||||
return MessageLevel::Fatal;
|
||||
// Skip PrePost, it's not exposed to !![]!
|
||||
// Also skip StdErr and StdOut
|
||||
else
|
||||
return MessageLevel::Unknown;
|
||||
}
|
||||
|
||||
MessageLevel::Enum MessageLevel::fromLine(QString &line)
|
||||
{
|
||||
// Level prefix
|
||||
int endmark = line.indexOf("]!");
|
||||
if (line.startsWith("!![") && endmark != -1)
|
||||
{
|
||||
auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
|
||||
line = line.mid(endmark + 2);
|
||||
return level;
|
||||
}
|
||||
return MessageLevel::Unknown;
|
||||
// Level prefix
|
||||
int endmark = line.indexOf("]!");
|
||||
if (line.startsWith("!![") && endmark != -1)
|
||||
{
|
||||
auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
|
||||
line = line.mid(endmark + 2);
|
||||
return level;
|
||||
}
|
||||
return MessageLevel::Unknown;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,16 @@ namespace MessageLevel
|
||||
{
|
||||
enum Enum
|
||||
{
|
||||
Unknown, /**< No idea what this is or where it came from */
|
||||
StdOut, /**< Undetermined stderr messages */
|
||||
StdErr, /**< Undetermined stdout messages */
|
||||
MultiMC, /**< MultiMC Messages */
|
||||
Debug, /**< Debug Messages */
|
||||
Info, /**< Info Messages */
|
||||
Message, /**< Standard Messages */
|
||||
Warning, /**< Warnings */
|
||||
Error, /**< Errors */
|
||||
Fatal, /**< Fatal Errors */
|
||||
Unknown, /**< No idea what this is or where it came from */
|
||||
StdOut, /**< Undetermined stderr messages */
|
||||
StdErr, /**< Undetermined stdout messages */
|
||||
MultiMC, /**< MultiMC Messages */
|
||||
Debug, /**< Debug Messages */
|
||||
Info, /**< Info Messages */
|
||||
Message, /**< Standard Messages */
|
||||
Warning, /**< Warnings */
|
||||
Error, /**< Errors */
|
||||
Fatal, /**< Fatal Errors */
|
||||
};
|
||||
MessageLevel::Enum getLevel(const QString &levelName);
|
||||
|
||||
|
||||
@@ -1,93 +1,76 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
|
||||
class NullInstance: public BaseInstance
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
|
||||
:BaseInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
setVersionBroken(true);
|
||||
}
|
||||
virtual ~NullInstance() {};
|
||||
virtual bool setIntendedVersionId(QString) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual QString currentVersionId() const override
|
||||
{
|
||||
return "Null";
|
||||
};
|
||||
virtual QString intendedVersionId() const override
|
||||
{
|
||||
return "Null";
|
||||
};
|
||||
virtual void init() override
|
||||
{
|
||||
};
|
||||
virtual QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
virtual bool shouldUpdate() const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
virtual QSet< QString > traits() override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
virtual QString instanceConfigFolder() const override
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual shared_qobject_ptr< Task > createUpdateTask() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual std::shared_ptr<Task> createJarModdingTask() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual void setShouldUpdate(bool) override
|
||||
{
|
||||
};
|
||||
virtual std::shared_ptr< BaseVersionList > versionList() const override
|
||||
{
|
||||
return nullptr;
|
||||
};
|
||||
virtual QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
return QProcessEnvironment();
|
||||
}
|
||||
virtual QMap<QString, QString> getVariables() const override
|
||||
{
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QString getLogFileRoot() override
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
virtual QString typeName() const override
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
bool canExport() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QStringList verboseDescription(AuthSessionPtr session) override
|
||||
{
|
||||
QStringList out;
|
||||
out << "Null instance - placeholder.";
|
||||
return out;
|
||||
}
|
||||
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
|
||||
:BaseInstance(globalSettings, settings, rootDir)
|
||||
{
|
||||
setVersionBroken(true);
|
||||
}
|
||||
virtual ~NullInstance() {};
|
||||
void saveNow() override
|
||||
{
|
||||
}
|
||||
QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
QSet< QString > traits() const override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
QString instanceConfigFolder() const override
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
return QProcessEnvironment();
|
||||
}
|
||||
QMap<QString, QString> getVariables() const override
|
||||
{
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
IPathMatcher::Ptr getLogFileMatcher() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
QString getLogFileRoot() override
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
QString typeName() const override
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
bool canExport() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canEdit() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canLaunch() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QStringList verboseDescription(AuthSessionPtr session) override
|
||||
{
|
||||
QStringList out;
|
||||
out << "Null instance - placeholder.";
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
49
api/logic/ProblemProvider.h
Normal file
49
api/logic/ProblemProvider.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
enum class ProblemSeverity
|
||||
{
|
||||
None,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
struct PatchProblem
|
||||
{
|
||||
ProblemSeverity m_severity;
|
||||
QString m_description;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ProblemProvider
|
||||
{
|
||||
public:
|
||||
virtual ~ProblemProvider() {};
|
||||
virtual const QList<PatchProblem> getProblems() const = 0;
|
||||
virtual ProblemSeverity getProblemSeverity() const = 0;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
|
||||
{
|
||||
public:
|
||||
const QList<PatchProblem> getProblems() const override
|
||||
{
|
||||
return m_problems;
|
||||
}
|
||||
ProblemSeverity getProblemSeverity() const override
|
||||
{
|
||||
return m_problemSeverity;
|
||||
}
|
||||
virtual void addProblem(ProblemSeverity severity, const QString &description)
|
||||
{
|
||||
if(severity > m_problemSeverity)
|
||||
{
|
||||
m_problemSeverity = severity;
|
||||
}
|
||||
m_problems.append({severity, description});
|
||||
}
|
||||
|
||||
private:
|
||||
QList<PatchProblem> m_problems;
|
||||
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
|
||||
};
|
||||
@@ -1,17 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace details
|
||||
{
|
||||
struct DeleteQObjectLater
|
||||
{
|
||||
void operator()(QObject *obj) const
|
||||
{
|
||||
obj->deleteLater();
|
||||
}
|
||||
void operator()(QObject *obj) const
|
||||
{
|
||||
obj->deleteLater();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
@@ -28,56 +28,56 @@ template <typename T>
|
||||
class shared_qobject_ptr
|
||||
{
|
||||
public:
|
||||
shared_qobject_ptr(){}
|
||||
shared_qobject_ptr(T * wrap)
|
||||
{
|
||||
reset(wrap);
|
||||
}
|
||||
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
}
|
||||
template<typename Derived>
|
||||
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
|
||||
{
|
||||
m_ptr = other.unwrap();
|
||||
}
|
||||
shared_qobject_ptr(){}
|
||||
shared_qobject_ptr(T * wrap)
|
||||
{
|
||||
reset(wrap);
|
||||
}
|
||||
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
}
|
||||
template<typename Derived>
|
||||
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
|
||||
{
|
||||
m_ptr = other.unwrap();
|
||||
}
|
||||
|
||||
public:
|
||||
void reset(T * wrap)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
|
||||
}
|
||||
void reset(const shared_qobject_ptr<T> &other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_ptr.reset();
|
||||
}
|
||||
T * get() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T * operator->() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T & operator*() const
|
||||
{
|
||||
return *m_ptr.get();
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return m_ptr.get() != nullptr;
|
||||
}
|
||||
const std::shared_ptr <T> unwrap() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
void reset(T * wrap)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
|
||||
}
|
||||
void reset(const shared_qobject_ptr<T> &other)
|
||||
{
|
||||
m_ptr = other.m_ptr;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_ptr.reset();
|
||||
}
|
||||
T * get() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T * operator->() const
|
||||
{
|
||||
return m_ptr.get();
|
||||
}
|
||||
T & operator*() const
|
||||
{
|
||||
return *m_ptr.get();
|
||||
}
|
||||
operator bool() const
|
||||
{
|
||||
return m_ptr.get() != nullptr;
|
||||
}
|
||||
const std::shared_ptr <T> unwrap() const
|
||||
{
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr <T> m_ptr;
|
||||
std::shared_ptr <T> m_ptr;
|
||||
};
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
#pragma once
|
||||
#include <QWriteLocker>
|
||||
#include <QReadLocker>
|
||||
template <typename K, typename V>
|
||||
class RWStorage
|
||||
{
|
||||
public:
|
||||
void add(K key, V value)
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache[key] = value;
|
||||
stale_entries.remove(key);
|
||||
}
|
||||
V get(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
return cache[key];
|
||||
}
|
||||
else return V();
|
||||
}
|
||||
bool get(K key, V& value)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
value = cache[key];
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
bool has(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
return cache.contains(key);
|
||||
}
|
||||
bool stale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(!cache.contains(key))
|
||||
return true;
|
||||
return stale_entries.contains(key);
|
||||
}
|
||||
void setStale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
stale_entries.insert(key);
|
||||
}
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache.clear();
|
||||
}
|
||||
void add(K key, V value)
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache[key] = value;
|
||||
stale_entries.remove(key);
|
||||
}
|
||||
V get(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
return cache[key];
|
||||
}
|
||||
else return V();
|
||||
}
|
||||
bool get(K key, V& value)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
value = cache[key];
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
bool has(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
return cache.contains(key);
|
||||
}
|
||||
bool stale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
if(!cache.contains(key))
|
||||
return true;
|
||||
return stale_entries.contains(key);
|
||||
}
|
||||
void setStale(K key)
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
stale_entries.insert(key);
|
||||
}
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache.clear();
|
||||
stale_entries.clear();
|
||||
}
|
||||
private:
|
||||
QReadWriteLock lock;
|
||||
QMap<K, V> cache;
|
||||
QSet<K> stale_entries;
|
||||
};
|
||||
QReadWriteLock lock;
|
||||
QMap<K, V> cache;
|
||||
QSet<K> stale_entries;
|
||||
};
|
||||
|
||||
@@ -4,108 +4,108 @@
|
||||
#include <QDebug>
|
||||
|
||||
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent)
|
||||
: QObject(parent), m_watcher(new QFileSystemWatcher(this))
|
||||
: QObject(parent), m_watcher(new QFileSystemWatcher(this))
|
||||
{
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
|
||||
&RecursiveFileSystemWatcher::fileChange);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this,
|
||||
&RecursiveFileSystemWatcher::directoryChange);
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
|
||||
&RecursiveFileSystemWatcher::fileChange);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this,
|
||||
&RecursiveFileSystemWatcher::directoryChange);
|
||||
}
|
||||
|
||||
void RecursiveFileSystemWatcher::setRootDir(const QDir &root)
|
||||
{
|
||||
bool wasEnabled = m_isEnabled;
|
||||
disable();
|
||||
m_root = root;
|
||||
setFiles(scanRecursive(m_root));
|
||||
if (wasEnabled)
|
||||
{
|
||||
enable();
|
||||
}
|
||||
bool wasEnabled = m_isEnabled;
|
||||
disable();
|
||||
m_root = root;
|
||||
setFiles(scanRecursive(m_root));
|
||||
if (wasEnabled)
|
||||
{
|
||||
enable();
|
||||
}
|
||||
}
|
||||
void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles)
|
||||
{
|
||||
bool wasEnabled = m_isEnabled;
|
||||
disable();
|
||||
m_watchFiles = watchFiles;
|
||||
if (wasEnabled)
|
||||
{
|
||||
enable();
|
||||
}
|
||||
bool wasEnabled = m_isEnabled;
|
||||
disable();
|
||||
m_watchFiles = watchFiles;
|
||||
if (wasEnabled)
|
||||
{
|
||||
enable();
|
||||
}
|
||||
}
|
||||
|
||||
void RecursiveFileSystemWatcher::enable()
|
||||
{
|
||||
if (m_isEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(m_root != QDir::root());
|
||||
addFilesToWatcherRecursive(m_root);
|
||||
m_isEnabled = true;
|
||||
if (m_isEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Q_ASSERT(m_root != QDir::root());
|
||||
addFilesToWatcherRecursive(m_root);
|
||||
m_isEnabled = true;
|
||||
}
|
||||
void RecursiveFileSystemWatcher::disable()
|
||||
{
|
||||
if (!m_isEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_isEnabled = false;
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
if (!m_isEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_isEnabled = false;
|
||||
m_watcher->removePaths(m_watcher->files());
|
||||
m_watcher->removePaths(m_watcher->directories());
|
||||
}
|
||||
|
||||
void RecursiveFileSystemWatcher::setFiles(const QStringList &files)
|
||||
{
|
||||
if (files != m_files)
|
||||
{
|
||||
m_files = files;
|
||||
emit filesChanged();
|
||||
}
|
||||
if (files != m_files)
|
||||
{
|
||||
m_files = files;
|
||||
emit filesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir)
|
||||
{
|
||||
m_watcher->addPath(dir.absolutePath());
|
||||
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
{
|
||||
addFilesToWatcherRecursive(dir.absoluteFilePath(directory));
|
||||
}
|
||||
if (m_watchFiles)
|
||||
{
|
||||
for (const QFileInfo &info : dir.entryInfoList(QDir::Files))
|
||||
{
|
||||
m_watcher->addPath(info.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
m_watcher->addPath(dir.absolutePath());
|
||||
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
{
|
||||
addFilesToWatcherRecursive(dir.absoluteFilePath(directory));
|
||||
}
|
||||
if (m_watchFiles)
|
||||
{
|
||||
for (const QFileInfo &info : dir.entryInfoList(QDir::Files))
|
||||
{
|
||||
m_watcher->addPath(info.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory)
|
||||
{
|
||||
QStringList ret;
|
||||
if(!m_matcher)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden))
|
||||
{
|
||||
ret.append(scanRecursive(directory.absoluteFilePath(dir)));
|
||||
}
|
||||
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
|
||||
{
|
||||
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
|
||||
if (m_matcher->matches(relPath))
|
||||
{
|
||||
ret.append(relPath);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
QStringList ret;
|
||||
if(!m_matcher)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden))
|
||||
{
|
||||
ret.append(scanRecursive(directory.absoluteFilePath(dir)));
|
||||
}
|
||||
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
|
||||
{
|
||||
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
|
||||
if (m_matcher->matches(relPath))
|
||||
{
|
||||
ret.append(relPath);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void RecursiveFileSystemWatcher::fileChange(const QString &path)
|
||||
{
|
||||
emit fileChanged(path);
|
||||
emit fileChanged(path);
|
||||
}
|
||||
void RecursiveFileSystemWatcher::directoryChange(const QString &path)
|
||||
{
|
||||
setFiles(scanRecursive(m_root));
|
||||
setFiles(scanRecursive(m_root));
|
||||
}
|
||||
|
||||
@@ -8,56 +8,56 @@
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
RecursiveFileSystemWatcher(QObject *parent);
|
||||
RecursiveFileSystemWatcher(QObject *parent);
|
||||
|
||||
void setRootDir(const QDir &root);
|
||||
QDir rootDir() const
|
||||
{
|
||||
return m_root;
|
||||
}
|
||||
void setRootDir(const QDir &root);
|
||||
QDir rootDir() const
|
||||
{
|
||||
return m_root;
|
||||
}
|
||||
|
||||
// WARNING: setting this to true may be bad for performance
|
||||
void setWatchFiles(const bool watchFiles);
|
||||
bool watchFiles() const
|
||||
{
|
||||
return m_watchFiles;
|
||||
}
|
||||
// WARNING: setting this to true may be bad for performance
|
||||
void setWatchFiles(const bool watchFiles);
|
||||
bool watchFiles() const
|
||||
{
|
||||
return m_watchFiles;
|
||||
}
|
||||
|
||||
void setMatcher(IPathMatcher::Ptr matcher)
|
||||
{
|
||||
m_matcher = matcher;
|
||||
}
|
||||
void setMatcher(IPathMatcher::Ptr matcher)
|
||||
{
|
||||
m_matcher = matcher;
|
||||
}
|
||||
|
||||
QStringList files() const
|
||||
{
|
||||
return m_files;
|
||||
}
|
||||
QStringList files() const
|
||||
{
|
||||
return m_files;
|
||||
}
|
||||
|
||||
signals:
|
||||
void filesChanged();
|
||||
void fileChanged(const QString &path);
|
||||
void filesChanged();
|
||||
void fileChanged(const QString &path);
|
||||
|
||||
public slots:
|
||||
void enable();
|
||||
void disable();
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
private:
|
||||
QDir m_root;
|
||||
bool m_watchFiles = false;
|
||||
bool m_isEnabled = false;
|
||||
IPathMatcher::Ptr m_matcher;
|
||||
QDir m_root;
|
||||
bool m_watchFiles = false;
|
||||
bool m_isEnabled = false;
|
||||
IPathMatcher::Ptr m_matcher;
|
||||
|
||||
QFileSystemWatcher *m_watcher;
|
||||
QFileSystemWatcher *m_watcher;
|
||||
|
||||
QStringList m_files;
|
||||
void setFiles(const QStringList &files);
|
||||
QStringList m_files;
|
||||
void setFiles(const QStringList &files);
|
||||
|
||||
void addFilesToWatcherRecursive(const QDir &dir);
|
||||
QStringList scanRecursive(const QDir &dir);
|
||||
void addFilesToWatcherRecursive(const QDir &dir);
|
||||
QStringList scanRecursive(const QDir &dir);
|
||||
|
||||
private slots:
|
||||
void fileChange(const QString &path);
|
||||
void directoryChange(const QString &path);
|
||||
void fileChange(const QString &path);
|
||||
void directoryChange(const QString &path);
|
||||
};
|
||||
|
||||
@@ -7,292 +7,292 @@ template <char Tseparator>
|
||||
class SeparatorPrefixTree
|
||||
{
|
||||
public:
|
||||
SeparatorPrefixTree(QStringList paths)
|
||||
{
|
||||
insert(paths);
|
||||
}
|
||||
SeparatorPrefixTree(QStringList paths)
|
||||
{
|
||||
insert(paths);
|
||||
}
|
||||
|
||||
SeparatorPrefixTree(bool contained = false)
|
||||
{
|
||||
m_contained = contained;
|
||||
}
|
||||
SeparatorPrefixTree(bool contained = false)
|
||||
{
|
||||
m_contained = contained;
|
||||
}
|
||||
|
||||
void insert(QStringList paths)
|
||||
{
|
||||
for(auto &path: paths)
|
||||
{
|
||||
insert(path);
|
||||
}
|
||||
}
|
||||
void insert(QStringList paths)
|
||||
{
|
||||
for(auto &path: paths)
|
||||
{
|
||||
insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
/// insert an exact path into the tree
|
||||
SeparatorPrefixTree & insert(QString path)
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
children[path] = SeparatorPrefixTree(true);
|
||||
return children[path];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
if(!children.contains(prefix))
|
||||
{
|
||||
children[prefix] = SeparatorPrefixTree(false);
|
||||
}
|
||||
return children[prefix].insert(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
/// insert an exact path into the tree
|
||||
SeparatorPrefixTree & insert(QString path)
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
children[path] = SeparatorPrefixTree(true);
|
||||
return children[path];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
if(!children.contains(prefix))
|
||||
{
|
||||
children[prefix] = SeparatorPrefixTree(false);
|
||||
}
|
||||
return children[prefix].insert(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// is the path fully contained in the tree?
|
||||
bool contains(QString path) const
|
||||
{
|
||||
auto node = find(path);
|
||||
return node != nullptr;
|
||||
}
|
||||
/// is the path fully contained in the tree?
|
||||
bool contains(QString path) const
|
||||
{
|
||||
auto node = find(path);
|
||||
return node != nullptr;
|
||||
}
|
||||
|
||||
/// does the tree cover a path? That means the prefix of the path is contained in the tree
|
||||
bool covers(QString path) const
|
||||
{
|
||||
// if we found some valid node, it's good enough. the tree covers the path
|
||||
if(m_contained)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).covers(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).covers(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
/// does the tree cover a path? That means the prefix of the path is contained in the tree
|
||||
bool covers(QString path) const
|
||||
{
|
||||
// if we found some valid node, it's good enough. the tree covers the path
|
||||
if(m_contained)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).covers(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).covers(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// return the contained path that covers the path specified
|
||||
QString cover(QString path) const
|
||||
{
|
||||
// if we found some valid node, it's good enough. the tree covers the path
|
||||
if(m_contained)
|
||||
{
|
||||
return QString("");
|
||||
}
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
auto nested = (*found).cover(QString());
|
||||
if(nested.isNull())
|
||||
{
|
||||
return nested;
|
||||
}
|
||||
if(nested.isEmpty())
|
||||
return path;
|
||||
return path + Tseparator + nested;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
auto nested = (*found).cover(path.mid(sepIndex + 1));
|
||||
if(nested.isNull())
|
||||
{
|
||||
return nested;
|
||||
}
|
||||
if(nested.isEmpty())
|
||||
return prefix;
|
||||
return prefix + Tseparator + nested;
|
||||
}
|
||||
}
|
||||
/// return the contained path that covers the path specified
|
||||
QString cover(QString path) const
|
||||
{
|
||||
// if we found some valid node, it's good enough. the tree covers the path
|
||||
if(m_contained)
|
||||
{
|
||||
return QString("");
|
||||
}
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
auto nested = (*found).cover(QString());
|
||||
if(nested.isNull())
|
||||
{
|
||||
return nested;
|
||||
}
|
||||
if(nested.isEmpty())
|
||||
return path;
|
||||
return path + Tseparator + nested;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
auto nested = (*found).cover(path.mid(sepIndex + 1));
|
||||
if(nested.isNull())
|
||||
{
|
||||
return nested;
|
||||
}
|
||||
if(nested.isEmpty())
|
||||
return prefix;
|
||||
return prefix + Tseparator + nested;
|
||||
}
|
||||
}
|
||||
|
||||
/// Does the path-specified node exist in the tree? It does not have to be contained.
|
||||
bool exists(QString path) const
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).exists(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
/// Does the path-specified node exist in the tree? It does not have to be contained.
|
||||
bool exists(QString path) const
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (*found).exists(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// find a node in the tree by name
|
||||
const SeparatorPrefixTree * find(QString path) const
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &(*found);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return (*found).find(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
/// find a node in the tree by name
|
||||
const SeparatorPrefixTree * find(QString path) const
|
||||
{
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
auto found = children.find(path);
|
||||
if(found == children.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &(*found);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto prefix = path.left(sepIndex);
|
||||
auto found = children.find(prefix);
|
||||
if(found == children.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return (*found).find(path.mid(sepIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/// is this a leaf node?
|
||||
bool leaf() const
|
||||
{
|
||||
return children.isEmpty();
|
||||
}
|
||||
/// is this a leaf node?
|
||||
bool leaf() const
|
||||
{
|
||||
return children.isEmpty();
|
||||
}
|
||||
|
||||
/// is this node actually contained in the tree, or is it purely structural?
|
||||
bool contained() const
|
||||
{
|
||||
return m_contained;
|
||||
}
|
||||
/// is this node actually contained in the tree, or is it purely structural?
|
||||
bool contained() const
|
||||
{
|
||||
return m_contained;
|
||||
}
|
||||
|
||||
/// Remove a path from the tree
|
||||
bool remove(QString path)
|
||||
{
|
||||
return removeInternal(path) != Failed;
|
||||
}
|
||||
/// Remove a path from the tree
|
||||
bool remove(QString path)
|
||||
{
|
||||
return removeInternal(path) != Failed;
|
||||
}
|
||||
|
||||
/// Clear all children of this node tree node
|
||||
void clear()
|
||||
{
|
||||
children.clear();
|
||||
}
|
||||
/// Clear all children of this node tree node
|
||||
void clear()
|
||||
{
|
||||
children.clear();
|
||||
}
|
||||
|
||||
QStringList toStringList() const
|
||||
{
|
||||
QStringList collected;
|
||||
// collecting these is more expensive.
|
||||
auto iter = children.begin();
|
||||
while(iter != children.end())
|
||||
{
|
||||
QStringList list = iter.value().toStringList();
|
||||
for(int i = 0; i < list.size(); i++)
|
||||
{
|
||||
list[i] = iter.key() + Tseparator + list[i];
|
||||
}
|
||||
collected.append(list);
|
||||
if((*iter).m_contained)
|
||||
{
|
||||
collected.append(iter.key());
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
QStringList toStringList() const
|
||||
{
|
||||
QStringList collected;
|
||||
// collecting these is more expensive.
|
||||
auto iter = children.begin();
|
||||
while(iter != children.end())
|
||||
{
|
||||
QStringList list = iter.value().toStringList();
|
||||
for(int i = 0; i < list.size(); i++)
|
||||
{
|
||||
list[i] = iter.key() + Tseparator + list[i];
|
||||
}
|
||||
collected.append(list);
|
||||
if((*iter).m_contained)
|
||||
{
|
||||
collected.append(iter.key());
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
private:
|
||||
enum Removal
|
||||
{
|
||||
Failed,
|
||||
Succeeded,
|
||||
HasChildren
|
||||
};
|
||||
Removal removeInternal(QString path = QString())
|
||||
{
|
||||
if(path.isEmpty())
|
||||
{
|
||||
if(!m_contained)
|
||||
{
|
||||
// remove all children - we are removing a prefix
|
||||
clear();
|
||||
return Succeeded;
|
||||
}
|
||||
m_contained = false;
|
||||
if(children.size())
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
return Succeeded;
|
||||
}
|
||||
Removal remStatus = Failed;
|
||||
QString childToRemove;
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
childToRemove = path;
|
||||
auto found = children.find(childToRemove);
|
||||
if(found == children.end())
|
||||
{
|
||||
return Failed;
|
||||
}
|
||||
remStatus = (*found).removeInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
childToRemove = path.left(sepIndex);
|
||||
auto found = children.find(childToRemove);
|
||||
if(found == children.end())
|
||||
{
|
||||
return Failed;
|
||||
}
|
||||
remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
|
||||
}
|
||||
switch (remStatus)
|
||||
{
|
||||
case Failed:
|
||||
case HasChildren:
|
||||
{
|
||||
return remStatus;
|
||||
}
|
||||
case Succeeded:
|
||||
{
|
||||
children.remove(childToRemove);
|
||||
if(m_contained)
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
if(children.size())
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
return Succeeded;
|
||||
}
|
||||
}
|
||||
return Failed;
|
||||
}
|
||||
enum Removal
|
||||
{
|
||||
Failed,
|
||||
Succeeded,
|
||||
HasChildren
|
||||
};
|
||||
Removal removeInternal(QString path = QString())
|
||||
{
|
||||
if(path.isEmpty())
|
||||
{
|
||||
if(!m_contained)
|
||||
{
|
||||
// remove all children - we are removing a prefix
|
||||
clear();
|
||||
return Succeeded;
|
||||
}
|
||||
m_contained = false;
|
||||
if(children.size())
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
return Succeeded;
|
||||
}
|
||||
Removal remStatus = Failed;
|
||||
QString childToRemove;
|
||||
auto sepIndex = path.indexOf(Tseparator);
|
||||
if(sepIndex == -1)
|
||||
{
|
||||
childToRemove = path;
|
||||
auto found = children.find(childToRemove);
|
||||
if(found == children.end())
|
||||
{
|
||||
return Failed;
|
||||
}
|
||||
remStatus = (*found).removeInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
childToRemove = path.left(sepIndex);
|
||||
auto found = children.find(childToRemove);
|
||||
if(found == children.end())
|
||||
{
|
||||
return Failed;
|
||||
}
|
||||
remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
|
||||
}
|
||||
switch (remStatus)
|
||||
{
|
||||
case Failed:
|
||||
case HasChildren:
|
||||
{
|
||||
return remStatus;
|
||||
}
|
||||
case Succeeded:
|
||||
{
|
||||
children.remove(childToRemove);
|
||||
if(m_contained)
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
if(children.size())
|
||||
{
|
||||
return HasChildren;
|
||||
}
|
||||
return Succeeded;
|
||||
}
|
||||
}
|
||||
return Failed;
|
||||
}
|
||||
|
||||
private:
|
||||
QMap<QString,SeparatorPrefixTree<Tseparator>> children;
|
||||
bool m_contained = false;
|
||||
QMap<QString,SeparatorPrefixTree<Tseparator>> children;
|
||||
bool m_contained = false;
|
||||
};
|
||||
|
||||
@@ -12,27 +12,27 @@ class Usable;
|
||||
*/
|
||||
class Usable
|
||||
{
|
||||
friend class UseLock;
|
||||
friend class UseLock;
|
||||
public:
|
||||
std::size_t useCount()
|
||||
{
|
||||
return m_useCount;
|
||||
}
|
||||
bool isInUse()
|
||||
{
|
||||
return m_useCount > 0;
|
||||
}
|
||||
std::size_t useCount()
|
||||
{
|
||||
return m_useCount;
|
||||
}
|
||||
bool isInUse()
|
||||
{
|
||||
return m_useCount > 0;
|
||||
}
|
||||
protected:
|
||||
virtual void decrementUses()
|
||||
{
|
||||
m_useCount--;
|
||||
}
|
||||
virtual void incrementUses()
|
||||
{
|
||||
m_useCount++;
|
||||
}
|
||||
virtual void decrementUses()
|
||||
{
|
||||
m_useCount--;
|
||||
}
|
||||
virtual void incrementUses()
|
||||
{
|
||||
m_useCount++;
|
||||
}
|
||||
private:
|
||||
std::size_t m_useCount = 0;
|
||||
std::size_t m_useCount = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -43,16 +43,16 @@ private:
|
||||
class UseLock
|
||||
{
|
||||
public:
|
||||
UseLock(std::shared_ptr<Usable> usable)
|
||||
: m_usable(usable)
|
||||
{
|
||||
// this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate.
|
||||
m_usable->incrementUses();
|
||||
}
|
||||
~UseLock()
|
||||
{
|
||||
m_usable->decrementUses();
|
||||
}
|
||||
UseLock(std::shared_ptr<Usable> usable)
|
||||
: m_usable(usable)
|
||||
{
|
||||
// this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate.
|
||||
m_usable->incrementUses();
|
||||
}
|
||||
~UseLock()
|
||||
{
|
||||
m_usable->decrementUses();
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<Usable> m_usable;
|
||||
std::shared_ptr<Usable> m_usable;
|
||||
};
|
||||
|
||||
@@ -7,134 +7,79 @@
|
||||
|
||||
Version::Version(const QString &str) : m_string(str)
|
||||
{
|
||||
parse();
|
||||
parse();
|
||||
}
|
||||
|
||||
bool Version::operator<(const Version &other) const
|
||||
{
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 < sec2;
|
||||
}
|
||||
}
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 < sec2;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
bool Version::operator<=(const Version &other) const
|
||||
{
|
||||
return *this < other || *this == other;
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
bool Version::operator>(const Version &other) const
|
||||
{
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 > sec2;
|
||||
}
|
||||
}
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 > sec2;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
bool Version::operator>=(const Version &other) const
|
||||
{
|
||||
return *this > other || *this == other;
|
||||
return *this > other || *this == other;
|
||||
}
|
||||
bool Version::operator==(const Version &other) const
|
||||
{
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const int size = qMax(m_sections.size(), other.m_sections.size());
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
bool Version::operator!=(const Version &other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
void Version::parse()
|
||||
{
|
||||
m_sections.clear();
|
||||
m_sections.clear();
|
||||
|
||||
QStringList parts = m_string.split('.');
|
||||
// FIXME: this is bad. versions can contain a lot more separators...
|
||||
QStringList parts = m_string.split('.');
|
||||
|
||||
for (const auto part : parts)
|
||||
{
|
||||
m_sections.append(Section(part));
|
||||
}
|
||||
}
|
||||
|
||||
bool versionIsInInterval(const QString &version, const QString &interval)
|
||||
{
|
||||
return versionIsInInterval(Version(version), interval);
|
||||
}
|
||||
bool versionIsInInterval(const Version &version, const QString &interval)
|
||||
{
|
||||
if (interval.isEmpty() || version.toString() == interval)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Interval notation is used
|
||||
QRegularExpression exp(
|
||||
"(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
|
||||
QRegularExpressionMatch match = exp.match(interval);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
const QChar start = match.captured("start").at(0);
|
||||
const QChar end = match.captured("end").at(0);
|
||||
const QString bottom = match.captured("bottom");
|
||||
const QString top = match.captured("top");
|
||||
|
||||
// check if in range (bottom)
|
||||
if (!bottom.isEmpty())
|
||||
{
|
||||
const auto bottomVersion = Version(bottom);
|
||||
if ((start == '[') && !(version >= bottomVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((start == '(') && !(version > bottomVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if in range (top)
|
||||
if (!top.isEmpty())
|
||||
{
|
||||
const auto topVersion = Version(top);
|
||||
if ((end == ']') && !(version <= topVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((end == ')') && !(version < topVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
for (const auto part : parts)
|
||||
{
|
||||
m_sections.append(Section(part));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,104 +7,101 @@
|
||||
|
||||
class QUrl;
|
||||
|
||||
struct MULTIMC_LOGIC_EXPORT Version
|
||||
class MULTIMC_LOGIC_EXPORT Version
|
||||
{
|
||||
Version(const QString &str);
|
||||
Version() {}
|
||||
public:
|
||||
Version(const QString &str);
|
||||
Version() {}
|
||||
|
||||
bool operator<(const Version &other) const;
|
||||
bool operator<=(const Version &other) const;
|
||||
bool operator>(const Version &other) const;
|
||||
bool operator>=(const Version &other) const;
|
||||
bool operator==(const Version &other) const;
|
||||
bool operator!=(const Version &other) const;
|
||||
bool operator<(const Version &other) const;
|
||||
bool operator<=(const Version &other) const;
|
||||
bool operator>(const Version &other) const;
|
||||
bool operator>=(const Version &other) const;
|
||||
bool operator==(const Version &other) const;
|
||||
bool operator!=(const Version &other) const;
|
||||
|
||||
QString toString() const
|
||||
{
|
||||
return m_string;
|
||||
}
|
||||
QString toString() const
|
||||
{
|
||||
return m_string;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_string;
|
||||
struct Section
|
||||
{
|
||||
explicit Section(const QString &fullString)
|
||||
{
|
||||
m_fullString = fullString;
|
||||
int cutoff = m_fullString.size();
|
||||
for(int i = 0; i < m_fullString.size(); i++)
|
||||
{
|
||||
if(!m_fullString[i].isDigit())
|
||||
{
|
||||
cutoff = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto numPart = m_fullString.leftRef(cutoff);
|
||||
if(numPart.size())
|
||||
{
|
||||
numValid = true;
|
||||
m_numPart = numPart.toInt();
|
||||
}
|
||||
auto stringPart = m_fullString.midRef(cutoff);
|
||||
if(stringPart.size())
|
||||
{
|
||||
m_stringPart = stringPart.toString();
|
||||
}
|
||||
}
|
||||
explicit Section() {}
|
||||
bool numValid = false;
|
||||
int m_numPart = 0;
|
||||
QString m_stringPart;
|
||||
QString m_fullString;
|
||||
QString m_string;
|
||||
struct Section
|
||||
{
|
||||
explicit Section(const QString &fullString)
|
||||
{
|
||||
m_fullString = fullString;
|
||||
int cutoff = m_fullString.size();
|
||||
for(int i = 0; i < m_fullString.size(); i++)
|
||||
{
|
||||
if(!m_fullString[i].isDigit())
|
||||
{
|
||||
cutoff = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto numPart = m_fullString.leftRef(cutoff);
|
||||
if(numPart.size())
|
||||
{
|
||||
numValid = true;
|
||||
m_numPart = numPart.toInt();
|
||||
}
|
||||
auto stringPart = m_fullString.midRef(cutoff);
|
||||
if(stringPart.size())
|
||||
{
|
||||
m_stringPart = stringPart.toString();
|
||||
}
|
||||
}
|
||||
explicit Section() {}
|
||||
bool numValid = false;
|
||||
int m_numPart = 0;
|
||||
QString m_stringPart;
|
||||
QString m_fullString;
|
||||
|
||||
inline bool operator!=(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString != other.m_fullString;
|
||||
}
|
||||
}
|
||||
inline bool operator<(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
if(m_numPart < other.m_numPart)
|
||||
return true;
|
||||
if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString < other.m_fullString;
|
||||
}
|
||||
}
|
||||
inline bool operator>(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
if(m_numPart > other.m_numPart)
|
||||
return true;
|
||||
if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString > other.m_fullString;
|
||||
}
|
||||
}
|
||||
};
|
||||
QList<Section> m_sections;
|
||||
inline bool operator!=(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString != other.m_fullString;
|
||||
}
|
||||
}
|
||||
inline bool operator<(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
if(m_numPart < other.m_numPart)
|
||||
return true;
|
||||
if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString < other.m_fullString;
|
||||
}
|
||||
}
|
||||
inline bool operator>(const Section &other) const
|
||||
{
|
||||
if(numValid && other.numValid)
|
||||
{
|
||||
if(m_numPart > other.m_numPart)
|
||||
return true;
|
||||
if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_fullString > other.m_fullString;
|
||||
}
|
||||
}
|
||||
};
|
||||
QList<Section> m_sections;
|
||||
|
||||
void parse();
|
||||
void parse();
|
||||
};
|
||||
|
||||
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
|
||||
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,102 +20,64 @@
|
||||
|
||||
class ModUtilsTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
void setupVersions()
|
||||
{
|
||||
QTest::addColumn<QString>("first");
|
||||
QTest::addColumn<QString>("second");
|
||||
QTest::addColumn<bool>("lessThan");
|
||||
QTest::addColumn<bool>("equal");
|
||||
Q_OBJECT
|
||||
void setupVersions()
|
||||
{
|
||||
QTest::addColumn<QString>("first");
|
||||
QTest::addColumn<QString>("second");
|
||||
QTest::addColumn<bool>("lessThan");
|
||||
QTest::addColumn<bool>("equal");
|
||||
|
||||
QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true;
|
||||
QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true;
|
||||
QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true;
|
||||
QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true;
|
||||
QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true;
|
||||
QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true;
|
||||
QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true;
|
||||
QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true;
|
||||
|
||||
QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false;
|
||||
QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false;
|
||||
QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false;
|
||||
QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false;
|
||||
QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false;
|
||||
QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false;
|
||||
QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false;
|
||||
QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false;
|
||||
QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false;
|
||||
QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false;
|
||||
QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false;
|
||||
QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false;
|
||||
QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false;
|
||||
QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false;
|
||||
|
||||
QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false;
|
||||
}
|
||||
QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false;
|
||||
QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
void initTestCase()
|
||||
{
|
||||
|
||||
}
|
||||
void cleanupTestCase()
|
||||
{
|
||||
}
|
||||
void cleanupTestCase()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void test_versionIsInInterval_data()
|
||||
{
|
||||
QTest::addColumn<QString>("version");
|
||||
QTest::addColumn<QString>("interval");
|
||||
QTest::addColumn<bool>("result");
|
||||
void test_versionCompare_data()
|
||||
{
|
||||
setupVersions();
|
||||
}
|
||||
void test_versionCompare()
|
||||
{
|
||||
QFETCH(QString, first);
|
||||
QFETCH(QString, second);
|
||||
QFETCH(bool, lessThan);
|
||||
QFETCH(bool, equal);
|
||||
|
||||
QTest::newRow("empty, true") << "1.2.3" << "" << true;
|
||||
QTest::newRow("one version, true") << "1.2.3" << "1.2.3" << true;
|
||||
QTest::newRow("one version, false") << "1.2.3" << "1.2.2" << false;
|
||||
const auto v1 = Version(first);
|
||||
const auto v2 = Version(second);
|
||||
|
||||
QTest::newRow("one version inclusive <-> infinity, true") << "1.2.3" << "[1.2.3,)" << true;
|
||||
QTest::newRow("one version exclusive <-> infinity, true") << "1.2.3" << "(1.2.2,)" << true;
|
||||
QTest::newRow("one version inclusive <-> infitity, false") << "1.2.3" << "[1.2.4,)" << false;
|
||||
QTest::newRow("one version exclusive <-> infinity, false") << "1.2.3" << "(1.2.3,)" << false;
|
||||
|
||||
QTest::newRow("infinity <-> one version inclusive, true") << "1.2.3" << "(,1.2.3]" << true;
|
||||
QTest::newRow("infinity <-> one version exclusive, true") << "1.2.3" << "(,1.2.4)" << true;
|
||||
QTest::newRow("infinity <-> one version inclusive, false") << "1.2.3" << "(,1.2.2]" << false;
|
||||
QTest::newRow("infinity <-> one version exclusive, false") << "1.2.3" << "(,1.2.3)" << false;
|
||||
|
||||
QTest::newRow("inclusive <-> inclusive, true") << "1.2.3" << "[1.2.2,1.2.3]" << true;
|
||||
QTest::newRow("inclusive <-> exclusive, true") << "1.2.3" << "[1.2.3,1.2.4)" << true;
|
||||
QTest::newRow("exclusive <-> inclusive, true") << "1.2.3" << "(1.2.2,1.2.3]" << true;
|
||||
QTest::newRow("exclusive <-> exclusive, true") << "1.2.3" << "(1.2.2,1.2.4)" << true;
|
||||
QTest::newRow("inclusive <-> inclusive, false") << "1.2.3" << "[1.0.0,1.2.2]" << false;
|
||||
QTest::newRow("inclusive <-> exclusive, false") << "1.2.3" << "[1.0.0,1.2.3)" << false;
|
||||
QTest::newRow("exclusive <-> inclusive, false") << "1.2.3" << "(1.2.3,2.0.0]" << false;
|
||||
QTest::newRow("exclusive <-> exclusive, false") << "1.2.3" << "(1.0.0,1.2.3)" << false;
|
||||
}
|
||||
void test_versionIsInInterval()
|
||||
{
|
||||
QFETCH(QString, version);
|
||||
QFETCH(QString, interval);
|
||||
QFETCH(bool, result);
|
||||
|
||||
QCOMPARE(versionIsInInterval(version, interval), result);
|
||||
}
|
||||
|
||||
void test_versionCompare_data()
|
||||
{
|
||||
setupVersions();
|
||||
}
|
||||
void test_versionCompare()
|
||||
{
|
||||
QFETCH(QString, first);
|
||||
QFETCH(QString, second);
|
||||
QFETCH(bool, lessThan);
|
||||
QFETCH(bool, equal);
|
||||
|
||||
const auto v1 = Version(first);
|
||||
const auto v2 = Version(second);
|
||||
|
||||
QCOMPARE(v1 < v2, lessThan);
|
||||
QCOMPARE(v1 > v2, !lessThan && !equal);
|
||||
QCOMPARE(v1 == v2, equal);
|
||||
}
|
||||
QCOMPARE(v1 < v2, lessThan);
|
||||
QCOMPARE(v1 > v2, !lessThan && !equal);
|
||||
QCOMPARE(v1 == v2, equal);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(ModUtilsTest)
|
||||
|
||||
20
api/logic/WatchLock.h
Normal file
20
api/logic/WatchLock.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
struct WatchLock
|
||||
{
|
||||
WatchLock(QFileSystemWatcher * watcher, const QString& directory)
|
||||
: m_watcher(watcher), m_directory(directory)
|
||||
{
|
||||
m_watcher->removePath(m_directory);
|
||||
}
|
||||
~WatchLock()
|
||||
{
|
||||
m_watcher->addPath(m_directory);
|
||||
}
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QString m_directory;
|
||||
};
|
||||
@@ -6,20 +6,21 @@
|
||||
|
||||
enum IconType : unsigned
|
||||
{
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT IIconList
|
||||
{
|
||||
public:
|
||||
virtual ~IIconList();
|
||||
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
|
||||
virtual bool deleteIcon(const QString &key) = 0;
|
||||
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
|
||||
virtual bool iconFileExists(const QString &key) const = 0;
|
||||
virtual void installIcons(const QStringList &iconFiles) = 0;
|
||||
virtual ~IIconList();
|
||||
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
|
||||
virtual bool deleteIcon(const QString &key) = 0;
|
||||
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
|
||||
virtual bool iconFileExists(const QString &key) const = 0;
|
||||
virtual void installIcons(const QStringList &iconFiles) = 0;
|
||||
virtual void installIcon(const QString &file, const QString &name) = 0;
|
||||
};
|
||||
|
||||
62
api/logic/icons/IconUtils.cpp
Normal file
62
api/logic/icons/IconUtils.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "IconUtils.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QDirIterator>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
std::array<const char *, 6> validIconExtensions = {{
|
||||
"svg",
|
||||
"png",
|
||||
"ico",
|
||||
"gif",
|
||||
"jpg",
|
||||
"jpeg"
|
||||
}};
|
||||
}
|
||||
|
||||
namespace IconUtils{
|
||||
|
||||
QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
||||
int best_found = validIconExtensions.size();
|
||||
QString best_filename;
|
||||
|
||||
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
auto fileInfo = it.fileInfo();
|
||||
|
||||
if(fileInfo.completeBaseName() != iconKey)
|
||||
continue;
|
||||
|
||||
auto extension = fileInfo.suffix();
|
||||
|
||||
for(int i = 0; i < best_found; i++) {
|
||||
if(extension == validIconExtensions[i]) {
|
||||
best_found = i;
|
||||
qDebug() << i << " : " << fileInfo.fileName();
|
||||
best_filename = fileInfo.fileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
return FS::PathCombine(folder, best_filename);
|
||||
}
|
||||
|
||||
QString getIconFilter() {
|
||||
QString out;
|
||||
QTextStream stream(&out);
|
||||
stream << '(';
|
||||
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
|
||||
if(i > 0) {
|
||||
stream << " ";
|
||||
}
|
||||
stream << "*." << validIconExtensions[i];
|
||||
}
|
||||
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
|
||||
stream << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
14
api/logic/icons/IconUtils.h
Normal file
14
api/logic/icons/IconUtils.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace IconUtils {
|
||||
|
||||
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
|
||||
MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey);
|
||||
|
||||
// Get icon file type filter for file browser dialogs
|
||||
MULTIMC_LOGIC_EXPORT QString getIconFilter();
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "JavaChecker.h"
|
||||
#include "JavaUtils.h"
|
||||
#include <FileSystem.h>
|
||||
#include <Commandline.h>
|
||||
#include <QFile>
|
||||
@@ -7,154 +8,157 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "Env.h"
|
||||
|
||||
JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void JavaChecker::performCheck()
|
||||
{
|
||||
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
|
||||
QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar");
|
||||
|
||||
QStringList args;
|
||||
QStringList args;
|
||||
|
||||
process.reset(new QProcess());
|
||||
if(m_args.size())
|
||||
{
|
||||
auto extraArgs = Commandline::splitArgs(m_args);
|
||||
args.append(extraArgs);
|
||||
}
|
||||
if(m_minMem != 0)
|
||||
{
|
||||
args << QString("-Xms%1m").arg(m_minMem);
|
||||
}
|
||||
if(m_maxMem != 0)
|
||||
{
|
||||
args << QString("-Xmx%1m").arg(m_maxMem);
|
||||
}
|
||||
if(m_permGen != 64)
|
||||
{
|
||||
args << QString("-XX:PermSize=%1m").arg(m_permGen);
|
||||
}
|
||||
process.reset(new QProcess());
|
||||
if(m_args.size())
|
||||
{
|
||||
auto extraArgs = Commandline::splitArgs(m_args);
|
||||
args.append(extraArgs);
|
||||
}
|
||||
if(m_minMem != 0)
|
||||
{
|
||||
args << QString("-Xms%1m").arg(m_minMem);
|
||||
}
|
||||
if(m_maxMem != 0)
|
||||
{
|
||||
args << QString("-Xmx%1m").arg(m_maxMem);
|
||||
}
|
||||
if(m_permGen != 64)
|
||||
{
|
||||
args << QString("-XX:PermSize=%1m").arg(m_permGen);
|
||||
}
|
||||
|
||||
args.append({"-jar", checkerJar});
|
||||
process->setArguments(args);
|
||||
process->setProgram(m_path);
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
qDebug() << "Running java checker: " + m_path + args.join(" ");;
|
||||
args.append({"-jar", checkerJar});
|
||||
process->setArguments(args);
|
||||
process->setProgram(m_path);
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
process->setProcessEnvironment(CleanEnviroment());
|
||||
qDebug() << "Running java checker: " + m_path + args.join(" ");;
|
||||
|
||||
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
|
||||
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
||||
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
|
||||
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
|
||||
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
|
||||
killTimer.setSingleShot(true);
|
||||
killTimer.start(15000);
|
||||
process->start();
|
||||
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
|
||||
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
||||
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
|
||||
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
|
||||
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
|
||||
killTimer.setSingleShot(true);
|
||||
killTimer.start(15000);
|
||||
process->start();
|
||||
}
|
||||
|
||||
void JavaChecker::stdoutReady()
|
||||
{
|
||||
QByteArray data = process->readAllStandardOutput();
|
||||
QString added = QString::fromLocal8Bit(data);
|
||||
added.remove('\r');
|
||||
m_stdout += added;
|
||||
QByteArray data = process->readAllStandardOutput();
|
||||
QString added = QString::fromLocal8Bit(data);
|
||||
added.remove('\r');
|
||||
m_stdout += added;
|
||||
}
|
||||
|
||||
void JavaChecker::stderrReady()
|
||||
{
|
||||
QByteArray data = process->readAllStandardError();
|
||||
QString added = QString::fromLocal8Bit(data);
|
||||
added.remove('\r');
|
||||
m_stderr += added;
|
||||
QByteArray data = process->readAllStandardError();
|
||||
QString added = QString::fromLocal8Bit(data);
|
||||
added.remove('\r');
|
||||
m_stderr += added;
|
||||
}
|
||||
|
||||
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||
{
|
||||
killTimer.stop();
|
||||
QProcessPtr _process;
|
||||
_process.swap(process);
|
||||
killTimer.stop();
|
||||
QProcessPtr _process = process;
|
||||
process.reset();
|
||||
|
||||
JavaCheckResult result;
|
||||
{
|
||||
result.path = m_path;
|
||||
result.id = m_id;
|
||||
}
|
||||
result.errorLog = m_stderr;
|
||||
result.outLog = m_stdout;
|
||||
qDebug() << "STDOUT" << m_stdout;
|
||||
qWarning() << "STDERR" << m_stderr;
|
||||
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
|
||||
JavaCheckResult result;
|
||||
{
|
||||
result.path = m_path;
|
||||
result.id = m_id;
|
||||
}
|
||||
result.errorLog = m_stderr;
|
||||
result.outLog = m_stdout;
|
||||
qDebug() << "STDOUT" << m_stdout;
|
||||
qWarning() << "STDERR" << m_stderr;
|
||||
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
|
||||
|
||||
if (status == QProcess::CrashExit || exitcode == 1)
|
||||
{
|
||||
result.validity = JavaCheckResult::Validity::Errored;
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
if (status == QProcess::CrashExit || exitcode == 1)
|
||||
{
|
||||
result.validity = JavaCheckResult::Validity::Errored;
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
bool success = true;
|
||||
|
||||
QMap<QString, QString> results;
|
||||
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
|
||||
for(QString line : lines)
|
||||
{
|
||||
line = line.trimmed();
|
||||
QMap<QString, QString> results;
|
||||
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
|
||||
for(QString line : lines)
|
||||
{
|
||||
line = line.trimmed();
|
||||
|
||||
auto parts = line.split('=', QString::SkipEmptyParts);
|
||||
if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
results.insert(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
auto parts = line.split('=', QString::SkipEmptyParts);
|
||||
if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty())
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
results.insert(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
|
||||
{
|
||||
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
|
||||
{
|
||||
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
|
||||
auto os_arch = results["os.arch"];
|
||||
auto java_version = results["java.version"];
|
||||
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
|
||||
auto os_arch = results["os.arch"];
|
||||
auto java_version = results["java.version"];
|
||||
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
|
||||
|
||||
|
||||
result.validity = JavaCheckResult::Validity::Valid;
|
||||
result.is_64bit = is_64;
|
||||
result.mojangPlatform = is_64 ? "64" : "32";
|
||||
result.realPlatform = os_arch;
|
||||
result.javaVersion = java_version;
|
||||
qDebug() << "Java checker succeeded.";
|
||||
emit checkFinished(result);
|
||||
result.validity = JavaCheckResult::Validity::Valid;
|
||||
result.is_64bit = is_64;
|
||||
result.mojangPlatform = is_64 ? "64" : "32";
|
||||
result.realPlatform = os_arch;
|
||||
result.javaVersion = java_version;
|
||||
qDebug() << "Java checker succeeded.";
|
||||
emit checkFinished(result);
|
||||
}
|
||||
|
||||
void JavaChecker::error(QProcess::ProcessError err)
|
||||
{
|
||||
if(err == QProcess::FailedToStart)
|
||||
{
|
||||
killTimer.stop();
|
||||
qDebug() << "Java checker has failed to start.";
|
||||
JavaCheckResult result;
|
||||
{
|
||||
result.path = m_path;
|
||||
result.id = m_id;
|
||||
}
|
||||
if(err == QProcess::FailedToStart)
|
||||
{
|
||||
killTimer.stop();
|
||||
qDebug() << "Java checker has failed to start.";
|
||||
JavaCheckResult result;
|
||||
{
|
||||
result.path = m_path;
|
||||
result.id = m_id;
|
||||
}
|
||||
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void JavaChecker::timeout()
|
||||
{
|
||||
// NO MERCY. NO ABUSE.
|
||||
if(process)
|
||||
{
|
||||
qDebug() << "Java checker has been killed by timeout.";
|
||||
process->kill();
|
||||
}
|
||||
// NO MERCY. NO ABUSE.
|
||||
if(process)
|
||||
{
|
||||
qDebug() << "Java checker has been killed by timeout.";
|
||||
process->kill();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "JavaVersion.h"
|
||||
@@ -11,50 +13,50 @@ class JavaChecker;
|
||||
|
||||
struct MULTIMC_LOGIC_EXPORT JavaCheckResult
|
||||
{
|
||||
QString path;
|
||||
QString mojangPlatform;
|
||||
QString realPlatform;
|
||||
JavaVersion javaVersion;
|
||||
QString outLog;
|
||||
QString errorLog;
|
||||
bool is_64bit = false;
|
||||
int id;
|
||||
enum class Validity
|
||||
{
|
||||
Errored,
|
||||
ReturnedInvalidData,
|
||||
Valid
|
||||
} validity = Validity::Errored;
|
||||
QString path;
|
||||
QString mojangPlatform;
|
||||
QString realPlatform;
|
||||
JavaVersion javaVersion;
|
||||
QString outLog;
|
||||
QString errorLog;
|
||||
bool is_64bit = false;
|
||||
int id;
|
||||
enum class Validity
|
||||
{
|
||||
Errored,
|
||||
ReturnedInvalidData,
|
||||
Valid
|
||||
} validity = Validity::Errored;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<QProcess> QProcessPtr;
|
||||
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
|
||||
typedef shared_qobject_ptr<QProcess> QProcessPtr;
|
||||
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
|
||||
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JavaChecker(QObject *parent = 0);
|
||||
void performCheck();
|
||||
explicit JavaChecker(QObject *parent = 0);
|
||||
void performCheck();
|
||||
|
||||
QString m_path;
|
||||
QString m_args;
|
||||
int m_id = 0;
|
||||
int m_minMem = 0;
|
||||
int m_maxMem = 0;
|
||||
int m_permGen = 64;
|
||||
QString m_path;
|
||||
QString m_args;
|
||||
int m_id = 0;
|
||||
int m_minMem = 0;
|
||||
int m_maxMem = 0;
|
||||
int m_permGen = 64;
|
||||
|
||||
signals:
|
||||
void checkFinished(JavaCheckResult result);
|
||||
void checkFinished(JavaCheckResult result);
|
||||
private:
|
||||
QProcessPtr process;
|
||||
QTimer killTimer;
|
||||
QString m_stdout;
|
||||
QString m_stderr;
|
||||
QProcessPtr process;
|
||||
QTimer killTimer;
|
||||
QString m_stdout;
|
||||
QString m_stderr;
|
||||
public
|
||||
slots:
|
||||
void timeout();
|
||||
void finished(int exitcode, QProcess::ExitStatus);
|
||||
void error(QProcess::ProcessError);
|
||||
void stdoutReady();
|
||||
void stderrReady();
|
||||
void timeout();
|
||||
void finished(int exitcode, QProcess::ExitStatus);
|
||||
void error(QProcess::ProcessError);
|
||||
void stdoutReady();
|
||||
void stderrReady();
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,27 +19,26 @@
|
||||
|
||||
void JavaCheckerJob::partFinished(JavaCheckResult result)
|
||||
{
|
||||
num_finished++;
|
||||
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
|
||||
<< javacheckers.size();
|
||||
emit progress(num_finished, javacheckers.size());
|
||||
num_finished++;
|
||||
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
|
||||
<< javacheckers.size();
|
||||
setProgress(num_finished, javacheckers.size());
|
||||
|
||||
javaresults.replace(result.id, result);
|
||||
javaresults.replace(result.id, result);
|
||||
|
||||
if (num_finished == javacheckers.size())
|
||||
{
|
||||
emit finished(javaresults);
|
||||
}
|
||||
if (num_finished == javacheckers.size())
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
void JavaCheckerJob::executeTask()
|
||||
{
|
||||
qDebug() << m_job_name.toLocal8Bit() << " started.";
|
||||
m_running = true;
|
||||
for (auto iter : javacheckers)
|
||||
{
|
||||
javaresults.append(JavaCheckResult());
|
||||
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
iter->performCheck();
|
||||
}
|
||||
qDebug() << m_job_name.toLocal8Bit() << " started.";
|
||||
for (auto iter : javacheckers)
|
||||
{
|
||||
javaresults.append(JavaCheckResult());
|
||||
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
iter->performCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,65 +20,42 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class JavaCheckerJob;
|
||||
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
|
||||
// FIXME: this just seems horribly redundant
|
||||
class JavaCheckerJob : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
||||
virtual ~JavaCheckerJob() {};
|
||||
|
||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||
{
|
||||
javacheckers.append(base);
|
||||
total_progress++;
|
||||
// if this is already running, the action needs to be started right away!
|
||||
if (isRunning())
|
||||
{
|
||||
setProgress(current_progress, total_progress);
|
||||
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
|
||||
base->performCheck();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
JavaCheckerPtr operator[](int index)
|
||||
{
|
||||
return javacheckers[index];
|
||||
}
|
||||
;
|
||||
JavaCheckerPtr first()
|
||||
{
|
||||
if (javacheckers.size())
|
||||
return javacheckers[0];
|
||||
return JavaCheckerPtr();
|
||||
}
|
||||
int size() const
|
||||
{
|
||||
return javacheckers.size();
|
||||
}
|
||||
virtual bool isRunning() const override
|
||||
{
|
||||
return m_running;
|
||||
}
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void finished(QList<JavaCheckResult>);
|
||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||
{
|
||||
javacheckers.append(base);
|
||||
// if this is already running, the action needs to be started right away!
|
||||
if (isRunning())
|
||||
{
|
||||
setProgress(num_finished, javacheckers.size());
|
||||
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
|
||||
base->performCheck();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QList<JavaCheckResult> getResults()
|
||||
{
|
||||
return javaresults;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void partFinished(JavaCheckResult result);
|
||||
void partFinished(JavaCheckResult result);
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
QString m_job_name;
|
||||
QList<JavaCheckerPtr> javacheckers;
|
||||
QList<JavaCheckResult> javaresults;
|
||||
qint64 current_progress = 0;
|
||||
qint64 total_progress = 0;
|
||||
int num_finished = 0;
|
||||
bool m_running = false;
|
||||
QString m_job_name;
|
||||
QList<JavaCheckerPtr> javacheckers;
|
||||
QList<JavaCheckResult> javaresults;
|
||||
int num_finished = 0;
|
||||
};
|
||||
|
||||
@@ -3,26 +3,26 @@
|
||||
|
||||
bool JavaInstall::operator<(const JavaInstall &rhs)
|
||||
{
|
||||
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||
if(archCompare != 0)
|
||||
return archCompare < 0;
|
||||
if(id < rhs.id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(id > rhs.id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
||||
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
|
||||
if(archCompare != 0)
|
||||
return archCompare < 0;
|
||||
if(id < rhs.id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if(id > rhs.id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator==(const JavaInstall &rhs)
|
||||
{
|
||||
return arch == rhs.arch && id == rhs.id && path == rhs.path;
|
||||
return arch == rhs.arch && id == rhs.id && path == rhs.path;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator>(const JavaInstall &rhs)
|
||||
{
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
}
|
||||
|
||||
@@ -5,34 +5,34 @@
|
||||
|
||||
struct JavaInstall : public BaseVersion
|
||||
{
|
||||
JavaInstall(){}
|
||||
JavaInstall(QString id, QString arch, QString path)
|
||||
: id(id), arch(arch), path(path)
|
||||
{
|
||||
}
|
||||
virtual QString descriptor()
|
||||
{
|
||||
return id.toString();
|
||||
}
|
||||
JavaInstall(){}
|
||||
JavaInstall(QString id, QString arch, QString path)
|
||||
: id(id), arch(arch), path(path)
|
||||
{
|
||||
}
|
||||
virtual QString descriptor()
|
||||
{
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
virtual QString name()
|
||||
{
|
||||
return id.toString();
|
||||
}
|
||||
virtual QString name()
|
||||
{
|
||||
return id.toString();
|
||||
}
|
||||
|
||||
virtual QString typeString() const
|
||||
{
|
||||
return arch;
|
||||
}
|
||||
virtual QString typeString() const
|
||||
{
|
||||
return arch;
|
||||
}
|
||||
|
||||
bool operator<(const JavaInstall & rhs);
|
||||
bool operator==(const JavaInstall & rhs);
|
||||
bool operator>(const JavaInstall & rhs);
|
||||
bool operator<(const JavaInstall & rhs);
|
||||
bool operator==(const JavaInstall & rhs);
|
||||
bool operator>(const JavaInstall & rhs);
|
||||
|
||||
JavaVersion id;
|
||||
QString arch;
|
||||
QString path;
|
||||
bool recommended = false;
|
||||
JavaVersion id;
|
||||
QString arch;
|
||||
QString path;
|
||||
bool recommended = false;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,92 +29,113 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Task *JavaInstallList::getLoadTask()
|
||||
shared_qobject_ptr<Task> JavaInstallList::getLoadTask()
|
||||
{
|
||||
return new JavaListLoadTask(this);
|
||||
load();
|
||||
return getCurrentTask();
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Task> JavaInstallList::getCurrentTask()
|
||||
{
|
||||
if(m_status == Status::InProgress)
|
||||
{
|
||||
return m_loadTask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JavaInstallList::load()
|
||||
{
|
||||
if(m_status != Status::InProgress)
|
||||
{
|
||||
m_status = Status::InProgress;
|
||||
m_loadTask = new JavaListLoadTask(this);
|
||||
m_loadTask->start();
|
||||
}
|
||||
}
|
||||
|
||||
const BaseVersionPtr JavaInstallList::at(int i) const
|
||||
{
|
||||
return m_vlist.at(i);
|
||||
return m_vlist.at(i);
|
||||
}
|
||||
|
||||
bool JavaInstallList::isLoaded()
|
||||
{
|
||||
return m_loaded;
|
||||
return m_status == JavaInstallList::Status::Done;
|
||||
}
|
||||
|
||||
int JavaInstallList::count() const
|
||||
{
|
||||
return m_vlist.count();
|
||||
return m_vlist.count();
|
||||
}
|
||||
|
||||
QVariant JavaInstallList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
if (index.row() > count())
|
||||
return QVariant();
|
||||
|
||||
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(m_vlist[index.row()]);
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
case VersionRole:
|
||||
return version->id.toString();
|
||||
case RecommendedRole:
|
||||
return version->recommended;
|
||||
case PathRole:
|
||||
return version->path;
|
||||
case ArchitectureRole:
|
||||
return version->arch;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(m_vlist[index.row()]);
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
case VersionRole:
|
||||
return version->id.toString();
|
||||
case RecommendedRole:
|
||||
return version->recommended;
|
||||
case PathRole:
|
||||
return version->path;
|
||||
case ArchitectureRole:
|
||||
return version->arch;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
BaseVersionList::RoleList JavaInstallList::providesRoles() const
|
||||
{
|
||||
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
|
||||
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
|
||||
}
|
||||
|
||||
|
||||
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_vlist = versions;
|
||||
m_loaded = true;
|
||||
sortVersions();
|
||||
if(m_vlist.size())
|
||||
{
|
||||
auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]);
|
||||
best->recommended = true;
|
||||
}
|
||||
endResetModel();
|
||||
beginResetModel();
|
||||
m_vlist = versions;
|
||||
sortVersions();
|
||||
if(m_vlist.size())
|
||||
{
|
||||
auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]);
|
||||
best->recommended = true;
|
||||
}
|
||||
endResetModel();
|
||||
m_status = Status::Done;
|
||||
m_loadTask.reset();
|
||||
}
|
||||
|
||||
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
|
||||
{
|
||||
auto rleft = std::dynamic_pointer_cast<JavaInstall>(left);
|
||||
auto rright = std::dynamic_pointer_cast<JavaInstall>(right);
|
||||
return (*rleft) > (*rright);
|
||||
auto rleft = std::dynamic_pointer_cast<JavaInstall>(left);
|
||||
auto rright = std::dynamic_pointer_cast<JavaInstall>(right);
|
||||
return (*rleft) > (*rright);
|
||||
}
|
||||
|
||||
void JavaInstallList::sortVersions()
|
||||
{
|
||||
beginResetModel();
|
||||
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
|
||||
endResetModel();
|
||||
beginResetModel();
|
||||
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task()
|
||||
{
|
||||
m_list = vlist;
|
||||
m_currentRecommended = NULL;
|
||||
m_list = vlist;
|
||||
m_currentRecommended = NULL;
|
||||
}
|
||||
|
||||
JavaListLoadTask::~JavaListLoadTask()
|
||||
@@ -123,64 +144,65 @@ JavaListLoadTask::~JavaListLoadTask()
|
||||
|
||||
void JavaListLoadTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Detecting Java installations..."));
|
||||
setStatus(tr("Detecting Java installations..."));
|
||||
|
||||
JavaUtils ju;
|
||||
QList<QString> candidate_paths = ju.FindJavaPaths();
|
||||
JavaUtils ju;
|
||||
QList<QString> candidate_paths = ju.FindJavaPaths();
|
||||
|
||||
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
|
||||
connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
|
||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||
m_job = new JavaCheckerJob("Java detection");
|
||||
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||
|
||||
qDebug() << "Probing the following Java paths: ";
|
||||
int id = 0;
|
||||
for(QString candidate : candidate_paths)
|
||||
{
|
||||
qDebug() << " " << candidate;
|
||||
qDebug() << "Probing the following Java paths: ";
|
||||
int id = 0;
|
||||
for(QString candidate : candidate_paths)
|
||||
{
|
||||
qDebug() << " " << candidate;
|
||||
|
||||
auto candidate_checker = new JavaChecker();
|
||||
candidate_checker->m_path = candidate;
|
||||
candidate_checker->m_id = id;
|
||||
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
|
||||
auto candidate_checker = new JavaChecker();
|
||||
candidate_checker->m_path = candidate;
|
||||
candidate_checker->m_id = id;
|
||||
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
|
||||
|
||||
id++;
|
||||
}
|
||||
id++;
|
||||
}
|
||||
|
||||
m_job->start();
|
||||
m_job->start();
|
||||
}
|
||||
|
||||
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
|
||||
void JavaListLoadTask::javaCheckerFinished()
|
||||
{
|
||||
QList<JavaInstallPtr> candidates;
|
||||
QList<JavaInstallPtr> candidates;
|
||||
auto results = m_job->getResults();
|
||||
|
||||
qDebug() << "Found the following valid Java installations:";
|
||||
for(JavaCheckResult result : results)
|
||||
{
|
||||
if(result.validity == JavaCheckResult::Validity::Valid)
|
||||
{
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
qDebug() << "Found the following valid Java installations:";
|
||||
for(JavaCheckResult result : results)
|
||||
{
|
||||
if(result.validity == JavaCheckResult::Validity::Valid)
|
||||
{
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = result.javaVersion;
|
||||
javaVersion->arch = result.mojangPlatform;
|
||||
javaVersion->path = result.path;
|
||||
candidates.append(javaVersion);
|
||||
javaVersion->id = result.javaVersion;
|
||||
javaVersion->arch = result.mojangPlatform;
|
||||
javaVersion->path = result.path;
|
||||
candidates.append(javaVersion);
|
||||
|
||||
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
|
||||
}
|
||||
}
|
||||
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
|
||||
}
|
||||
}
|
||||
|
||||
QList<BaseVersionPtr> javas_bvp;
|
||||
for (auto java : candidates)
|
||||
{
|
||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
QList<BaseVersionPtr> javas_bvp;
|
||||
for (auto java : candidates)
|
||||
{
|
||||
//qDebug() << java->id << java->arch << " at " << java->path;
|
||||
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
|
||||
|
||||
if (bp_java)
|
||||
{
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
}
|
||||
if (bp_java)
|
||||
{
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
}
|
||||
|
||||
m_list->updateListData(javas_bvp);
|
||||
emitSucceeded();
|
||||
m_list->updateListData(javas_bvp);
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,48 +24,60 @@
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaInstall.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class JavaListLoadTask;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
enum class Status
|
||||
{
|
||||
NotDone,
|
||||
InProgress,
|
||||
Done
|
||||
};
|
||||
public:
|
||||
explicit JavaInstallList(QObject *parent = 0);
|
||||
explicit JavaInstallList(QObject *parent = 0);
|
||||
|
||||
virtual Task *getLoadTask() override;
|
||||
virtual bool isLoaded() override;
|
||||
virtual const BaseVersionPtr at(int i) const override;
|
||||
virtual int count() const override;
|
||||
virtual void sortVersions() override;
|
||||
shared_qobject_ptr<Task> getLoadTask() override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersionPtr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
virtual RoleList providesRoles() const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
RoleList providesRoles() const override;
|
||||
|
||||
public slots:
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
|
||||
protected:
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
void load();
|
||||
shared_qobject_ptr<Task> getCurrentTask();
|
||||
|
||||
bool m_loaded = false;
|
||||
protected:
|
||||
Status m_status = Status::NotDone;
|
||||
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
};
|
||||
|
||||
class JavaListLoadTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JavaListLoadTask(JavaInstallList *vlist);
|
||||
~JavaListLoadTask();
|
||||
explicit JavaListLoadTask(JavaInstallList *vlist);
|
||||
virtual ~JavaListLoadTask();
|
||||
|
||||
virtual void executeTask();
|
||||
void executeTask() override;
|
||||
public slots:
|
||||
void javaCheckerFinished(QList<JavaCheckResult> results);
|
||||
void javaCheckerFinished();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
JavaInstallList *m_list;
|
||||
JavaInstall *m_currentRecommended;
|
||||
shared_qobject_ptr<JavaCheckerJob> m_job;
|
||||
JavaInstallList *m_list;
|
||||
JavaInstall *m_currentRecommended;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,231 +22,324 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include "java/JavaUtils.h"
|
||||
#include "java/JavaCheckerJob.h"
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
JavaUtils::JavaUtils()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
|
||||
{
|
||||
QDir mmcBin(QCoreApplication::applicationDirPath());
|
||||
auto items = LD_LIBRARY_PATH.split(':');
|
||||
QStringList final;
|
||||
for(auto & item: items)
|
||||
{
|
||||
QDir test(item);
|
||||
if(test == mmcBin)
|
||||
{
|
||||
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
|
||||
continue;
|
||||
}
|
||||
final.append(item);
|
||||
}
|
||||
return final.join(':');
|
||||
}
|
||||
#endif
|
||||
|
||||
QProcessEnvironment CleanEnviroment()
|
||||
{
|
||||
// prepare the process environment
|
||||
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
|
||||
QProcessEnvironment env;
|
||||
|
||||
QStringList ignored =
|
||||
{
|
||||
"JAVA_ARGS",
|
||||
"CLASSPATH",
|
||||
"CONFIGPATH",
|
||||
"JAVA_HOME",
|
||||
"JRE_HOME",
|
||||
"_JAVA_OPTIONS",
|
||||
"JAVA_OPTIONS",
|
||||
"JAVA_TOOL_OPTIONS"
|
||||
};
|
||||
for(auto key: rawenv.keys())
|
||||
{
|
||||
auto value = rawenv.value(key);
|
||||
// filter out dangerous java crap
|
||||
if(ignored.contains(key))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
// filter MultiMC-related things
|
||||
if(key.startsWith("QT_"))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
#ifdef Q_OS_LINUX
|
||||
// Do not pass LD_* variables to java. They were intended for MultiMC
|
||||
if(key.startsWith("LD_"))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
// Strip IBus
|
||||
// IBus is a Linux IME framework. For some reason, it breaks MC?
|
||||
if (key == "XMODIFIERS" && value.contains(IBUS))
|
||||
{
|
||||
QString save = value;
|
||||
value.replace(IBUS, "");
|
||||
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
|
||||
}
|
||||
if(key == "GAME_PRELOAD")
|
||||
{
|
||||
env.insert("LD_PRELOAD", value);
|
||||
continue;
|
||||
}
|
||||
if(key == "GAME_LIBRARY_PATH")
|
||||
{
|
||||
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
// qDebug() << "Env: " << key << value;
|
||||
env.insert(key, value);
|
||||
}
|
||||
#ifdef Q_OS_LINUX
|
||||
// HACK: Workaround for QTBUG42500
|
||||
if(!env.contains("LD_LIBRARY_PATH"))
|
||||
{
|
||||
env.insert("LD_LIBRARY_PATH", "");
|
||||
}
|
||||
#endif
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
|
||||
{
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = id;
|
||||
javaVersion->arch = arch;
|
||||
javaVersion->path = path;
|
||||
javaVersion->id = id;
|
||||
javaVersion->arch = arch;
|
||||
javaVersion->path = path;
|
||||
|
||||
return javaVersion;
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
JavaInstallPtr JavaUtils::GetDefaultJava()
|
||||
{
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = "java";
|
||||
javaVersion->arch = "unknown";
|
||||
javaVersion->id = "java";
|
||||
javaVersion->arch = "unknown";
|
||||
#if defined(Q_OS_WIN32)
|
||||
javaVersion->path = "javaw";
|
||||
javaVersion->path = "javaw";
|
||||
#else
|
||||
javaVersion->path = "java";
|
||||
javaVersion->path = "java";
|
||||
#endif
|
||||
|
||||
return javaVersion;
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
|
||||
{
|
||||
QList<JavaInstallPtr> javas;
|
||||
QList<JavaInstallPtr> javas;
|
||||
|
||||
QString archType = "unknown";
|
||||
if (keyType == KEY_WOW64_64KEY)
|
||||
archType = "64";
|
||||
else if (keyType == KEY_WOW64_32KEY)
|
||||
archType = "32";
|
||||
QString archType = "unknown";
|
||||
if (keyType == KEY_WOW64_64KEY)
|
||||
archType = "64";
|
||||
else if (keyType == KEY_WOW64_32KEY)
|
||||
archType = "32";
|
||||
|
||||
HKEY jreKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
|
||||
KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
|
||||
{
|
||||
// Read the current type version from the registry.
|
||||
// This will be used to find any key that contains the JavaHome value.
|
||||
char *value = new char[0];
|
||||
DWORD valueSz = 0;
|
||||
if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
|
||||
ERROR_MORE_DATA)
|
||||
{
|
||||
value = new char[valueSz];
|
||||
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
|
||||
}
|
||||
HKEY jreKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
|
||||
KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
|
||||
{
|
||||
// Read the current type version from the registry.
|
||||
// This will be used to find any key that contains the JavaHome value.
|
||||
char *value = new char[0];
|
||||
DWORD valueSz = 0;
|
||||
if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
|
||||
ERROR_MORE_DATA)
|
||||
{
|
||||
value = new char[valueSz];
|
||||
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
|
||||
}
|
||||
|
||||
QString recommended = value;
|
||||
QString recommended = value;
|
||||
|
||||
TCHAR subKeyName[255];
|
||||
DWORD subKeyNameSize, numSubKeys, retCode;
|
||||
TCHAR subKeyName[255];
|
||||
DWORD subKeyNameSize, numSubKeys, retCode;
|
||||
|
||||
// Get the number of subkeys
|
||||
RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL);
|
||||
// Get the number of subkeys
|
||||
RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL);
|
||||
|
||||
// Iterate until RegEnumKeyEx fails
|
||||
if (numSubKeys > 0)
|
||||
{
|
||||
for (int i = 0; i < numSubKeys; i++)
|
||||
{
|
||||
subKeyNameSize = 255;
|
||||
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (retCode == ERROR_SUCCESS)
|
||||
{
|
||||
// Now open the registry key for the version that we just got.
|
||||
QString newKeyName = keyName + "\\" + subKeyName;
|
||||
// Iterate until RegEnumKeyEx fails
|
||||
if (numSubKeys > 0)
|
||||
{
|
||||
for (DWORD i = 0; i < numSubKeys; i++)
|
||||
{
|
||||
subKeyNameSize = 255;
|
||||
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
|
||||
NULL);
|
||||
if (retCode == ERROR_SUCCESS)
|
||||
{
|
||||
// Now open the registry key for the version that we just got.
|
||||
QString newKeyName = keyName + "\\" + subKeyName;
|
||||
|
||||
HKEY newKey;
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
|
||||
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
|
||||
{
|
||||
// Read the JavaHome value to find where Java is installed.
|
||||
value = new char[0];
|
||||
valueSz = 0;
|
||||
if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
&valueSz) == ERROR_MORE_DATA)
|
||||
{
|
||||
value = new char[valueSz];
|
||||
RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
&valueSz);
|
||||
HKEY newKey;
|
||||
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
|
||||
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
|
||||
{
|
||||
// Read the JavaHome value to find where Java is installed.
|
||||
value = new char[0];
|
||||
valueSz = 0;
|
||||
if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
&valueSz) == ERROR_MORE_DATA)
|
||||
{
|
||||
value = new char[valueSz];
|
||||
RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
|
||||
&valueSz);
|
||||
|
||||
// Now, we construct the version object and add it to the list.
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
// Now, we construct the version object and add it to the list.
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = subKeyName;
|
||||
javaVersion->arch = archType;
|
||||
javaVersion->path =
|
||||
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
|
||||
javas.append(javaVersion);
|
||||
}
|
||||
javaVersion->id = subKeyName;
|
||||
javaVersion->arch = archType;
|
||||
javaVersion->path =
|
||||
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
|
||||
javas.append(javaVersion);
|
||||
}
|
||||
|
||||
RegCloseKey(newKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RegCloseKey(newKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(jreKey);
|
||||
}
|
||||
RegCloseKey(jreKey);
|
||||
}
|
||||
|
||||
return javas;
|
||||
return javas;
|
||||
}
|
||||
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QList<JavaInstallPtr> java_candidates;
|
||||
QList<JavaInstallPtr> java_candidates;
|
||||
|
||||
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
|
||||
java_candidates.append(JRE64s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK64s);
|
||||
java_candidates.append(JRE32s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK32s);
|
||||
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
|
||||
java_candidates.append(JRE64s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK64s);
|
||||
java_candidates.append(JRE32s);
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
|
||||
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
|
||||
java_candidates.append(JDK32s);
|
||||
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
|
||||
|
||||
QList<QString> candidates;
|
||||
for(JavaInstallPtr java_candidate : java_candidates)
|
||||
{
|
||||
if(!candidates.contains(java_candidate->path))
|
||||
{
|
||||
candidates.append(java_candidate->path);
|
||||
}
|
||||
}
|
||||
QList<QString> candidates;
|
||||
for(JavaInstallPtr java_candidate : java_candidates)
|
||||
{
|
||||
if(!candidates.contains(java_candidate->path))
|
||||
{
|
||||
candidates.append(java_candidate->path);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_MAC)
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
|
||||
javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
|
||||
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
||||
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
||||
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString &java, libraryJVMJavas) {
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
|
||||
}
|
||||
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
||||
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString &java, systemLibraryJVMJavas) {
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||
}
|
||||
return javas;
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
|
||||
javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
|
||||
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
|
||||
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
|
||||
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString &java, libraryJVMJavas) {
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
|
||||
}
|
||||
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
|
||||
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
foreach (const QString &java, systemLibraryJVMJavas) {
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
|
||||
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
|
||||
}
|
||||
return javas;
|
||||
}
|
||||
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
|
||||
qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
|
||||
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
auto scanJavaDir = [&](const QString & dirPath)
|
||||
{
|
||||
QDir dir(dirPath);
|
||||
if(!dir.exists())
|
||||
return;
|
||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
||||
for(auto & entry: entries)
|
||||
{
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
auto scanJavaDir = [&](const QString & dirPath)
|
||||
{
|
||||
QDir dir(dirPath);
|
||||
if(!dir.exists())
|
||||
return;
|
||||
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
|
||||
for(auto & entry: entries)
|
||||
{
|
||||
|
||||
QString prefix;
|
||||
if(entry.isAbsolute())
|
||||
{
|
||||
prefix = entry.absoluteFilePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = entry.filePath();
|
||||
}
|
||||
QString prefix;
|
||||
if(entry.isAbsolute())
|
||||
{
|
||||
prefix = entry.absoluteFilePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = entry.filePath();
|
||||
}
|
||||
|
||||
javas.append(FS::PathCombine(prefix, "jre/bin/java"));
|
||||
javas.append(FS::PathCombine(prefix, "bin/java"));
|
||||
}
|
||||
};
|
||||
// oracle RPMs
|
||||
scanJavaDir("/usr/java");
|
||||
// general locations used by distro packaging
|
||||
scanJavaDir("/usr/lib/jvm");
|
||||
scanJavaDir("/usr/lib32/jvm");
|
||||
// javas stored in MultiMC's folder
|
||||
scanJavaDir("java");
|
||||
return javas;
|
||||
javas.append(FS::PathCombine(prefix, "jre/bin/java"));
|
||||
javas.append(FS::PathCombine(prefix, "bin/java"));
|
||||
}
|
||||
};
|
||||
// oracle RPMs
|
||||
scanJavaDir("/usr/java");
|
||||
// general locations used by distro packaging
|
||||
scanJavaDir("/usr/lib/jvm");
|
||||
scanJavaDir("/usr/lib32/jvm");
|
||||
// javas stored in MultiMC's folder
|
||||
scanJavaDir("java");
|
||||
return javas;
|
||||
}
|
||||
#else
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
qDebug() << "Unknown operating system build - defaulting to \"java\"";
|
||||
qDebug() << "Unknown operating system build - defaulting to \"java\"";
|
||||
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
|
||||
return javas;
|
||||
return javas;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaChecker.h"
|
||||
#include "JavaInstallList.h"
|
||||
|
||||
@@ -27,17 +26,19 @@
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
QProcessEnvironment CleanEnviroment();
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
JavaUtils();
|
||||
JavaUtils();
|
||||
|
||||
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
|
||||
QList<QString> FindJavaPaths();
|
||||
JavaInstallPtr GetDefaultJava();
|
||||
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
|
||||
QList<QString> FindJavaPaths();
|
||||
JavaInstallPtr GetDefaultJava();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
|
||||
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -6,107 +6,116 @@
|
||||
|
||||
JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
|
||||
{
|
||||
m_string = javaVersionString;
|
||||
m_string = javaVersionString;
|
||||
|
||||
auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
|
||||
{
|
||||
auto str = match.captured(what);
|
||||
if(str.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return str.toInt();
|
||||
};
|
||||
auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
|
||||
{
|
||||
auto str = match.captured(what);
|
||||
if(str.isEmpty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return str.toInt();
|
||||
};
|
||||
|
||||
QRegularExpression pattern;
|
||||
if(javaVersionString.startsWith("1."))
|
||||
{
|
||||
pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
}
|
||||
QRegularExpression pattern;
|
||||
if(javaVersionString.startsWith("1."))
|
||||
{
|
||||
pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
}
|
||||
else
|
||||
{
|
||||
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
|
||||
}
|
||||
|
||||
auto match = pattern.match(m_string);
|
||||
m_parseable = match.hasMatch();
|
||||
m_major = getCapturedInteger(match, "major");
|
||||
m_minor = getCapturedInteger(match, "minor");
|
||||
m_security = getCapturedInteger(match, "security");
|
||||
m_prerelease = match.captured("prerelease");
|
||||
return *this;
|
||||
auto match = pattern.match(m_string);
|
||||
m_parseable = match.hasMatch();
|
||||
m_major = getCapturedInteger(match, "major");
|
||||
m_minor = getCapturedInteger(match, "minor");
|
||||
m_security = getCapturedInteger(match, "security");
|
||||
m_prerelease = match.captured("prerelease");
|
||||
return *this;
|
||||
}
|
||||
|
||||
JavaVersion::JavaVersion(const QString &rhs)
|
||||
{
|
||||
operator=(rhs);
|
||||
operator=(rhs);
|
||||
}
|
||||
|
||||
QString JavaVersion::toString()
|
||||
{
|
||||
return m_string;
|
||||
return m_string;
|
||||
}
|
||||
|
||||
bool JavaVersion::requiresPermGen()
|
||||
{
|
||||
if(m_parseable)
|
||||
{
|
||||
return m_major < 8;
|
||||
}
|
||||
return true;
|
||||
if(m_parseable)
|
||||
{
|
||||
return m_major < 8;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator<(const JavaVersion &rhs)
|
||||
{
|
||||
if(m_parseable && rhs.m_parseable)
|
||||
{
|
||||
if(m_major < rhs.m_major)
|
||||
return true;
|
||||
if(m_major > rhs.m_major)
|
||||
return false;
|
||||
if(m_minor < rhs.m_minor)
|
||||
return true;
|
||||
if(m_minor > rhs.m_minor)
|
||||
return false;
|
||||
if(m_security < rhs.m_security)
|
||||
return true;
|
||||
if(m_security > rhs.m_security)
|
||||
return false;
|
||||
if(m_parseable && rhs.m_parseable)
|
||||
{
|
||||
auto major = m_major;
|
||||
auto rmajor = rhs.m_major;
|
||||
|
||||
// everything else being equal, consider prerelease status
|
||||
bool thisPre = !m_prerelease.isEmpty();
|
||||
bool rhsPre = !rhs.m_prerelease.isEmpty();
|
||||
if(thisPre && !rhsPre)
|
||||
{
|
||||
// this is a prerelease and the other one isn't -> lesser
|
||||
return true;
|
||||
}
|
||||
else if(!thisPre && rhsPre)
|
||||
{
|
||||
// this isn't a prerelease and the other one is -> greater
|
||||
return false;
|
||||
}
|
||||
else if(thisPre && rhsPre)
|
||||
{
|
||||
// both are prereleases - use natural compare...
|
||||
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
|
||||
}
|
||||
// neither is prerelease, so they are the same -> this cannot be less than rhs
|
||||
return false;
|
||||
}
|
||||
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
|
||||
// HACK: discourage using java 9
|
||||
if(major > 8)
|
||||
major = -major;
|
||||
if(rmajor > 8)
|
||||
rmajor = -rmajor;
|
||||
|
||||
if(major < rmajor)
|
||||
return true;
|
||||
if(major > rmajor)
|
||||
return false;
|
||||
if(m_minor < rhs.m_minor)
|
||||
return true;
|
||||
if(m_minor > rhs.m_minor)
|
||||
return false;
|
||||
if(m_security < rhs.m_security)
|
||||
return true;
|
||||
if(m_security > rhs.m_security)
|
||||
return false;
|
||||
|
||||
// everything else being equal, consider prerelease status
|
||||
bool thisPre = !m_prerelease.isEmpty();
|
||||
bool rhsPre = !rhs.m_prerelease.isEmpty();
|
||||
if(thisPre && !rhsPre)
|
||||
{
|
||||
// this is a prerelease and the other one isn't -> lesser
|
||||
return true;
|
||||
}
|
||||
else if(!thisPre && rhsPre)
|
||||
{
|
||||
// this isn't a prerelease and the other one is -> greater
|
||||
return false;
|
||||
}
|
||||
else if(thisPre && rhsPre)
|
||||
{
|
||||
// both are prereleases - use natural compare...
|
||||
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
|
||||
}
|
||||
// neither is prerelease, so they are the same -> this cannot be less than rhs
|
||||
return false;
|
||||
}
|
||||
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator==(const JavaVersion &rhs)
|
||||
{
|
||||
if(m_parseable && rhs.m_parseable)
|
||||
{
|
||||
return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease;
|
||||
}
|
||||
return m_string == rhs.m_string;
|
||||
if(m_parseable && rhs.m_parseable)
|
||||
{
|
||||
return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease;
|
||||
}
|
||||
return m_string == rhs.m_string;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator>(const JavaVersion &rhs)
|
||||
{
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
}
|
||||
|
||||
@@ -3,40 +3,48 @@
|
||||
#include "multimc_logic_export.h"
|
||||
#include <QString>
|
||||
|
||||
// NOTE: apparently the GNU C library pollutes the global namespace with these... undef them.
|
||||
#ifdef major
|
||||
#undef major
|
||||
#endif
|
||||
#ifdef minor
|
||||
#undef minor
|
||||
#endif
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaVersion
|
||||
{
|
||||
friend class JavaVersionTest;
|
||||
friend class JavaVersionTest;
|
||||
public:
|
||||
JavaVersion() {};
|
||||
JavaVersion(const QString & rhs);
|
||||
JavaVersion() {};
|
||||
JavaVersion(const QString & rhs);
|
||||
|
||||
JavaVersion & operator=(const QString & rhs);
|
||||
JavaVersion & operator=(const QString & rhs);
|
||||
|
||||
bool operator<(const JavaVersion & rhs);
|
||||
bool operator==(const JavaVersion & rhs);
|
||||
bool operator>(const JavaVersion & rhs);
|
||||
bool operator<(const JavaVersion & rhs);
|
||||
bool operator==(const JavaVersion & rhs);
|
||||
bool operator>(const JavaVersion & rhs);
|
||||
|
||||
bool requiresPermGen();
|
||||
bool requiresPermGen();
|
||||
|
||||
QString toString();
|
||||
QString toString();
|
||||
|
||||
int major()
|
||||
{
|
||||
return m_major;
|
||||
}
|
||||
int minor()
|
||||
{
|
||||
return m_minor;
|
||||
}
|
||||
int security()
|
||||
{
|
||||
return m_security;
|
||||
}
|
||||
int major()
|
||||
{
|
||||
return m_major;
|
||||
}
|
||||
int minor()
|
||||
{
|
||||
return m_minor;
|
||||
}
|
||||
int security()
|
||||
{
|
||||
return m_security;
|
||||
}
|
||||
private:
|
||||
QString m_string;
|
||||
int m_major = 0;
|
||||
int m_minor = 0;
|
||||
int m_security = 0;
|
||||
bool m_parseable = false;
|
||||
QString m_prerelease;
|
||||
QString m_string;
|
||||
int m_major = 0;
|
||||
int m_minor = 0;
|
||||
int m_security = 0;
|
||||
bool m_parseable = false;
|
||||
QString m_prerelease;
|
||||
};
|
||||
|
||||
@@ -5,110 +5,110 @@
|
||||
|
||||
class JavaVersionTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
private
|
||||
slots:
|
||||
void test_Parse_data()
|
||||
{
|
||||
QTest::addColumn<QString>("string");
|
||||
QTest::addColumn<int>("major");
|
||||
QTest::addColumn<int>("minor");
|
||||
QTest::addColumn<int>("security");
|
||||
QTest::addColumn<QString>("prerelease");
|
||||
void test_Parse_data()
|
||||
{
|
||||
QTest::addColumn<QString>("string");
|
||||
QTest::addColumn<int>("major");
|
||||
QTest::addColumn<int>("minor");
|
||||
QTest::addColumn<int>("security");
|
||||
QTest::addColumn<QString>("prerelease");
|
||||
|
||||
QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString();
|
||||
QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea";
|
||||
QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString();
|
||||
QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea";
|
||||
|
||||
QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString();
|
||||
QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString();
|
||||
QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString();
|
||||
QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea";
|
||||
QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea";
|
||||
}
|
||||
void test_Parse()
|
||||
{
|
||||
QFETCH(QString, string);
|
||||
QFETCH(int, major);
|
||||
QFETCH(int, minor);
|
||||
QFETCH(int, security);
|
||||
QFETCH(QString, prerelease);
|
||||
QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString();
|
||||
QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString();
|
||||
QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString();
|
||||
QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea";
|
||||
QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea";
|
||||
}
|
||||
void test_Parse()
|
||||
{
|
||||
QFETCH(QString, string);
|
||||
QFETCH(int, major);
|
||||
QFETCH(int, minor);
|
||||
QFETCH(int, security);
|
||||
QFETCH(QString, prerelease);
|
||||
|
||||
JavaVersion test(string);
|
||||
QCOMPARE(test.m_string, string);
|
||||
QCOMPARE(test.toString(), string);
|
||||
QCOMPARE(test.m_major, major);
|
||||
QCOMPARE(test.m_minor, minor);
|
||||
QCOMPARE(test.m_security, security);
|
||||
QCOMPARE(test.m_prerelease, prerelease);
|
||||
}
|
||||
JavaVersion test(string);
|
||||
QCOMPARE(test.m_string, string);
|
||||
QCOMPARE(test.toString(), string);
|
||||
QCOMPARE(test.m_major, major);
|
||||
QCOMPARE(test.m_minor, minor);
|
||||
QCOMPARE(test.m_security, security);
|
||||
QCOMPARE(test.m_prerelease, prerelease);
|
||||
}
|
||||
|
||||
void test_Sort_data()
|
||||
{
|
||||
QTest::addColumn<QString>("lhs");
|
||||
QTest::addColumn<QString>("rhs");
|
||||
QTest::addColumn<bool>("smaller");
|
||||
QTest::addColumn<bool>("equal");
|
||||
QTest::addColumn<bool>("bigger");
|
||||
void test_Sort_data()
|
||||
{
|
||||
QTest::addColumn<QString>("lhs");
|
||||
QTest::addColumn<QString>("rhs");
|
||||
QTest::addColumn<bool>("smaller");
|
||||
QTest::addColumn<bool>("equal");
|
||||
QTest::addColumn<bool>("bigger");
|
||||
|
||||
// old format and new format equivalence
|
||||
QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false;
|
||||
// old format major version
|
||||
QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false;
|
||||
// new format - first release vs first security patch
|
||||
QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false;
|
||||
QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true;
|
||||
// new format - first minor vs first release/security patch
|
||||
QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true;
|
||||
QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false;
|
||||
QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true;
|
||||
QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false;
|
||||
// new format - omitted numbers
|
||||
QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false;
|
||||
QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false;
|
||||
QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false;
|
||||
// early access and prereleases compared to final release
|
||||
QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false;
|
||||
QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false;
|
||||
QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true;
|
||||
// prerelease difference only testing
|
||||
QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false;
|
||||
QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false;
|
||||
QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false;
|
||||
QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false;
|
||||
QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false;
|
||||
QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false;
|
||||
QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false;
|
||||
}
|
||||
void test_Sort()
|
||||
{
|
||||
QFETCH(QString, lhs);
|
||||
QFETCH(QString, rhs);
|
||||
QFETCH(bool, smaller);
|
||||
QFETCH(bool, equal);
|
||||
QFETCH(bool, bigger);
|
||||
JavaVersion lver(lhs);
|
||||
JavaVersion rver(rhs);
|
||||
QCOMPARE(lver < rver, smaller);
|
||||
QCOMPARE(lver == rver, equal);
|
||||
QCOMPARE(lver > rver, bigger);
|
||||
}
|
||||
void test_PermGen_data()
|
||||
{
|
||||
QTest::addColumn<QString>("version");
|
||||
QTest::addColumn<bool>("needs_permgen");
|
||||
QTest::newRow("1.6.0_33") << "1.6.0_33" << true;
|
||||
QTest::newRow("1.7.0_60") << "1.7.0_60" << true;
|
||||
QTest::newRow("1.8.0_22") << "1.8.0_22" << false;
|
||||
QTest::newRow("9-ea") << "9-ea" << false;
|
||||
QTest::newRow("9.2.4") << "9.2.4" << false;
|
||||
}
|
||||
void test_PermGen()
|
||||
{
|
||||
QFETCH(QString, version);
|
||||
QFETCH(bool, needs_permgen);
|
||||
JavaVersion v(version);
|
||||
QCOMPARE(needs_permgen, v.requiresPermGen());
|
||||
}
|
||||
// old format and new format equivalence
|
||||
QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false;
|
||||
// old format major version
|
||||
QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false;
|
||||
// new format - first release vs first security patch
|
||||
QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false;
|
||||
QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true;
|
||||
// new format - first minor vs first release/security patch
|
||||
QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true;
|
||||
QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false;
|
||||
QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true;
|
||||
QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false;
|
||||
// new format - omitted numbers
|
||||
QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false;
|
||||
QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false;
|
||||
QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false;
|
||||
// early access and prereleases compared to final release
|
||||
QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false;
|
||||
QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false;
|
||||
QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true;
|
||||
// prerelease difference only testing
|
||||
QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false;
|
||||
QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false;
|
||||
QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false;
|
||||
QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false;
|
||||
QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false;
|
||||
QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false;
|
||||
QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false;
|
||||
}
|
||||
void test_Sort()
|
||||
{
|
||||
QFETCH(QString, lhs);
|
||||
QFETCH(QString, rhs);
|
||||
QFETCH(bool, smaller);
|
||||
QFETCH(bool, equal);
|
||||
QFETCH(bool, bigger);
|
||||
JavaVersion lver(lhs);
|
||||
JavaVersion rver(rhs);
|
||||
QCOMPARE(lver < rver, smaller);
|
||||
QCOMPARE(lver == rver, equal);
|
||||
QCOMPARE(lver > rver, bigger);
|
||||
}
|
||||
void test_PermGen_data()
|
||||
{
|
||||
QTest::addColumn<QString>("version");
|
||||
QTest::addColumn<bool>("needs_permgen");
|
||||
QTest::newRow("1.6.0_33") << "1.6.0_33" << true;
|
||||
QTest::newRow("1.7.0_60") << "1.7.0_60" << true;
|
||||
QTest::newRow("1.8.0_22") << "1.8.0_22" << false;
|
||||
QTest::newRow("9-ea") << "9-ea" << false;
|
||||
QTest::newRow("9.2.4") << "9.2.4" << false;
|
||||
}
|
||||
void test_PermGen()
|
||||
{
|
||||
QFETCH(QString, version);
|
||||
QFETCH(bool, needs_permgen);
|
||||
JavaVersion v(version);
|
||||
QCOMPARE(needs_permgen, v.requiresPermGen());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(JavaVersionTest)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,115 +22,115 @@
|
||||
|
||||
void CheckJava::executeTask()
|
||||
{
|
||||
auto instance = m_parent->instance();
|
||||
auto settings = instance->settings();
|
||||
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
|
||||
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
|
||||
auto instance = m_parent->instance();
|
||||
auto settings = instance->settings();
|
||||
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
|
||||
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
|
||||
|
||||
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
|
||||
if (realJavaPath.isEmpty())
|
||||
{
|
||||
if (perInstance)
|
||||
{
|
||||
emit logLine(
|
||||
tr("The java binary \"%1\" couldn't be found. Please fix the java path "
|
||||
"override in the instance's settings or disable it.").arg(m_javaPath),
|
||||
MessageLevel::Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
|
||||
"the settings.").arg(m_javaPath),
|
||||
MessageLevel::Warning);
|
||||
}
|
||||
emitFailed(tr("Java path is not valid."));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
|
||||
}
|
||||
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
|
||||
if (realJavaPath.isEmpty())
|
||||
{
|
||||
if (perInstance)
|
||||
{
|
||||
emit logLine(
|
||||
tr("The java binary \"%1\" couldn't be found. Please fix the java path "
|
||||
"override in the instance's settings or disable it.").arg(m_javaPath),
|
||||
MessageLevel::Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
|
||||
"the settings.").arg(m_javaPath),
|
||||
MessageLevel::Warning);
|
||||
}
|
||||
emitFailed(tr("Java path is not valid."));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
|
||||
}
|
||||
|
||||
QFileInfo javaInfo(realJavaPath);
|
||||
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
||||
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
|
||||
auto storedArchitecture = settings->get("JavaArchitecture").toString();
|
||||
auto storedVersion = settings->get("JavaVersion").toString();
|
||||
m_javaUnixTime = javaUnixTime;
|
||||
// if timestamps are not the same, or something is missing, check!
|
||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
|
||||
{
|
||||
m_JavaChecker = std::make_shared<JavaChecker>();
|
||||
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
|
||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||
m_JavaChecker->m_path = realJavaPath;
|
||||
m_JavaChecker->performCheck();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto verString = instance->settings()->get("JavaVersion").toString();
|
||||
auto archString = instance->settings()->get("JavaArchitecture").toString();
|
||||
printJavaInfo(verString, archString);
|
||||
}
|
||||
emitSucceeded();
|
||||
QFileInfo javaInfo(realJavaPath);
|
||||
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
|
||||
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
|
||||
auto storedArchitecture = settings->get("JavaArchitecture").toString();
|
||||
auto storedVersion = settings->get("JavaVersion").toString();
|
||||
m_javaUnixTime = javaUnixTime;
|
||||
// if timestamps are not the same, or something is missing, check!
|
||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
|
||||
{
|
||||
m_JavaChecker = new JavaChecker();
|
||||
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
|
||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||
m_JavaChecker->m_path = realJavaPath;
|
||||
m_JavaChecker->performCheck();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto verString = instance->settings()->get("JavaVersion").toString();
|
||||
auto archString = instance->settings()->get("JavaArchitecture").toString();
|
||||
printJavaInfo(verString, archString);
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
void CheckJava::checkJavaFinished(JavaCheckResult result)
|
||||
{
|
||||
switch (result.validity)
|
||||
{
|
||||
case JavaCheckResult::Validity::Errored:
|
||||
{
|
||||
// Error message displayed if java can't start
|
||||
emit logLine(tr("Could not start java:"), MessageLevel::Error);
|
||||
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
|
||||
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
|
||||
printSystemInfo(false, false);
|
||||
emitFailed(tr("Could not start java!"));
|
||||
return;
|
||||
}
|
||||
case JavaCheckResult::Validity::ReturnedInvalidData:
|
||||
{
|
||||
emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
|
||||
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
|
||||
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
|
||||
printSystemInfo(false, false);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
case JavaCheckResult::Validity::Valid:
|
||||
{
|
||||
auto instance = m_parent->instance();
|
||||
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
|
||||
instance->settings()->set("JavaVersion", result.javaVersion.toString());
|
||||
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
||||
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
switch (result.validity)
|
||||
{
|
||||
case JavaCheckResult::Validity::Errored:
|
||||
{
|
||||
// Error message displayed if java can't start
|
||||
emit logLine(tr("Could not start java:"), MessageLevel::Error);
|
||||
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
|
||||
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
|
||||
printSystemInfo(false, false);
|
||||
emitFailed(tr("Could not start java!"));
|
||||
return;
|
||||
}
|
||||
case JavaCheckResult::Validity::ReturnedInvalidData:
|
||||
{
|
||||
emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
|
||||
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
|
||||
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
|
||||
printSystemInfo(false, false);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
case JavaCheckResult::Validity::Valid:
|
||||
{
|
||||
auto instance = m_parent->instance();
|
||||
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
|
||||
instance->settings()->set("JavaVersion", result.javaVersion.toString());
|
||||
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
|
||||
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
|
||||
{
|
||||
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
|
||||
printSystemInfo(true, architecture == "64");
|
||||
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
|
||||
printSystemInfo(true, architecture == "64");
|
||||
}
|
||||
|
||||
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
|
||||
{
|
||||
auto cpu64 = Sys::isCPU64bit();
|
||||
auto system64 = Sys::isSystem64bit();
|
||||
if(cpu64 != system64)
|
||||
{
|
||||
emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
|
||||
}
|
||||
if(javaIsKnown)
|
||||
{
|
||||
if(javaIs64bit != system64)
|
||||
{
|
||||
emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
|
||||
}
|
||||
}
|
||||
auto cpu64 = Sys::isCPU64bit();
|
||||
auto system64 = Sys::isSystem64bit();
|
||||
if(cpu64 != system64)
|
||||
{
|
||||
emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
|
||||
}
|
||||
if(javaIsKnown)
|
||||
{
|
||||
if(javaIs64bit != system64)
|
||||
{
|
||||
emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,25 +21,25 @@
|
||||
|
||||
class CheckJava: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
|
||||
virtual ~CheckJava() {};
|
||||
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
|
||||
virtual ~CheckJava() {};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void executeTask();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private slots:
|
||||
void checkJavaFinished(JavaCheckResult result);
|
||||
void checkJavaFinished(JavaCheckResult result);
|
||||
|
||||
private:
|
||||
void printJavaInfo(const QString & version, const QString & architecture);
|
||||
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
|
||||
void printJavaInfo(const QString & version, const QString & architecture);
|
||||
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
|
||||
|
||||
private:
|
||||
QString m_javaPath;
|
||||
qlonglong m_javaUnixTime;
|
||||
JavaCheckerPtr m_JavaChecker;
|
||||
QString m_javaPath;
|
||||
qlonglong m_javaUnixTime;
|
||||
JavaCheckerPtr m_JavaChecker;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
void LaunchStep::bind(LaunchTask *parent)
|
||||
{
|
||||
m_parent = parent;
|
||||
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
||||
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
||||
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
|
||||
connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
|
||||
connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested);
|
||||
m_parent = parent;
|
||||
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
|
||||
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
|
||||
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
|
||||
connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
|
||||
connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,28 +23,28 @@
|
||||
class LaunchTask;
|
||||
class LaunchStep: public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
public: /* methods */
|
||||
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
|
||||
{
|
||||
bind(parent);
|
||||
};
|
||||
virtual ~LaunchStep() {};
|
||||
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
|
||||
{
|
||||
bind(parent);
|
||||
};
|
||||
virtual ~LaunchStep() {};
|
||||
|
||||
protected: /* methods */
|
||||
virtual void bind(LaunchTask *parent);
|
||||
private: /* methods */
|
||||
void bind(LaunchTask *parent);
|
||||
|
||||
signals:
|
||||
void logLines(QStringList lines, MessageLevel::Enum level);
|
||||
void logLine(QString line, MessageLevel::Enum level);
|
||||
void readyForLaunch();
|
||||
void progressReportingRequest();
|
||||
void logLines(QStringList lines, MessageLevel::Enum level);
|
||||
void logLine(QString line, MessageLevel::Enum level);
|
||||
void readyForLaunch();
|
||||
void progressReportingRequest();
|
||||
|
||||
public slots:
|
||||
virtual void proceed() {};
|
||||
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
|
||||
virtual void finalize() {};
|
||||
virtual void proceed() {};
|
||||
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
|
||||
virtual void finalize() {};
|
||||
|
||||
protected: /* data */
|
||||
LaunchTask *m_parent;
|
||||
};
|
||||
LaunchTask *m_parent;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -30,251 +30,251 @@
|
||||
|
||||
void LaunchTask::init()
|
||||
{
|
||||
m_instance->setRunning(true);
|
||||
m_instance->setRunning(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
{
|
||||
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
proc->init();
|
||||
return proc;
|
||||
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
proc->init();
|
||||
return proc;
|
||||
}
|
||||
|
||||
LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
|
||||
{
|
||||
}
|
||||
|
||||
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.append(step);
|
||||
m_steps.append(step);
|
||||
}
|
||||
|
||||
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.prepend(step);
|
||||
m_steps.prepend(step);
|
||||
}
|
||||
|
||||
void LaunchTask::executeTask()
|
||||
{
|
||||
m_instance->setCrashed(false);
|
||||
if(!m_steps.size())
|
||||
{
|
||||
state = LaunchTask::Finished;
|
||||
emitSucceeded();
|
||||
}
|
||||
state = LaunchTask::Running;
|
||||
onStepFinished();
|
||||
m_instance->setCrashed(false);
|
||||
if(!m_steps.size())
|
||||
{
|
||||
state = LaunchTask::Finished;
|
||||
emitSucceeded();
|
||||
}
|
||||
state = LaunchTask::Running;
|
||||
onStepFinished();
|
||||
}
|
||||
|
||||
void LaunchTask::onReadyForLaunch()
|
||||
{
|
||||
state = LaunchTask::Waiting;
|
||||
emit readyForLaunch();
|
||||
state = LaunchTask::Waiting;
|
||||
emit readyForLaunch();
|
||||
}
|
||||
|
||||
void LaunchTask::onStepFinished()
|
||||
{
|
||||
// initial -> just start the first step
|
||||
if(currentStep == -1)
|
||||
{
|
||||
currentStep ++;
|
||||
m_steps[currentStep]->start();
|
||||
return;
|
||||
}
|
||||
// initial -> just start the first step
|
||||
if(currentStep == -1)
|
||||
{
|
||||
currentStep ++;
|
||||
m_steps[currentStep]->start();
|
||||
return;
|
||||
}
|
||||
|
||||
auto step = m_steps[currentStep];
|
||||
if(step->successful())
|
||||
{
|
||||
// end?
|
||||
if(currentStep == m_steps.size() - 1)
|
||||
{
|
||||
finalizeSteps(true, QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStep ++;
|
||||
step = m_steps[currentStep];
|
||||
step->start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finalizeSteps(false, step->failReason());
|
||||
}
|
||||
auto step = m_steps[currentStep];
|
||||
if(step->wasSuccessful())
|
||||
{
|
||||
// end?
|
||||
if(currentStep == m_steps.size() - 1)
|
||||
{
|
||||
finalizeSteps(true, QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
currentStep ++;
|
||||
step = m_steps[currentStep];
|
||||
step->start();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finalizeSteps(false, step->failReason());
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchTask::finalizeSteps(bool successful, const QString& error)
|
||||
{
|
||||
for(auto step = currentStep; step >= 0; step--)
|
||||
{
|
||||
m_steps[step]->finalize();
|
||||
}
|
||||
if(successful)
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitFailed(error);
|
||||
}
|
||||
for(auto step = currentStep; step >= 0; step--)
|
||||
{
|
||||
m_steps[step]->finalize();
|
||||
}
|
||||
if(successful)
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitFailed(error);
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchTask::onProgressReportingRequested()
|
||||
{
|
||||
state = LaunchTask::Waiting;
|
||||
emit requestProgress(m_steps[currentStep].get());
|
||||
state = LaunchTask::Waiting;
|
||||
emit requestProgress(m_steps[currentStep].get());
|
||||
}
|
||||
|
||||
void LaunchTask::setCensorFilter(QMap<QString, QString> filter)
|
||||
{
|
||||
m_censorFilter = filter;
|
||||
m_censorFilter = filter;
|
||||
}
|
||||
|
||||
QString LaunchTask::censorPrivateInfo(QString in)
|
||||
{
|
||||
auto iter = m_censorFilter.begin();
|
||||
while (iter != m_censorFilter.end())
|
||||
{
|
||||
in.replace(iter.key(), iter.value());
|
||||
iter++;
|
||||
}
|
||||
return in;
|
||||
auto iter = m_censorFilter.begin();
|
||||
while (iter != m_censorFilter.end())
|
||||
{
|
||||
in.replace(iter.key(), iter.value());
|
||||
iter++;
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
void LaunchTask::proceed()
|
||||
{
|
||||
if(state != LaunchTask::Waiting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_steps[currentStep]->proceed();
|
||||
if(state != LaunchTask::Waiting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_steps[currentStep]->proceed();
|
||||
}
|
||||
|
||||
bool LaunchTask::canAbort() const
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case LaunchTask::Aborted:
|
||||
case LaunchTask::Failed:
|
||||
case LaunchTask::Finished:
|
||||
return false;
|
||||
case LaunchTask::NotStarted:
|
||||
return true;
|
||||
case LaunchTask::Running:
|
||||
case LaunchTask::Waiting:
|
||||
{
|
||||
auto step = m_steps[currentStep];
|
||||
return step->canAbort();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
switch(state)
|
||||
{
|
||||
case LaunchTask::Aborted:
|
||||
case LaunchTask::Failed:
|
||||
case LaunchTask::Finished:
|
||||
return false;
|
||||
case LaunchTask::NotStarted:
|
||||
return true;
|
||||
case LaunchTask::Running:
|
||||
case LaunchTask::Waiting:
|
||||
{
|
||||
auto step = m_steps[currentStep];
|
||||
return step->canAbort();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LaunchTask::abort()
|
||||
{
|
||||
switch(state)
|
||||
{
|
||||
case LaunchTask::Aborted:
|
||||
case LaunchTask::Failed:
|
||||
case LaunchTask::Finished:
|
||||
return true;
|
||||
case LaunchTask::NotStarted:
|
||||
{
|
||||
state = LaunchTask::Aborted;
|
||||
emitFailed("Aborted");
|
||||
return true;
|
||||
}
|
||||
case LaunchTask::Running:
|
||||
case LaunchTask::Waiting:
|
||||
{
|
||||
auto step = m_steps[currentStep];
|
||||
if(!step->canAbort())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(step->abort())
|
||||
{
|
||||
state = LaunchTask::Aborted;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
switch(state)
|
||||
{
|
||||
case LaunchTask::Aborted:
|
||||
case LaunchTask::Failed:
|
||||
case LaunchTask::Finished:
|
||||
return true;
|
||||
case LaunchTask::NotStarted:
|
||||
{
|
||||
state = LaunchTask::Aborted;
|
||||
emitFailed("Aborted");
|
||||
return true;
|
||||
}
|
||||
case LaunchTask::Running:
|
||||
case LaunchTask::Waiting:
|
||||
{
|
||||
auto step = m_steps[currentStep];
|
||||
if(!step->canAbort())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(step->abort())
|
||||
{
|
||||
state = LaunchTask::Aborted;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
|
||||
{
|
||||
if(!m_logModel)
|
||||
{
|
||||
m_logModel.reset(new LogModel());
|
||||
m_logModel->setMaxLines(m_instance->getConsoleMaxLines());
|
||||
m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow());
|
||||
// FIXME: should this really be here?
|
||||
m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
|
||||
"You may have to fix your mods because the game is still logging to files and"
|
||||
" likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines()));
|
||||
}
|
||||
return m_logModel;
|
||||
if(!m_logModel)
|
||||
{
|
||||
m_logModel.reset(new LogModel());
|
||||
m_logModel->setMaxLines(m_instance->getConsoleMaxLines());
|
||||
m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow());
|
||||
// FIXME: should this really be here?
|
||||
m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
|
||||
"You may have to fix your mods because the game is still logging to files and"
|
||||
" likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines()));
|
||||
}
|
||||
return m_logModel;
|
||||
}
|
||||
|
||||
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
|
||||
{
|
||||
for (auto & line: lines)
|
||||
{
|
||||
onLogLine(line, defaultLevel);
|
||||
}
|
||||
for (auto & line: lines)
|
||||
{
|
||||
onLogLine(line, defaultLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
|
||||
{
|
||||
// if the launcher part set a log level, use it
|
||||
auto innerLevel = MessageLevel::fromLine(line);
|
||||
if(innerLevel != MessageLevel::Unknown)
|
||||
{
|
||||
level = innerLevel;
|
||||
}
|
||||
// if the launcher part set a log level, use it
|
||||
auto innerLevel = MessageLevel::fromLine(line);
|
||||
if(innerLevel != MessageLevel::Unknown)
|
||||
{
|
||||
level = innerLevel;
|
||||
}
|
||||
|
||||
// If the level is still undetermined, guess level
|
||||
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
|
||||
{
|
||||
level = m_instance->guessLevel(line, level);
|
||||
}
|
||||
// If the level is still undetermined, guess level
|
||||
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
|
||||
{
|
||||
level = m_instance->guessLevel(line, level);
|
||||
}
|
||||
|
||||
// censor private user info
|
||||
line = censorPrivateInfo(line);
|
||||
// censor private user info
|
||||
line = censorPrivateInfo(line);
|
||||
|
||||
auto &model = *getLogModel();
|
||||
model.append(level, line);
|
||||
auto &model = *getLogModel();
|
||||
model.append(level, line);
|
||||
}
|
||||
|
||||
void LaunchTask::emitSucceeded()
|
||||
{
|
||||
m_instance->setRunning(false);
|
||||
Task::emitSucceeded();
|
||||
m_instance->setRunning(false);
|
||||
Task::emitSucceeded();
|
||||
}
|
||||
|
||||
void LaunchTask::emitFailed(QString reason)
|
||||
{
|
||||
m_instance->setRunning(false);
|
||||
m_instance->setCrashed(true);
|
||||
Task::emitFailed(reason);
|
||||
m_instance->setRunning(false);
|
||||
m_instance->setCrashed(true);
|
||||
Task::emitFailed(reason);
|
||||
}
|
||||
|
||||
QString LaunchTask::substituteVariables(const QString &cmd) const
|
||||
{
|
||||
QString out = cmd;
|
||||
auto variables = m_instance->getVariables();
|
||||
for (auto it = variables.begin(); it != variables.end(); ++it)
|
||||
{
|
||||
out.replace("$" + it.key(), it.value());
|
||||
}
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
for (auto var : env.keys())
|
||||
{
|
||||
out.replace("$" + var, env.value(var));
|
||||
}
|
||||
return out;
|
||||
QString out = cmd;
|
||||
auto variables = m_instance->getVariables();
|
||||
for (auto it = variables.begin(); it != variables.end(); ++it)
|
||||
{
|
||||
out.replace("$" + it.key(), it.value());
|
||||
}
|
||||
auto env = QProcessEnvironment::systemEnvironment();
|
||||
for (auto var : env.keys())
|
||||
{
|
||||
out.replace("$" + var, env.value(var));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -28,98 +28,98 @@
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT LaunchTask: public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
protected:
|
||||
explicit LaunchTask(InstancePtr instance);
|
||||
void init();
|
||||
explicit LaunchTask(InstancePtr instance);
|
||||
void init();
|
||||
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
NotStarted,
|
||||
Running,
|
||||
Waiting,
|
||||
Failed,
|
||||
Aborted,
|
||||
Finished
|
||||
};
|
||||
enum State
|
||||
{
|
||||
NotStarted,
|
||||
Running,
|
||||
Waiting,
|
||||
Failed,
|
||||
Aborted,
|
||||
Finished
|
||||
};
|
||||
|
||||
public: /* methods */
|
||||
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
|
||||
virtual ~LaunchTask() {};
|
||||
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
||||
virtual ~LaunchTask() {};
|
||||
|
||||
void appendStep(std::shared_ptr<LaunchStep> step);
|
||||
void prependStep(std::shared_ptr<LaunchStep> step);
|
||||
void setCensorFilter(QMap<QString, QString> filter);
|
||||
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void setCensorFilter(QMap<QString, QString> filter);
|
||||
|
||||
InstancePtr instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
InstancePtr instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void setPid(qint64 pid)
|
||||
{
|
||||
m_pid = pid;
|
||||
}
|
||||
void setPid(qint64 pid)
|
||||
{
|
||||
m_pid = pid;
|
||||
}
|
||||
|
||||
qint64 pid()
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
qint64 pid()
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prepare the process for launch (for multi-stage launch)
|
||||
*/
|
||||
virtual void executeTask() override;
|
||||
/**
|
||||
* @brief prepare the process for launch (for multi-stage launch)
|
||||
*/
|
||||
virtual void executeTask() override;
|
||||
|
||||
/**
|
||||
* @brief launch the armed instance
|
||||
*/
|
||||
void proceed();
|
||||
/**
|
||||
* @brief launch the armed instance
|
||||
*/
|
||||
void proceed();
|
||||
|
||||
/**
|
||||
* @brief abort launch
|
||||
*/
|
||||
bool abort() override;
|
||||
/**
|
||||
* @brief abort launch
|
||||
*/
|
||||
bool abort() override;
|
||||
|
||||
bool canAbort() const override;
|
||||
bool canAbort() const override;
|
||||
|
||||
shared_qobject_ptr<LogModel> getLogModel();
|
||||
shared_qobject_ptr<LogModel> getLogModel();
|
||||
|
||||
public:
|
||||
QString substituteVariables(const QString &cmd) const;
|
||||
QString censorPrivateInfo(QString in);
|
||||
QString substituteVariables(const QString &cmd) const;
|
||||
QString censorPrivateInfo(QString in);
|
||||
|
||||
protected: /* methods */
|
||||
virtual void emitFailed(QString reason) override;
|
||||
virtual void emitSucceeded() override;
|
||||
virtual void emitFailed(QString reason) override;
|
||||
virtual void emitSucceeded() override;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief emitted when the launch preparations are done
|
||||
*/
|
||||
void readyForLaunch();
|
||||
/**
|
||||
* @brief emitted when the launch preparations are done
|
||||
*/
|
||||
void readyForLaunch();
|
||||
|
||||
void requestProgress(Task *task);
|
||||
void requestProgress(Task *task);
|
||||
|
||||
void requestLogging();
|
||||
void requestLogging();
|
||||
|
||||
public slots:
|
||||
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||
void onReadyForLaunch();
|
||||
void onStepFinished();
|
||||
void onProgressReportingRequested();
|
||||
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
|
||||
void onReadyForLaunch();
|
||||
void onStepFinished();
|
||||
void onProgressReportingRequested();
|
||||
|
||||
private: /*methods */
|
||||
void finalizeSteps(bool successful, const QString & error);
|
||||
void finalizeSteps(bool successful, const QString & error);
|
||||
|
||||
protected: /* data */
|
||||
InstancePtr m_instance;
|
||||
shared_qobject_ptr<LogModel> m_logModel;
|
||||
QList <std::shared_ptr<LaunchStep>> m_steps;
|
||||
QMap<QString, QString> m_censorFilter;
|
||||
int currentStep = -1;
|
||||
State state = NotStarted;
|
||||
qint64 m_pid = -1;
|
||||
InstancePtr m_instance;
|
||||
shared_qobject_ptr<LogModel> m_logModel;
|
||||
QList <shared_qobject_ptr<LaunchStep>> m_steps;
|
||||
QMap<QString, QString> m_censorFilter;
|
||||
int currentStep = -1;
|
||||
State state = NotStarted;
|
||||
qint64 m_pid = -1;
|
||||
};
|
||||
|
||||
@@ -2,148 +2,166 @@
|
||||
|
||||
LogModel::LogModel(QObject *parent):QAbstractListModel(parent)
|
||||
{
|
||||
m_content.resize(m_maxLines);
|
||||
m_content.resize(m_maxLines);
|
||||
}
|
||||
|
||||
int LogModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return m_numLines;
|
||||
return m_numLines;
|
||||
}
|
||||
|
||||
QVariant LogModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= m_numLines)
|
||||
return QVariant();
|
||||
if (index.row() < 0 || index.row() >= m_numLines)
|
||||
return QVariant();
|
||||
|
||||
auto row = index.row();
|
||||
auto realRow = (row + m_firstLine) % m_maxLines;
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
{
|
||||
return m_content[realRow].line;
|
||||
}
|
||||
if(role == LevelRole)
|
||||
{
|
||||
return m_content[realRow].level;
|
||||
}
|
||||
auto row = index.row();
|
||||
auto realRow = (row + m_firstLine) % m_maxLines;
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole)
|
||||
{
|
||||
return m_content[realRow].line;
|
||||
}
|
||||
if(role == LevelRole)
|
||||
{
|
||||
return m_content[realRow].level;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void LogModel::append(MessageLevel::Enum level, QString line)
|
||||
{
|
||||
if(m_suspended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int lineNum = (m_firstLine + m_numLines) % m_maxLines;
|
||||
// overflow
|
||||
if(m_numLines == m_maxLines)
|
||||
{
|
||||
if(m_stopOnOverflow)
|
||||
{
|
||||
// nothing more to do, the buffer is full
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), 0, 0);
|
||||
m_firstLine = (m_firstLine + 1) % m_maxLines;
|
||||
m_numLines --;
|
||||
endRemoveRows();
|
||||
}
|
||||
else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow)
|
||||
{
|
||||
level = MessageLevel::Fatal;
|
||||
line = m_overflowMessage;
|
||||
}
|
||||
beginInsertRows(QModelIndex(), m_numLines, m_numLines);
|
||||
m_numLines ++;
|
||||
m_content[lineNum].level = level;
|
||||
m_content[lineNum].line = line;
|
||||
endInsertRows();
|
||||
if(m_suspended)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int lineNum = (m_firstLine + m_numLines) % m_maxLines;
|
||||
// overflow
|
||||
if(m_numLines == m_maxLines)
|
||||
{
|
||||
if(m_stopOnOverflow)
|
||||
{
|
||||
// nothing more to do, the buffer is full
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), 0, 0);
|
||||
m_firstLine = (m_firstLine + 1) % m_maxLines;
|
||||
m_numLines --;
|
||||
endRemoveRows();
|
||||
}
|
||||
else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow)
|
||||
{
|
||||
level = MessageLevel::Fatal;
|
||||
line = m_overflowMessage;
|
||||
}
|
||||
beginInsertRows(QModelIndex(), m_numLines, m_numLines);
|
||||
m_numLines ++;
|
||||
m_content[lineNum].level = level;
|
||||
m_content[lineNum].line = line;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void LogModel::suspend(bool suspend)
|
||||
{
|
||||
m_suspended = suspend;
|
||||
m_suspended = suspend;
|
||||
}
|
||||
|
||||
bool LogModel::suspended()
|
||||
{
|
||||
return m_suspended;
|
||||
}
|
||||
|
||||
void LogModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
m_firstLine = 0;
|
||||
m_numLines = 0;
|
||||
endResetModel();
|
||||
beginResetModel();
|
||||
m_firstLine = 0;
|
||||
m_numLines = 0;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QString LogModel::toPlainText()
|
||||
{
|
||||
QString out;
|
||||
out.reserve(m_numLines * 80);
|
||||
for(int i = 0; i < m_numLines; i++)
|
||||
{
|
||||
QString & line = m_content[(m_firstLine + i) % m_maxLines].line;
|
||||
out.append(line + '\n');
|
||||
}
|
||||
out.squeeze();
|
||||
return out;
|
||||
QString out;
|
||||
out.reserve(m_numLines * 80);
|
||||
for(int i = 0; i < m_numLines; i++)
|
||||
{
|
||||
QString & line = m_content[(m_firstLine + i) % m_maxLines].line;
|
||||
out.append(line + '\n');
|
||||
}
|
||||
out.squeeze();
|
||||
return out;
|
||||
}
|
||||
|
||||
void LogModel::setMaxLines(int maxLines)
|
||||
{
|
||||
// no-op
|
||||
if(maxLines == m_maxLines)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// if it all still fits in the buffer, just resize it
|
||||
if(m_firstLine + m_numLines < m_maxLines)
|
||||
{
|
||||
m_maxLines = maxLines;
|
||||
m_content.resize(maxLines);
|
||||
return;
|
||||
}
|
||||
// otherwise, we need to reorganize the data because it crosses the wrap boundary
|
||||
QVector<entry> newContent;
|
||||
newContent.resize(maxLines);
|
||||
if(m_numLines <= maxLines)
|
||||
{
|
||||
// if it all fits in the new buffer, just copy it over
|
||||
for(int i = 0; i < m_numLines; i++)
|
||||
{
|
||||
newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
|
||||
}
|
||||
m_content.swap(newContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if it doesn't fit, part of the data needs to be thrown away (the oldest log messages)
|
||||
int lead = m_numLines - maxLines;
|
||||
beginRemoveRows(QModelIndex(), 0, lead - 1);
|
||||
for(int i = 0; i < maxLines; i++)
|
||||
{
|
||||
newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
|
||||
}
|
||||
m_numLines = m_maxLines;
|
||||
m_content.swap(newContent);
|
||||
endRemoveRows();
|
||||
}
|
||||
m_firstLine = 0;
|
||||
m_maxLines = maxLines;
|
||||
// no-op
|
||||
if(maxLines == m_maxLines)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// if it all still fits in the buffer, just resize it
|
||||
if(m_firstLine + m_numLines < m_maxLines)
|
||||
{
|
||||
m_maxLines = maxLines;
|
||||
m_content.resize(maxLines);
|
||||
return;
|
||||
}
|
||||
// otherwise, we need to reorganize the data because it crosses the wrap boundary
|
||||
QVector<entry> newContent;
|
||||
newContent.resize(maxLines);
|
||||
if(m_numLines <= maxLines)
|
||||
{
|
||||
// if it all fits in the new buffer, just copy it over
|
||||
for(int i = 0; i < m_numLines; i++)
|
||||
{
|
||||
newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
|
||||
}
|
||||
m_content.swap(newContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if it doesn't fit, part of the data needs to be thrown away (the oldest log messages)
|
||||
int lead = m_numLines - maxLines;
|
||||
beginRemoveRows(QModelIndex(), 0, lead - 1);
|
||||
for(int i = 0; i < maxLines; i++)
|
||||
{
|
||||
newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
|
||||
}
|
||||
m_numLines = m_maxLines;
|
||||
m_content.swap(newContent);
|
||||
endRemoveRows();
|
||||
}
|
||||
m_firstLine = 0;
|
||||
m_maxLines = maxLines;
|
||||
}
|
||||
|
||||
int LogModel::getMaxLines()
|
||||
{
|
||||
return m_maxLines;
|
||||
return m_maxLines;
|
||||
}
|
||||
|
||||
void LogModel::setStopOnOverflow(bool stop)
|
||||
{
|
||||
m_stopOnOverflow = stop;
|
||||
m_stopOnOverflow = stop;
|
||||
}
|
||||
|
||||
void LogModel::setOverflowMessage(const QString& overflowMessage)
|
||||
{
|
||||
m_overflowMessage = overflowMessage;
|
||||
m_overflowMessage = overflowMessage;
|
||||
}
|
||||
|
||||
void LogModel::setLineWrap(bool state)
|
||||
{
|
||||
if(m_lineWrap != state)
|
||||
{
|
||||
m_lineWrap = state;
|
||||
}
|
||||
}
|
||||
|
||||
bool LogModel::wrapLines() const
|
||||
{
|
||||
return m_lineWrap;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user