mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-16 08:57:15 +00:00
Compare commits
631 Commits
0.4.4
...
0.5.2-fina
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b85d7c0de | ||
|
|
1eb6e77f75 | ||
|
|
f49f8b875a | ||
|
|
23d2a99619 | ||
|
|
170bd677fd | ||
|
|
84e23e2e7a | ||
|
|
aafac3934b | ||
|
|
17fac2e0df | ||
|
|
8bbaab334c | ||
|
|
155de307bc | ||
|
|
2e8d8b60b0 | ||
|
|
c200301673 | ||
|
|
0a6af3cff3 | ||
|
|
c7f89ec6b5 | ||
|
|
cf43abc87e | ||
|
|
c421134d49 | ||
|
|
782384f185 | ||
|
|
ceb5fc6d75 | ||
|
|
201d4ac317 | ||
|
|
85320777c6 | ||
|
|
ba38991c13 | ||
|
|
98e17998fe | ||
|
|
944ff256b2 | ||
|
|
71584fb8cc | ||
|
|
b2dbaaa9e2 | ||
|
|
0a89b04afd | ||
|
|
ffa8792c13 | ||
|
|
fbcbddd4d0 | ||
|
|
a6ef0059cc | ||
|
|
3e81e2cb5b | ||
|
|
858b490c74 | ||
|
|
12c3683ec0 | ||
|
|
f530aae9d3 | ||
|
|
5ea224f8f5 | ||
|
|
e033cf8974 | ||
|
|
8e7330ae54 | ||
|
|
ccb524ed9f | ||
|
|
cff34a14dc | ||
|
|
8421ef622d | ||
|
|
c4ec6bc0f5 | ||
|
|
705a658fef | ||
|
|
6f17183bf0 | ||
|
|
0249bd9eea | ||
|
|
e1bd1c6145 | ||
|
|
4c0db2b99d | ||
|
|
9ca9addad3 | ||
|
|
64723f68e3 | ||
|
|
a666dc0a1a | ||
|
|
722896d41f | ||
|
|
46c5368a78 | ||
|
|
476d641841 | ||
|
|
374710a87b | ||
|
|
2344ee2dcd | ||
|
|
481ecb178c | ||
|
|
123b59e63f | ||
|
|
92bb001787 | ||
|
|
03d2858c62 | ||
|
|
a6882787b0 | ||
|
|
2517d2c84d | ||
|
|
035bdc7576 | ||
|
|
4ca6878743 | ||
|
|
ef73a2bd32 | ||
|
|
5994c47d7c | ||
|
|
66ffab71ae | ||
|
|
ce70407363 | ||
|
|
dccf9d7219 | ||
|
|
dd0c815396 | ||
|
|
55541c387c | ||
|
|
d5fdc23eb2 | ||
|
|
a5fb931e8e | ||
|
|
486d653586 | ||
|
|
121e2fd46c | ||
|
|
295c6e808a | ||
|
|
7a14b63957 | ||
|
|
44805145dc | ||
|
|
00c4aebeaa | ||
|
|
ee6f2f0a8e | ||
|
|
95f961fb61 | ||
|
|
ad25c89ac4 | ||
|
|
905bc2e440 | ||
|
|
2f8c752d1f | ||
|
|
2ec15c32e4 | ||
|
|
69be23c5f6 | ||
|
|
e974950d48 | ||
|
|
9efdd7232c | ||
|
|
9b41986634 | ||
|
|
b09fad9cbf | ||
|
|
fd34ca5a0f | ||
|
|
9cf8b42d89 | ||
|
|
12f6534e77 | ||
|
|
3769897be1 | ||
|
|
590ff82fd1 | ||
|
|
f9d94a45ee | ||
|
|
27e26a656b | ||
|
|
b6f133f579 | ||
|
|
01649f761d | ||
|
|
dae3b06885 | ||
|
|
07589b5114 | ||
|
|
7cff5ba2e1 | ||
|
|
1276ecdbb7 | ||
|
|
8b952b3870 | ||
|
|
37cc59c04d | ||
|
|
bc753859b5 | ||
|
|
13b575f7a9 | ||
|
|
495e752f8a | ||
|
|
87dd951505 | ||
|
|
3780a25d27 | ||
|
|
6ebf6e7785 | ||
|
|
f4de049b13 | ||
|
|
f0b71f989e | ||
|
|
ac66af6c13 | ||
|
|
85b64ad767 | ||
|
|
3a4304d89d | ||
|
|
a9c0d812a6 | ||
|
|
b6b2350e02 | ||
|
|
2e0a45cc2f | ||
|
|
fe68d59460 | ||
|
|
4b03dfcbd7 | ||
|
|
a36c962a31 | ||
|
|
e9949e3a54 | ||
|
|
a717864013 | ||
|
|
54e0b9bc9b | ||
|
|
249e5c13d7 | ||
|
|
412855ae3d | ||
|
|
deabfa78f8 | ||
|
|
2b9017a69c | ||
|
|
172ff47a65 | ||
|
|
b5aaf88f12 | ||
|
|
8731318fef | ||
|
|
dd0e996081 | ||
|
|
3d94fb8d24 | ||
|
|
2c2c1b0a17 | ||
|
|
0493170936 | ||
|
|
2597bde4f9 | ||
|
|
cee53f7f3c | ||
|
|
1b4851a941 | ||
|
|
d66fdcd4cc | ||
|
|
bbe139dce5 | ||
|
|
872cfe036d | ||
|
|
f07496ac6d | ||
|
|
6e80f03409 | ||
|
|
69f3ab019d | ||
|
|
eb747e08b7 | ||
|
|
67eca08b22 | ||
|
|
9aff21c181 | ||
|
|
ec05ca2775 | ||
|
|
042f3ef55c | ||
|
|
2f0441b3c1 | ||
|
|
55544893a3 | ||
|
|
e2f3652a0f | ||
|
|
c60db13af7 | ||
|
|
fc198dd308 | ||
|
|
74b4343c43 | ||
|
|
877d1020db | ||
|
|
bc6d1b5304 | ||
|
|
c44d41ee9b | ||
|
|
cf0694a0cb | ||
|
|
b76d4573cd | ||
|
|
6ec2652b45 | ||
|
|
eec87db86a | ||
|
|
42a98c3661 | ||
|
|
1f2bed2ef1 | ||
|
|
57c84ec2b1 | ||
|
|
2164dc13f4 | ||
|
|
f626fd02c7 | ||
|
|
7da70a75eb | ||
|
|
969418f01f | ||
|
|
6ecfe8546f | ||
|
|
8b74f6dcf0 | ||
|
|
d4109938fe | ||
|
|
be89024d4e | ||
|
|
56394f93e5 | ||
|
|
e07456f4bf | ||
|
|
54e5a98da0 | ||
|
|
a1abbd9e05 | ||
|
|
a750f6e63c | ||
|
|
4440f68e59 | ||
|
|
67b22c8105 | ||
|
|
12413f722d | ||
|
|
5aff10d51d | ||
|
|
377316999e | ||
|
|
f9791a5cc8 | ||
|
|
603b0408ab | ||
|
|
ecd5d3a2db | ||
|
|
898e3cd4e7 | ||
|
|
e1a530f84d | ||
|
|
c50b3cdeec | ||
|
|
b0bfffcd90 | ||
|
|
80b28e7d49 | ||
|
|
16650790d0 | ||
|
|
e32d7238c9 | ||
|
|
771dd6f9ab | ||
|
|
e8ba5dafc6 | ||
|
|
ed3884fd38 | ||
|
|
1be7d57332 | ||
|
|
aa4842a91d | ||
|
|
b6d455a02b | ||
|
|
47e37635f5 | ||
|
|
fcd4a482f7 | ||
|
|
00e5968bd2 | ||
|
|
5ae3b2c114 | ||
|
|
4392abfb8d | ||
|
|
72c92893a5 | ||
|
|
0890a81695 | ||
|
|
432ec74174 | ||
|
|
b795ad5209 | ||
|
|
c44e85c765 | ||
|
|
b29ef49415 | ||
|
|
f184eff71a | ||
|
|
b3e3a6fc88 | ||
|
|
0ff6f3a036 | ||
|
|
bf0f27bd60 | ||
|
|
dd5b07e38d | ||
|
|
ea685651a1 | ||
|
|
53b4bd019f | ||
|
|
f032e32133 | ||
|
|
d587720010 | ||
|
|
2929ca7413 | ||
|
|
ff8f495d44 | ||
|
|
f56983e5ca | ||
|
|
ec6204e447 | ||
|
|
9e3534f2f6 | ||
|
|
b7d8e512f4 | ||
|
|
fb9dfcb951 | ||
|
|
010e07eb45 | ||
|
|
576d808d71 | ||
|
|
f63d1bc99c | ||
|
|
02c1df2c3c | ||
|
|
1854e05e1b | ||
|
|
0c06ab364c | ||
|
|
07608ebc4c | ||
|
|
36f3813ce5 | ||
|
|
f96d20b6f7 | ||
|
|
ead4c17d0a | ||
|
|
d4eacb56b3 | ||
|
|
3d8728f52f | ||
|
|
2e4fa7ec13 | ||
|
|
fd2103d6ee | ||
|
|
94d4684809 | ||
|
|
b54839b897 | ||
|
|
80b81c2c1e | ||
|
|
f53cd55fbb | ||
|
|
a3cd3d5ff1 | ||
|
|
1a9793197f | ||
|
|
9497b7e96c | ||
|
|
a0b47aee5b | ||
|
|
17ad1e64f8 | ||
|
|
71e4b147ec | ||
|
|
f6b2ccb110 | ||
|
|
c943019ab5 | ||
|
|
fc43fd1105 | ||
|
|
401d5b698f | ||
|
|
1a0bbdd9ac | ||
|
|
495d320ce2 | ||
|
|
5e737f42bf | ||
|
|
6e6e2bf262 | ||
|
|
163a3095b1 | ||
|
|
a20e2590da | ||
|
|
1978078662 | ||
|
|
ea08ede4c3 | ||
|
|
b7f75637fa | ||
|
|
4ee1900201 | ||
|
|
ab67d763f4 | ||
|
|
7ca9f92343 | ||
|
|
cbd4b88e91 | ||
|
|
0958bb2fcc | ||
|
|
fbec48080b | ||
|
|
33b6222f9f | ||
|
|
5ecaed21b5 | ||
|
|
9fd66b3bb1 | ||
|
|
47ea2a71c0 | ||
|
|
2c2b960ab4 | ||
|
|
ce326ccdff | ||
|
|
1ed86bfe1a | ||
|
|
781e53cccb | ||
|
|
25991c36af | ||
|
|
5ccfbba435 | ||
|
|
1f0e76a3c1 | ||
|
|
d8b1ae38fb | ||
|
|
59e6b4ed55 | ||
|
|
5ff9f90ce9 | ||
|
|
34bf4ccdc7 | ||
|
|
aa8103adf2 | ||
|
|
b300c4956c | ||
|
|
4d0caf6254 | ||
|
|
d1e344f28f | ||
|
|
e0a9970d59 | ||
|
|
6f92ca843e | ||
|
|
8cf23bd5aa | ||
|
|
c6afa7d73e | ||
|
|
dcb4e0fa6f | ||
|
|
15aaded80b | ||
|
|
b9b5a82c2e | ||
|
|
da2af5e449 | ||
|
|
1363b1d364 | ||
|
|
a008efd24e | ||
|
|
5f57df8110 | ||
|
|
b437988d7b | ||
|
|
07449e514a | ||
|
|
bd2843952a | ||
|
|
5402acb3c6 | ||
|
|
e8063d193d | ||
|
|
7670d72bd9 | ||
|
|
c64a7940c1 | ||
|
|
243c5d1cfb | ||
|
|
478ff11485 | ||
|
|
2db4a595dd | ||
|
|
2da3162206 | ||
|
|
a5b8f22eab | ||
|
|
b5902b739e | ||
|
|
11afc61426 | ||
|
|
384c03c9c5 | ||
|
|
21490a42bd | ||
|
|
d855f4905d | ||
|
|
12c7b11fe6 | ||
|
|
055774dd58 | ||
|
|
fdbe431d6b | ||
|
|
9c01b2294f | ||
|
|
895d8ab469 | ||
|
|
8a4fd8c468 | ||
|
|
61ad480588 | ||
|
|
79ae4ef2f7 | ||
|
|
125abf5027 | ||
|
|
9ad99ac481 | ||
|
|
69989ab54e | ||
|
|
7ef1f88de7 | ||
|
|
432e812da1 | ||
|
|
d5aee5fd23 | ||
|
|
457dd2e94e | ||
|
|
44db72ead5 | ||
|
|
4fbcb3efb9 | ||
|
|
439c6b43a3 | ||
|
|
db926a546e | ||
|
|
4a900a58d4 | ||
|
|
f93f867c3d | ||
|
|
7459eb627c | ||
|
|
ae4216de61 | ||
|
|
ac8ff88061 | ||
|
|
477a1a88c6 | ||
|
|
cf0308c970 | ||
|
|
e2fd299fc5 | ||
|
|
e993b1152d | ||
|
|
877240524d | ||
|
|
b7ff8a4c1c | ||
|
|
22c0d5cf46 | ||
|
|
12b14c3400 | ||
|
|
00994a425e | ||
|
|
ccb5fc6f4a | ||
|
|
1cbe543b39 | ||
|
|
260a2cea59 | ||
|
|
143e24fa04 | ||
|
|
33c3850b40 | ||
|
|
271ad9e4fd | ||
|
|
cca6700134 | ||
|
|
0af04dc060 | ||
|
|
e4df8165f7 | ||
|
|
1ab26ef2ad | ||
|
|
5d5bee4992 | ||
|
|
e60a652b78 | ||
|
|
9ba1cd15e7 | ||
|
|
b107617112 | ||
|
|
0a187d0ad3 | ||
|
|
8d3f13c447 | ||
|
|
dd8eacee1b | ||
|
|
e38cc1d480 | ||
|
|
cfd5976471 | ||
|
|
8ef07ec634 | ||
|
|
2315f463a8 | ||
|
|
38901ed21d | ||
|
|
ec3472f21d | ||
|
|
bd96a25f7a | ||
|
|
877fc94f50 | ||
|
|
db5816b0a2 | ||
|
|
a1fd50e920 | ||
|
|
51070a13f7 | ||
|
|
36dbf1fb43 | ||
|
|
1ca9fc8961 | ||
|
|
b8cdcdb96b | ||
|
|
38693e1d6c | ||
|
|
40b233448c | ||
|
|
7d8c71aad8 | ||
|
|
498dc8fc03 | ||
|
|
c3480d6fe4 | ||
|
|
b5d5490714 | ||
|
|
83434a9be5 | ||
|
|
583e5946f4 | ||
|
|
16df6c16f3 | ||
|
|
6148023ad6 | ||
|
|
6496c65285 | ||
|
|
db5e55e026 | ||
|
|
23d0bd8edd | ||
|
|
cd108fd029 | ||
|
|
151a0ca11e | ||
|
|
bc917668ff | ||
|
|
961c1c61b8 | ||
|
|
0d15247247 | ||
|
|
b6ec2ac4b0 | ||
|
|
c5bb33c716 | ||
|
|
40ed2654c7 | ||
|
|
875c707358 | ||
|
|
e5f7676622 | ||
|
|
cc4d0a0a8e | ||
|
|
d0e88011dc | ||
|
|
6858f1dd62 | ||
|
|
9681f724e5 | ||
|
|
5bc29b06a9 | ||
|
|
96fdaebb5c | ||
|
|
4e3af265da | ||
|
|
d7b3887fe1 | ||
|
|
0adb572a07 | ||
|
|
8ed10c5b81 | ||
|
|
d8caab515a | ||
|
|
6310f6569c | ||
|
|
2fc18921b0 | ||
|
|
61c5a67777 | ||
|
|
8e7caf4e25 | ||
|
|
5dd48e89f5 | ||
|
|
5133b0f34f | ||
|
|
5f41886d76 | ||
|
|
7f1320390c | ||
|
|
526a511f45 | ||
|
|
5628d3d379 | ||
|
|
f86a39c21c | ||
|
|
34ddfc7ecc | ||
|
|
d14a61b0df | ||
|
|
bbba63eca5 | ||
|
|
d403d12d6a | ||
|
|
b343434f99 | ||
|
|
f723721bd0 | ||
|
|
b427a652ad | ||
|
|
9684d3b0a0 | ||
|
|
1feb4bb387 | ||
|
|
dd97ea8029 | ||
|
|
88f5c8d347 | ||
|
|
15b7c3039a | ||
|
|
22c5ced5dc | ||
|
|
64b70acac1 | ||
|
|
82e05661d2 | ||
|
|
166813cb91 | ||
|
|
38e42ad794 | ||
|
|
6d7bff2476 | ||
|
|
977e11ef8d | ||
|
|
634bdcdbcb | ||
|
|
1e51b62c88 | ||
|
|
24db645167 | ||
|
|
dde35a0eb8 | ||
|
|
57b75dfcf7 | ||
|
|
06a67fbd38 | ||
|
|
3a8b238052 | ||
|
|
161dc66c2c | ||
|
|
678da0b639 | ||
|
|
db69a3dacd | ||
|
|
47f919173e | ||
|
|
c1f7dda8fe | ||
|
|
678c4793f9 | ||
|
|
405cea1778 | ||
|
|
96c497f654 | ||
|
|
10b3906b53 | ||
|
|
6fd18a5cce | ||
|
|
9920062003 | ||
|
|
99f248ecd4 | ||
|
|
b9e06b5da0 | ||
|
|
ff64b6cf1d | ||
|
|
84757f485b | ||
|
|
b7f8241968 | ||
|
|
a98e1df10c | ||
|
|
f9e186ab70 | ||
|
|
50a4a1e19e | ||
|
|
2f087b55b9 | ||
|
|
2dcedcfde3 | ||
|
|
c1c23e47a7 | ||
|
|
8fb5d4add3 | ||
|
|
185ff238c2 | ||
|
|
09673cc16e | ||
|
|
dfb0a3b724 | ||
|
|
ce99fabe13 | ||
|
|
0e0ddf5494 | ||
|
|
12b9a90c4c | ||
|
|
8715746774 | ||
|
|
cfdfd0e811 | ||
|
|
81b37dae18 | ||
|
|
29ce36c7bc | ||
|
|
22a0294a33 | ||
|
|
5334d88c1d | ||
|
|
06080108f3 | ||
|
|
08898c7c63 | ||
|
|
1bc2fbef11 | ||
|
|
a296ec914b | ||
|
|
53d8c9169d | ||
|
|
4c11ce8063 | ||
|
|
cff2e4823a | ||
|
|
d0b31da4b5 | ||
|
|
2ad9e6393f | ||
|
|
743af4769e | ||
|
|
6ab6a450f6 | ||
|
|
3ed467e1fa | ||
|
|
44d76f5d82 | ||
|
|
ff715f7785 | ||
|
|
43c777f386 | ||
|
|
a39fb1ef17 | ||
|
|
c75cac684e | ||
|
|
f2026df597 | ||
|
|
416e08f741 | ||
|
|
5bbe1c7132 | ||
|
|
bd1a28d863 | ||
|
|
ffcb5ab1ef | ||
|
|
88f975eff7 | ||
|
|
11c376f6f1 | ||
|
|
757b4e260b | ||
|
|
2a4647125d | ||
|
|
9598f80335 | ||
|
|
34a5e59007 | ||
|
|
1271188019 | ||
|
|
4c6edc9fd4 | ||
|
|
49d3705d16 | ||
|
|
c09dc85090 | ||
|
|
c10a4a54d9 | ||
|
|
1b884d0a9d | ||
|
|
8e9d5f56b5 | ||
|
|
5779ffd664 | ||
|
|
4fc4a17256 | ||
|
|
bb01c91469 | ||
|
|
fb3c9efc8a | ||
|
|
55f9117ce3 | ||
|
|
994c815bb9 | ||
|
|
32f45578fd | ||
|
|
2af03ba0d9 | ||
|
|
aea51a0876 | ||
|
|
75dfbc61fc | ||
|
|
f8650e3965 | ||
|
|
84549ed807 | ||
|
|
d5c79db12c | ||
|
|
f623dc54ef | ||
|
|
dc279fbfdc | ||
|
|
8fa58dc244 | ||
|
|
07bebddac9 | ||
|
|
4f417d527e | ||
|
|
c7c81463fd | ||
|
|
6cfac115b1 | ||
|
|
3507ccaf50 | ||
|
|
28aa8f342e | ||
|
|
4d8f068f9c | ||
|
|
1f9dd45e49 | ||
|
|
f061bf7a27 | ||
|
|
d8ea3501eb | ||
|
|
9df2f1fa5c | ||
|
|
fe540e5dda | ||
|
|
c7398dfdc5 | ||
|
|
0220fe4f9d | ||
|
|
58840ac10c | ||
|
|
3d3725f088 | ||
|
|
47bbc349eb | ||
|
|
c8687a8d05 | ||
|
|
234f57b8e6 | ||
|
|
d4d8cb4891 | ||
|
|
d1ba972c59 | ||
|
|
db877ba121 | ||
|
|
4730f54df7 | ||
|
|
7a71ecd8af | ||
|
|
4e94de413b | ||
|
|
141e0a02a0 | ||
|
|
473971b6e7 | ||
|
|
b47e196b32 | ||
|
|
cd9d37aac4 | ||
|
|
28a39ef7ac | ||
|
|
d313e9ab09 | ||
|
|
382ae78a0b | ||
|
|
aa70ed2244 | ||
|
|
154d19bb74 | ||
|
|
c088d3bef0 | ||
|
|
6775e3e72b | ||
|
|
8b4e22bbb8 | ||
|
|
c7b39fe116 | ||
|
|
865b200571 | ||
|
|
dc84ac3682 | ||
|
|
695bfd5f7c | ||
|
|
5ff2681da6 | ||
|
|
5359f4499a | ||
|
|
04b45f3629 | ||
|
|
9249768db5 | ||
|
|
6f3aa65bd6 | ||
|
|
e508728246 | ||
|
|
360ec557b2 | ||
|
|
7334b8e520 | ||
|
|
791221e923 | ||
|
|
593111b144 | ||
|
|
3b6574181e | ||
|
|
eae544f0eb | ||
|
|
2eb3ec39bf | ||
|
|
a27c341781 | ||
|
|
02f82e9694 | ||
|
|
405833bbbe | ||
|
|
8be865fb2a | ||
|
|
568a79e7b1 | ||
|
|
06c9a64a87 | ||
|
|
ceec70e014 | ||
|
|
ef34cafe17 | ||
|
|
93b247592d | ||
|
|
b8a8b09796 | ||
|
|
a53f8d506e | ||
|
|
f9a17eb9de | ||
|
|
c6c5134398 | ||
|
|
49ff31f131 | ||
|
|
e25e076d2e | ||
|
|
125ddc5f93 | ||
|
|
d03dbea1b7 | ||
|
|
c6427caa9e | ||
|
|
0be0e822e4 | ||
|
|
55e5322fbe | ||
|
|
0886786bb5 | ||
|
|
1151037f96 | ||
|
|
acb3346409 | ||
|
|
85756d0e78 | ||
|
|
5c599d8658 | ||
|
|
4db31aacd6 | ||
|
|
a30a9559c7 | ||
|
|
01f44e0f39 | ||
|
|
bbcd44a657 | ||
|
|
a060d79c12 | ||
|
|
28140b1db6 | ||
|
|
5af1f0cf50 | ||
|
|
7778c84121 | ||
|
|
4ae0d8e0af | ||
|
|
9f14b319df | ||
|
|
a9af17eebb | ||
|
|
d3c2230a24 | ||
|
|
f8bd687994 | ||
|
|
fa8d3c564d | ||
|
|
80d3f734c6 | ||
|
|
9ad9826d08 | ||
|
|
6a09fd2898 |
@@ -10,7 +10,7 @@ NamespaceIndentation: None
|
||||
BreakBeforeBraces: Allman
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
ColumnLimit: 96
|
||||
ColumnLimit: 160
|
||||
MaxEmptyLinesToKeep: 1
|
||||
|
||||
Standard: Cpp11
|
||||
|
||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.pem -crlf
|
||||
**/testdata/** -text -diff
|
||||
50
.github/ISSUE_TEMPLATE.md
vendored
Normal file
50
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
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.
|
||||
2. Proof-read your ENTIRE issue report.
|
||||
- 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.
|
||||
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?!".
|
||||
5. Place all information below the ---- of lines.
|
||||
- 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
|
||||
-->
|
||||
|
||||
System Information
|
||||
-----------------------------
|
||||
MultiMC version:
|
||||
|
||||
Operating System:
|
||||
|
||||
Summary of the issue or suggestion:
|
||||
----------------------------------------------
|
||||
|
||||
|
||||
What should happen:
|
||||
------------------------------
|
||||
|
||||
|
||||
Steps to reproduce the issue (Add more if needed):
|
||||
-------------------------------------------------------------
|
||||
1.
|
||||
|
||||
2.
|
||||
|
||||
3.
|
||||
|
||||
Suspected cause:
|
||||
---------------------------
|
||||
|
||||
|
||||
Logs/Screenshots:
|
||||
----------------------------
|
||||
[//]: # (Please refer to https://github.com/MultiMC/MultiMC5/wiki/Log-Upload for instructions on how to attach your logs.)
|
||||
|
||||
|
||||
Additional Info:
|
||||
---------------------------
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "depends/libnbtplusplus"]
|
||||
path = libraries/libnbtplusplus
|
||||
url = https://github.com/MultiMC/libnbtplusplus.git
|
||||
[submodule "libraries/quazip"]
|
||||
path = libraries/quazip
|
||||
url = https://github.com/MultiMC/quazip.git
|
||||
51
.travis.yml
51
.travis.yml
@@ -1,28 +1,39 @@
|
||||
# General set up
|
||||
language: cpp
|
||||
cache: apt
|
||||
|
||||
# Build matrix set up
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
cache: apt
|
||||
before_install:
|
||||
- sudo apt-add-repository -y ppa:beineri/opt-qt521
|
||||
- sudo apt-add-repository -y ppa:kalakris/cmake
|
||||
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
- sudo apt-get update -qq
|
||||
# - 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
|
||||
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
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
- sudo apt-get install -y -qq cmake qt52base qt52svg qt52tools qt52x11extras qt52webkit
|
||||
- sudo apt-get install -y -qq g++-4.8
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi
|
||||
- source travis/prepare.sh # installs qt and cmake. need to source because some env vars are set from there
|
||||
|
||||
# Actual work
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DCMAKE_PREFIX_PATH=/opt/qt52/lib/cmake ..
|
||||
- cmake -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH ..
|
||||
script:
|
||||
- make -j4
|
||||
- make test
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "irc.esper.net#MultiMC"
|
||||
template:
|
||||
- "%{build_number} (%{branch} - %{commit} : %{author}): %{message} (%{build_url})"
|
||||
email: false
|
||||
- make -j4 && make test ARGS="-V"
|
||||
|
||||
164
BUILD.md
164
BUILD.md
@@ -2,7 +2,9 @@ Build Instructions
|
||||
==================
|
||||
|
||||
# Contents
|
||||
|
||||
* [Note](#note)
|
||||
* [Getting the source](#source)
|
||||
* [Linux](#linux)
|
||||
* [Windows](#windows)
|
||||
* [OS X](#os-x)
|
||||
@@ -14,60 +16,90 @@ That would be anything outside your home folder. Before runing `make install`, m
|
||||
you set the install path to something you have write access to. Never build this under
|
||||
an administrator/root level account. Don't use `sudo`. It won't work and it's not supposed to work.
|
||||
|
||||
|
||||
# Getting the source
|
||||
|
||||
Clone the source code using git and grab all the submodules:
|
||||
|
||||
```
|
||||
git clone git@github.com:MultiMC/MultiMC5.git
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
# Linux
|
||||
|
||||
Getting the project to build and run on Linux is easy if you use Ubuntu 13.10 (or 13.04) and Qt's IDE, Qt Creator.
|
||||
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
|
||||
|
||||
## Dependencies
|
||||
* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)")
|
||||
* A copy of the MultiMC source (clone it with git)
|
||||
* cmake
|
||||
* build-essential
|
||||
* zlib (for example, zlib1g-dev)
|
||||
* java (for example, openjdk-7-jdk)
|
||||
* GL headers (for example, libgl1-mesa-dev)
|
||||
## 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.
|
||||
* cmake 3.1 or newer
|
||||
* zlib (for example, `zlib1g-dev`)
|
||||
* java (for example, `openjdk-8-jdk`)
|
||||
* GL headers (for example, `libgl1-mesa-dev`)
|
||||
|
||||
## Getting set up
|
||||
### Building from command line
|
||||
You need a source folder, a build folder and an install folder.
|
||||
|
||||
### Installing dependencies
|
||||
Just run `sudo apt-get install <dependency>` for each dependency (other than Qt and the MultiMC source) from above.
|
||||
Let's say you want everything in `~/MultiMC/`:
|
||||
|
||||
### Installing Qt
|
||||
1. Run the Qt installer
|
||||
2. Choose a place to install Qt,
|
||||
3. Choose the components you want to install
|
||||
- You need Qt 5.1.1/gcc 64-bit ticked,
|
||||
- You need Tools/Qt Creator ticked,
|
||||
```
|
||||
# make all the folders
|
||||
mkdir ~/MultiMC && cd ~/MultiMC
|
||||
mkdir build
|
||||
mkdir install
|
||||
# clone the complete source
|
||||
git clone --recursive git@github.com:MultiMC/MultiMC5.git src
|
||||
# configure the project
|
||||
cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX=../install ../src
|
||||
# build & install (use -j with the number of cores your CPU has)
|
||||
make -j8 install
|
||||
```
|
||||
|
||||
You can use IDEs like KDevelop or QtCreator to open the CMake project if you want to work on the code.
|
||||
|
||||
### Installing Qt using the installer (optional)
|
||||
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 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,
|
||||
5. Double check the install details and then click "Install"
|
||||
4. Accept the license agreements.
|
||||
5. Double check the install details and then click "Install".
|
||||
- Installation can take a very long time, go grab a cup of tea or something and let it work.
|
||||
|
||||
### Loading the project
|
||||
1. Open Qt Creator,
|
||||
2. Choose File->Open File or Project,
|
||||
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.1.1 GCC 64bit)",
|
||||
- Hit the "Run CMake" button,
|
||||
### Loading the project in Qt Creator (optional)
|
||||
1. Open Qt Creator.
|
||||
2. Choose `File->Open File or Project`.
|
||||
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)".
|
||||
- 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.
|
||||
6. Cross your fingers and press the Run button (bottom left of Qt Creator)!
|
||||
6. Cross your fingers and press the Run button (bottom left of Qt Creator).
|
||||
- If the project builds successfully it will run and the MultiMC5 window will pop up.
|
||||
|
||||
*These build instructions worked for me (Drayshak) on a fresh Ubuntu 13.10 x64 install. If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
|
||||
**If this doesn't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
|
||||
|
||||
# Windows
|
||||
|
||||
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 VC's build tools as it uses some C++11 features that aren't implemented in it at the time of writing.
|
||||
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.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Windows")
|
||||
* OpenSSL (http://slproweb.com/products/Win32OpenSSL.html) ("Win32 OpenSSL \<version\> 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.
|
||||
* CMake (http://www.cmake.org/cmake/resources/software.html) ("Windows (Win32 Installer)")
|
||||
* A copy of the MultiMC source (clone it with git)
|
||||
* [Qt 5.4.1+ 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)
|
||||
* [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.
|
||||
|
||||
## Getting set up
|
||||
|
||||
@@ -75,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.1.1/MinGW 4.8 (32 bit) ticked,
|
||||
- You need Qt 5.4.1/MinGW 4.9 (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,
|
||||
@@ -84,8 +116,8 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
|
||||
### Installing OpenSSL
|
||||
1. Run the OpenSSL installer,
|
||||
2. It's best to choose the option to copy OpenSSL DLLs to the /bin directory
|
||||
- If you do this you'll need to add that directory (the default being C:\OpenSSL-Win32\bin) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
|
||||
2. It's best to choose the option to copy OpenSSL DLLs to the `/bin` directory
|
||||
- If you do this you'll need to add that directory (the default being `C:\OpenSSL-Win32\bin`) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
|
||||
|
||||
### Installing CMake
|
||||
1. Run the CMake installer,
|
||||
@@ -100,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.1.1 MinGW 32bit)",
|
||||
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 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.
|
||||
@@ -108,26 +140,46 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
- If the project builds successfully it will run and the MultiMC5 window will pop up,
|
||||
- Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error.
|
||||
|
||||
*These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
|
||||
**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
|
||||
### Compile from command line on Windows
|
||||
1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
|
||||
2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/MultiMC/MultiMC5.git`, and change directory to the folder you cloned to.
|
||||
3. Make a build directory, and change directory to the directory and do `cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\Path\that\makes\sense\for\you`. By default, it will install to C:\Program Files (x86), which you might not want, if you want a local installation. If you want to install it to that directory, make sure to run the command window as administrator.
|
||||
3. Do `mingw32-make -jX`, where X is the number of cores your CPU has plus one.
|
||||
4. Now to wait for it to compile. This could take some time. Hopefully it compiles properly.
|
||||
5. Run the command `mingw32-make install`, and it should install MultiMC, to whatever the `-DCMAKE_INSTALL_PREFIX` was.
|
||||
6. In most cases, whenever compiling, the OpenSSL dll's aren't put into the directory to where MultiMC installs, meaning you cannot log in. The best way to fix this is just to do `copy C:\OpenSSL-Win32\*.dll C:\Where\you\installed\MultiMC\to`. This should copy the required OpenSSL dll's to log in.
|
||||
|
||||
# OS X
|
||||
|
||||
### Install prerequisites:
|
||||
1. install homebrew
|
||||
2. brew install qt5
|
||||
3. brew tap homebrew/versions
|
||||
4. brew install gcc48
|
||||
5. brew install cmake
|
||||
* install homebrew
|
||||
* then:
|
||||
|
||||
```
|
||||
brew install qt5
|
||||
brew tap homebrew/versions
|
||||
brew install gcc48
|
||||
brew install cmake
|
||||
```
|
||||
|
||||
### Build
|
||||
1. git clone https://github.com/MultiMC/MultiMC5.git
|
||||
2. cd MultiMC5
|
||||
3. mkdir build
|
||||
4. cd build
|
||||
5. export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
|
||||
6. export CC=/usr/local/bin/gcc-4.8
|
||||
7. export CXX=/usr/local/bin/g++-4.8
|
||||
8. cmake ..
|
||||
9. make
|
||||
|
||||
*These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
|
||||
Pick an installation path - this is where the final `.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration.
|
||||
|
||||
```
|
||||
git clone https://github.com/MultiMC/MultiMC5.git
|
||||
git submodule init
|
||||
git submodule update
|
||||
cd MultiMC5
|
||||
mkdir build
|
||||
cd build
|
||||
export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
|
||||
export CC=/usr/local/bin/gcc-4.8
|
||||
export CXX=/usr/local/bin/g++-4.8
|
||||
cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/Users/YOU/some/path/that/makes/sense/
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
**These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "BuildConfig.h"
|
||||
|
||||
Config BuildConfig;
|
||||
|
||||
Config::Config()
|
||||
{
|
||||
// Version information
|
||||
VERSION_MAJOR = @MultiMC_VERSION_MAJOR@;
|
||||
VERSION_MINOR = @MultiMC_VERSION_MINOR@;
|
||||
VERSION_HOTFIX = @MultiMC_VERSION_HOTFIX@;
|
||||
VERSION_BUILD = @MultiMC_VERSION_BUILD@;
|
||||
VERSION_TYPE = "@MultiMC_VERSION_TYPE@";
|
||||
|
||||
if(VERSION_TYPE == "Release")
|
||||
versionTypeEnum = Release;
|
||||
else if(VERSION_TYPE == "ReleaseCandidate")
|
||||
versionTypeEnum = ReleaseCandidate;
|
||||
else if(VERSION_TYPE == "Development")
|
||||
versionTypeEnum = Development;
|
||||
else
|
||||
versionTypeEnum = Custom;
|
||||
VERSION_CHANNEL = "@MultiMC_VERSION_CHANNEL@";
|
||||
BUILD_PLATFORM = "@MultiMC_BUILD_PLATFORM@";
|
||||
CHANLIST_URL = "@MultiMC_CHANLIST_URL@";
|
||||
NOTIFICATION_URL = "@MultiMC_NOTIFICATION_URL@";
|
||||
FULL_VERSION_STR = "@MultiMC_VERSION_MAJOR@.@MultiMC_VERSION_MINOR@.@MultiMC_VERSION_BUILD@";
|
||||
|
||||
UPDATER_DRY_RUN = @MultiMC_UPDATER_DRY_RUN_value@;
|
||||
UPDATER_FORCE_LOCAL = @MultiMC_UPDATER_FORCE_LOCAL_value@;
|
||||
|
||||
GIT_COMMIT = "@MultiMC_GIT_COMMIT@";
|
||||
VERSION_STR = "@MultiMC_VERSION_STRING@";
|
||||
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
|
||||
}
|
||||
|
||||
QString Config::versionTypeName() const
|
||||
{
|
||||
switch (versionTypeEnum)
|
||||
{
|
||||
case Release:
|
||||
return "Stable Release";
|
||||
case ReleaseCandidate:
|
||||
return "Release Candidate";
|
||||
case Development:
|
||||
return "Development";
|
||||
case Custom:
|
||||
default:
|
||||
return "Custom";
|
||||
}
|
||||
}
|
||||
|
||||
QString Config::printableVersionString() const
|
||||
{
|
||||
QString vstr = QString("%1.%2").arg(QString::number(VERSION_MAJOR), QString::number(VERSION_MINOR));
|
||||
|
||||
if (VERSION_HOTFIX > 0) vstr += "." + QString::number(VERSION_HOTFIX);
|
||||
|
||||
// If the build is a development build or release candidate, add that info to the end.
|
||||
if (versionTypeEnum == Development) vstr += "-dev" + QString::number(VERSION_BUILD);
|
||||
else if (versionTypeEnum == ReleaseCandidate) vstr += "-rc" + QString::number(VERSION_BUILD);
|
||||
|
||||
return vstr;
|
||||
}
|
||||
895
CMakeLists.txt
895
CMakeLists.txt
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 2.8.9)
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
|
||||
if(IS_IN_SOURCE_BUILD)
|
||||
@@ -16,878 +16,79 @@ enable_testing()
|
||||
######## Set CMake options ########
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(FILES_TO_TRANSLATE )
|
||||
|
||||
######## Set module path ########
|
||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
|
||||
set(MMC_SRC "${PROJECT_SOURCE_DIR}")
|
||||
set(MMC_BIN "${PROJECT_BINARY_DIR}")
|
||||
|
||||
# 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})
|
||||
endif()
|
||||
|
||||
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
|
||||
|
||||
######## Set compiler flags ########
|
||||
include(UseCXX11)
|
||||
include(Coverage)
|
||||
set(CMAKE_CXX_FLAGS " -Wall ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED true)
|
||||
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}")
|
||||
if(UNIX AND APPLE)
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
|
||||
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
# Find the required Qt parts
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Concurrent REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Test REQUIRED)
|
||||
find_package(Qt5Xml REQUIRED)
|
||||
find_package(Qt5LinguistTools REQUIRED)
|
||||
find_package(Qt5WebKitWidgets REQUIRED)
|
||||
|
||||
include_directories(
|
||||
${Qt5Core_INCLUDE_DIRS}
|
||||
${Qt5Widgets_INCLUDE_DIRS}
|
||||
${Qt5Concurrent_INCLUDE_DIRS}
|
||||
${Qt5Test_INCLUDE_DIRS}
|
||||
${Qt5Network_INCLUDE_DIRS}
|
||||
${Qt5Xml_INCLUDE_DIRS}
|
||||
)
|
||||
find_package(Qt5Core)
|
||||
find_package(Qt5Widgets)
|
||||
find_package(Qt5Concurrent)
|
||||
find_package(Qt5Network)
|
||||
find_package(Qt5Test)
|
||||
find_package(Qt5Xml)
|
||||
|
||||
# The Qt5 cmake files don't provide its install paths, so ask qmake.
|
||||
include(QMakeQuery)
|
||||
query_qmake(QT_INSTALL_PLUGINS QT_PLUGINS_DIR)
|
||||
query_qmake(QT_INSTALL_IMPORTS QT_IMPORTS_DIR)
|
||||
query_qmake(QT_INSTALL_LIBS QT_LIBS_DIR)
|
||||
query_qmake(QT_INSTALL_LIBEXECS QT_LIBEXECS_DIR)
|
||||
query_qmake(QT_HOST_DATA QT_DATA_DIR)
|
||||
set(QT_MKSPECS_DIR ${QT_DATA_DIR}/mkspecs)
|
||||
|
||||
|
||||
################################ SET UP BUILD OPTIONS ################################
|
||||
|
||||
######## Check endianness ########
|
||||
include(TestBigEndian)
|
||||
test_big_endian(BIGENDIAN)
|
||||
if(${BIGENDIAN})
|
||||
add_definitions(-DMULTIMC_BIG_ENDIAN)
|
||||
endif(${BIGENDIAN})
|
||||
|
||||
######## Set URLs ########
|
||||
set(MultiMC_NEWS_RSS_URL "http://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 4)
|
||||
set(MultiMC_VERSION_HOTFIX 4)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
|
||||
# Version type
|
||||
set(MultiMC_VERSION_TYPE "Custom" CACHE STRING "MultiMC's version type. This should be one of 'Custom', 'Release', 'ReleaseCandidate', or 'Development', depending on what type of version this is.")
|
||||
|
||||
# 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.")
|
||||
|
||||
# Version channel
|
||||
set(MultiMC_VERSION_CHANNEL "" CACHE STRING "The current build's channel. Included in the version string.")
|
||||
|
||||
# Channel list URL
|
||||
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
|
||||
|
||||
# Updater enabled?
|
||||
set(MultiMC_UPDATER false CACHE BOOL "Whether or not the update system is enabled. If this is enabled, you must also set MultiMC_CHANLIST_URL and MultiMC_VERSION_CHANNEL in order for it to work properly.")
|
||||
|
||||
# Notification URL
|
||||
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||
|
||||
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}")
|
||||
if(MultiMC_VERSION_HOTFIX GREATER 0)
|
||||
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_RELEASE_VERSION_NAME}.${MultiMC_VERSION_HOTFIX}")
|
||||
if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# Build a version string to display in the configure logs.
|
||||
if(MultiMC_VERSION_TYPE STREQUAL "Custom")
|
||||
message(STATUS "Version Type: Custom")
|
||||
set(MultiMC_VERSION_STRING "${MultiMC_RELEASE_VERSION_NAME}")
|
||||
elseif(MultiMC_VERSION_TYPE STREQUAL "Release")
|
||||
message(STATUS "Version Type: Stable Release")
|
||||
set(MultiMC_VERSION_STRING "${MultiMC_RELEASE_VERSION_NAME}")
|
||||
elseif(MultiMC_VERSION_TYPE STREQUAL "ReleaseCandidate")
|
||||
message(STATUS "Version Type: Release Candidate")
|
||||
set(MultiMC_VERSION_STRING "${MultiMC_RELEASE_VERSION_NAME}-rc${MultiMC_VERSION_BUILD}")
|
||||
elseif(MultiMC_VERSION_TYPE STREQUAL "Development")
|
||||
message(STATUS "Version Type: Development")
|
||||
set(MultiMC_VERSION_STRING "${MultiMC_RELEASE_VERSION_NAME}-dev${MultiMC_VERSION_BUILD}")
|
||||
else()
|
||||
message(ERROR "Invalid build type.")
|
||||
endif()
|
||||
|
||||
message(STATUS "MultiMC 5 Version: ${MultiMC_VERSION_STRING}")
|
||||
|
||||
# If the update system is enabled, make sure MultiMC_CHANLIST_URL and MultiMC_VERSION_CHANNEL are set.
|
||||
if(MultiMC_UPDATER)
|
||||
if(MultiMC_VERSION_CHANNEL STREQUAL "")
|
||||
message(FATAL_ERROR "Update system is enabled, but MultiMC_VERSION_CHANNEL is not set.\n"
|
||||
"Please ensure the CMake variables MultiMC_VERSION_CHANNEL, MultiMC_CHANLIST_URL, and MultiMC_VERSION_BUILD are set.")
|
||||
endif()
|
||||
if(MultiMC_CHANLIST_URL STREQUAL "")
|
||||
message(FATAL_ERROR "Update system is enabled, but MultiMC_CHANLIST_URL is not set.\n"
|
||||
"Please ensure the CMake variables MultiMC_VERSION_CHANNEL, MultiMC_CHANLIST_URL, and MultiMC_VERSION_BUILD are set.")
|
||||
endif()
|
||||
if(MultiMC_VERSION_BUILD LESS 0)
|
||||
message(FATAL_ERROR "Update system is enabled, but MultiMC_VERSION_BUILD is not set.\n"
|
||||
"Please ensure the CMake variables MultiMC_VERSION_CHANNEL, MultiMC_CHANLIST_URL, and MultiMC_VERSION_BUILD are set.")
|
||||
endif()
|
||||
message(STATUS "Updater is enabled. Channel list URL: ${MultiMC_CHANLIST_URL}")
|
||||
endif()
|
||||
|
||||
#### Updater-related build config options ####
|
||||
option(MultiMC_UPDATER_DRY_RUN "Enable updater dry-run mode -- for updater development." OFF)
|
||||
option(MultiMC_UPDATER_FORCE_LOCAL "Do not download updated updater -- for updater development." OFF)
|
||||
|
||||
if(MultiMC_UPDATER_DRY_RUN)
|
||||
set(MultiMC_UPDATER_DRY_RUN_value "true")
|
||||
else()
|
||||
set(MultiMC_UPDATER_DRY_RUN_value "false")
|
||||
endif()
|
||||
|
||||
if(MultiMC_UPDATER_FORCE_LOCAL)
|
||||
set(MultiMC_UPDATER_FORCE_LOCAL_value "true")
|
||||
else()
|
||||
set(MultiMC_UPDATER_FORCE_LOCAL_value "false")
|
||||
endif()
|
||||
|
||||
#### For QuickMods
|
||||
option(MultiMC_WEBKIT_INSPECTOR "Enable the QWebInspector for debugging" OFF)
|
||||
if(MultiMC_WEBKIT_INSPECTOR)
|
||||
add_definitions(-DWEBKIT_INSPECTOR)
|
||||
endif()
|
||||
|
||||
#### Custom target to just print the version.
|
||||
add_custom_target(version echo "Version: ${MultiMC_VERSION_STRING}")
|
||||
|
||||
#### Check the current Git commit
|
||||
include(GitFunctions)
|
||||
git_run(COMMAND rev-parse HEAD DEFAULT "Unknown" OUTPUT_VAR MultiMC_GIT_COMMIT)
|
||||
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
|
||||
|
||||
######## Configure header ########
|
||||
configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR}/BuildConfig.cpp")
|
||||
|
||||
######## Packaging/install paths setup ########
|
||||
|
||||
if(UNIX AND APPLE)
|
||||
set(BINARY_DEST_DIR MultiMC.app/Contents/MacOS)
|
||||
set(PLUGIN_DEST_DIR MultiMC.app/Contents/MacOS)
|
||||
set(QTCONF_DEST_DIR MultiMC.app/Contents/Resources)
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
|
||||
|
||||
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_REV}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2013 MultiMC Contributors")
|
||||
elseif(UNIX)
|
||||
set(BINARY_DEST_DIR bin)
|
||||
set(PLUGIN_DEST_DIR plugins)
|
||||
set(QTCONF_DEST_DIR .)
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
|
||||
elseif(WIN32)
|
||||
set(BINARY_DEST_DIR .)
|
||||
set(PLUGIN_DEST_DIR .)
|
||||
set(QTCONF_DEST_DIR .)
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
|
||||
endif()
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS "${QT_LIBS_DIR}")
|
||||
|
||||
################################ Included Libs ################################
|
||||
|
||||
# Add quazip
|
||||
add_definitions(-DQUAZIP_STATIC)
|
||||
add_subdirectory(depends/quazip)
|
||||
include_directories(depends/quazip)
|
||||
|
||||
# Add the java launcher and checker
|
||||
add_subdirectory(depends/launcher)
|
||||
add_subdirectory(depends/javacheck)
|
||||
|
||||
# Add xz decompression
|
||||
add_subdirectory(depends/xz-embedded)
|
||||
include_directories(${XZ_INCLUDE_DIR})
|
||||
|
||||
# Add pack200 decompression
|
||||
add_subdirectory(depends/pack200)
|
||||
include_directories(${PACK200_INCLUDE_DIR})
|
||||
|
||||
######## MultiMC Libs ########
|
||||
|
||||
# Add the util library.
|
||||
add_definitions(-DLIBUTIL_STATIC)
|
||||
add_subdirectory(depends/util)
|
||||
include_directories(${LIBUTIL_INCLUDE_DIR})
|
||||
|
||||
# Add the updater
|
||||
add_subdirectory(mmc_updater)
|
||||
|
||||
# Add the GUI -> Logic connection header
|
||||
add_subdirectory(depends/LogicalGui)
|
||||
include_directories(${LOGICALGUI_INCLUDE_DIR})
|
||||
|
||||
################################ FILES ################################
|
||||
|
||||
######## Sources and headers ########
|
||||
SET(MULTIMC_SOURCES
|
||||
# Application base
|
||||
MultiMC.h
|
||||
MultiMC.cpp
|
||||
MMCError.h
|
||||
BuildConfig.h
|
||||
${PROJECT_BINARY_DIR}/BuildConfig.cpp
|
||||
|
||||
# Logging
|
||||
logger/QsDebugOutput.cpp
|
||||
logger/QsDebugOutput.h
|
||||
logger/QsLog.cpp
|
||||
logger/QsLog.h
|
||||
logger/QsLogDest.cpp
|
||||
logger/QsLogDest.h
|
||||
|
||||
# GUI - general utilities
|
||||
gui/GuiUtil.h
|
||||
gui/GuiUtil.cpp
|
||||
gui/ColumnResizer.h
|
||||
gui/ColumnResizer.cpp
|
||||
|
||||
# GUI - windows
|
||||
gui/MainWindow.h
|
||||
gui/MainWindow.cpp
|
||||
gui/ConsoleWindow.h
|
||||
gui/ConsoleWindow.cpp
|
||||
|
||||
# GUI - page dialog pages
|
||||
gui/pages/BasePage.h
|
||||
gui/pages/VersionPage.cpp
|
||||
gui/pages/VersionPage.h
|
||||
gui/pages/TexturePackPage.h
|
||||
gui/pages/ResourcePackPage.h
|
||||
gui/pages/ModFolderPage.cpp
|
||||
gui/pages/ModFolderPage.h
|
||||
gui/pages/NotesPage.cpp
|
||||
gui/pages/NotesPage.h
|
||||
gui/pages/LegacyUpgradePage.cpp
|
||||
gui/pages/LegacyUpgradePage.h
|
||||
gui/pages/LegacyJarModPage.cpp
|
||||
gui/pages/LegacyJarModPage.h
|
||||
gui/pages/LogPage.cpp
|
||||
gui/pages/LogPage.h
|
||||
gui/pages/InstanceSettingsPage.cpp
|
||||
gui/pages/InstanceSettingsPage.h
|
||||
gui/pages/ScreenshotsPage.cpp
|
||||
gui/pages/ScreenshotsPage.h
|
||||
gui/pages/OtherLogsPage.cpp
|
||||
gui/pages/OtherLogsPage.h
|
||||
|
||||
# GUI - global settings pages
|
||||
gui/pages/global/AccountListPage.cpp
|
||||
gui/pages/global/AccountListPage.h
|
||||
gui/pages/global/ExternalToolsPage.cpp
|
||||
gui/pages/global/ExternalToolsPage.h
|
||||
gui/pages/global/JavaPage.cpp
|
||||
gui/pages/global/JavaPage.h
|
||||
gui/pages/global/MinecraftPage.cpp
|
||||
gui/pages/global/MinecraftPage.h
|
||||
gui/pages/global/MultiMCPage.cpp
|
||||
gui/pages/global/MultiMCPage.h
|
||||
gui/pages/global/ProxyPage.cpp
|
||||
gui/pages/global/ProxyPage.h
|
||||
|
||||
# GUI - dialogs
|
||||
gui/dialogs/AboutDialog.cpp
|
||||
gui/dialogs/AboutDialog.h
|
||||
gui/dialogs/AccountSelectDialog.cpp
|
||||
gui/dialogs/AccountSelectDialog.h
|
||||
gui/dialogs/CopyInstanceDialog.cpp
|
||||
gui/dialogs/CopyInstanceDialog.h
|
||||
gui/dialogs/CustomMessageBox.cpp
|
||||
gui/dialogs/CustomMessageBox.h
|
||||
gui/dialogs/EditAccountDialog.cpp
|
||||
gui/dialogs/EditAccountDialog.h
|
||||
gui/dialogs/IconPickerDialog.cpp
|
||||
gui/dialogs/IconPickerDialog.h
|
||||
gui/dialogs/LoginDialog.cpp
|
||||
gui/dialogs/LoginDialog.h
|
||||
gui/dialogs/LwjglSelectDialog.cpp
|
||||
gui/dialogs/LwjglSelectDialog.h
|
||||
gui/dialogs/ModEditDialogCommon.cpp
|
||||
gui/dialogs/ModEditDialogCommon.h
|
||||
gui/dialogs/NewInstanceDialog.cpp
|
||||
gui/dialogs/NewInstanceDialog.h
|
||||
gui/dialogs/NotificationDialog.cpp
|
||||
gui/dialogs/NotificationDialog.h
|
||||
gui/pagedialog/PageDialog.cpp
|
||||
gui/pagedialog/PageDialog.h
|
||||
gui/dialogs/ProgressDialog.cpp
|
||||
gui/dialogs/ProgressDialog.h
|
||||
gui/dialogs/UpdateDialog.cpp
|
||||
gui/dialogs/UpdateDialog.h
|
||||
gui/dialogs/VersionSelectDialog.cpp
|
||||
gui/dialogs/VersionSelectDialog.h
|
||||
|
||||
|
||||
# GUI - widgets
|
||||
gui/widgets/Common.cpp
|
||||
gui/widgets/Common.h
|
||||
gui/widgets/IconLabel.cpp
|
||||
gui/widgets/IconLabel.h
|
||||
gui/widgets/LabeledToolButton.cpp
|
||||
gui/widgets/LabeledToolButton.h
|
||||
gui/widgets/LineSeparator.cpp
|
||||
gui/widgets/LineSeparator.h
|
||||
gui/widgets/MCModInfoFrame.cpp
|
||||
gui/widgets/MCModInfoFrame.h
|
||||
gui/widgets/ModListView.cpp
|
||||
gui/widgets/ModListView.h
|
||||
gui/widgets/PageContainer.cpp
|
||||
gui/widgets/PageContainer.h
|
||||
gui/widgets/PageContainer_p.h
|
||||
gui/widgets/ServerStatus.cpp
|
||||
gui/widgets/ServerStatus.h
|
||||
gui/widgets/VersionListView.cpp
|
||||
gui/widgets/VersionListView.h
|
||||
|
||||
|
||||
# GUI - instance group view
|
||||
gui/groupview/GroupedProxyModel.cpp
|
||||
gui/groupview/GroupedProxyModel.h
|
||||
gui/groupview/GroupView.cpp
|
||||
gui/groupview/GroupView.h
|
||||
gui/groupview/InstanceDelegate.cpp
|
||||
gui/groupview/InstanceDelegate.h
|
||||
gui/groupview/VisualGroup.cpp
|
||||
gui/groupview/VisualGroup.h
|
||||
|
||||
# LOGIC - Base classes and infrastructure
|
||||
logic/BaseVersion.h
|
||||
logic/InstanceFactory.h
|
||||
logic/InstanceFactory.cpp
|
||||
logic/BaseInstance.h
|
||||
logic/BaseInstance.cpp
|
||||
logic/BaseInstance_p.h
|
||||
logic/Mod.h
|
||||
logic/Mod.cpp
|
||||
logic/ModList.h
|
||||
logic/ModList.cpp
|
||||
|
||||
# sets and maps for deciding based on versions
|
||||
logic/VersionFilterData.h
|
||||
logic/VersionFilterData.cpp
|
||||
|
||||
# Instance launch
|
||||
logic/InstanceLauncher.h
|
||||
logic/InstanceLauncher.cpp
|
||||
logic/MinecraftProcess.h
|
||||
logic/MinecraftProcess.cpp
|
||||
|
||||
# URN parser/resolver
|
||||
logic/URNResolver.cpp
|
||||
logic/URNResolver.h
|
||||
|
||||
# Annoying nag screen logic
|
||||
logic/NagUtils.h
|
||||
logic/NagUtils.cpp
|
||||
|
||||
# Player skin utilities
|
||||
logic/SkinUtils.h
|
||||
logic/SkinUtils.cpp
|
||||
|
||||
# misc model filter
|
||||
logic/EnabledItemFilter.h
|
||||
logic/EnabledItemFilter.cpp
|
||||
|
||||
# JSON parsing helpers
|
||||
logic/MMCJson.h
|
||||
logic/MMCJson.cpp
|
||||
|
||||
# RW lock protected map
|
||||
logic/RWStorage.h
|
||||
|
||||
# A variable that has an implicit default value and keeps track of changes
|
||||
logic/DefaultVariable.h
|
||||
|
||||
# network stuffs
|
||||
logic/net/NetAction.h
|
||||
logic/net/MD5EtagDownload.h
|
||||
logic/net/MD5EtagDownload.cpp
|
||||
logic/net/ByteArrayDownload.h
|
||||
logic/net/ByteArrayDownload.cpp
|
||||
logic/net/CacheDownload.h
|
||||
logic/net/CacheDownload.cpp
|
||||
logic/net/NetJob.h
|
||||
logic/net/NetJob.cpp
|
||||
logic/net/HttpMetaCache.h
|
||||
logic/net/HttpMetaCache.cpp
|
||||
logic/net/PasteUpload.h
|
||||
logic/net/PasteUpload.cpp
|
||||
logic/net/URLConstants.h
|
||||
logic/net/URLConstants.cpp
|
||||
|
||||
# Yggdrasil login stuff
|
||||
logic/auth/AuthSession.h
|
||||
logic/auth/AuthSession.cpp
|
||||
logic/auth/MojangAccountList.h
|
||||
logic/auth/MojangAccountList.cpp
|
||||
logic/auth/MojangAccount.h
|
||||
logic/auth/MojangAccount.cpp
|
||||
logic/auth/YggdrasilTask.h
|
||||
logic/auth/YggdrasilTask.cpp
|
||||
logic/auth/flows/AuthenticateTask.h
|
||||
logic/auth/flows/AuthenticateTask.cpp
|
||||
logic/auth/flows/RefreshTask.cpp
|
||||
logic/auth/flows/RefreshTask.cpp
|
||||
logic/auth/flows/ValidateTask.h
|
||||
logic/auth/flows/ValidateTask.cpp
|
||||
|
||||
# Update system
|
||||
logic/updater/UpdateChecker.h
|
||||
logic/updater/UpdateChecker.cpp
|
||||
logic/updater/DownloadUpdateTask.h
|
||||
logic/updater/DownloadUpdateTask.cpp
|
||||
logic/updater/NotificationChecker.h
|
||||
logic/updater/NotificationChecker.cpp
|
||||
|
||||
# News System
|
||||
logic/news/NewsChecker.h
|
||||
logic/news/NewsChecker.cpp
|
||||
logic/news/NewsEntry.h
|
||||
logic/news/NewsEntry.cpp
|
||||
|
||||
# Status system
|
||||
logic/status/StatusChecker.h
|
||||
logic/status/StatusChecker.cpp
|
||||
|
||||
# legacy instances
|
||||
logic/LegacyInstance.h
|
||||
logic/LegacyInstance.cpp
|
||||
logic/LegacyInstance_p.h
|
||||
logic/LegacyUpdate.h
|
||||
logic/LegacyUpdate.cpp
|
||||
|
||||
# OneSix instances
|
||||
logic/OneSixUpdate.h
|
||||
logic/OneSixUpdate.cpp
|
||||
logic/OneSixInstance.h
|
||||
logic/OneSixInstance.cpp
|
||||
logic/OneSixInstance_p.h
|
||||
|
||||
# OneSix version json infrastructure
|
||||
logic/minecraft/GradleSpecifier.h
|
||||
logic/minecraft/InstanceVersion.cpp
|
||||
logic/minecraft/InstanceVersion.h
|
||||
logic/minecraft/JarMod.cpp
|
||||
logic/minecraft/JarMod.h
|
||||
logic/minecraft/MinecraftVersion.cpp
|
||||
logic/minecraft/MinecraftVersion.h
|
||||
logic/minecraft/MinecraftVersionList.cpp
|
||||
logic/minecraft/MinecraftVersionList.h
|
||||
logic/minecraft/OneSixLibrary.cpp
|
||||
logic/minecraft/OneSixLibrary.h
|
||||
logic/minecraft/OneSixRule.cpp
|
||||
logic/minecraft/OneSixRule.h
|
||||
logic/minecraft/OpSys.cpp
|
||||
logic/minecraft/OpSys.h
|
||||
logic/minecraft/ParseUtils.cpp
|
||||
logic/minecraft/ParseUtils.h
|
||||
logic/minecraft/RawLibrary.cpp
|
||||
logic/minecraft/RawLibrary.h
|
||||
logic/minecraft/VersionBuilder.cpp
|
||||
logic/minecraft/VersionBuilder.h
|
||||
logic/minecraft/VersionBuildError.h
|
||||
logic/minecraft/VersionFile.cpp
|
||||
logic/minecraft/VersionFile.h
|
||||
logic/minecraft/VersionPatch.h
|
||||
logic/minecraft/VersionSource.h
|
||||
|
||||
# A Recursive file system watcher
|
||||
logic/RecursiveFileSystemWatcher.h
|
||||
logic/RecursiveFileSystemWatcher.cpp
|
||||
|
||||
# Various base classes
|
||||
logic/BaseInstaller.h
|
||||
logic/BaseInstaller.cpp
|
||||
logic/BaseVersionList.h
|
||||
logic/BaseVersionList.cpp
|
||||
|
||||
logic/InstanceList.h
|
||||
logic/InstanceList.cpp
|
||||
logic/LwjglVersionList.h
|
||||
logic/LwjglVersionList.cpp
|
||||
|
||||
# FTB
|
||||
logic/OneSixFTBInstance.h
|
||||
logic/OneSixFTBInstance.cpp
|
||||
logic/LegacyFTBInstance.h
|
||||
logic/LegacyFTBInstance.cpp
|
||||
|
||||
# the screenshots feature
|
||||
logic/screenshots/Screenshot.h
|
||||
logic/screenshots/ImgurUpload.h
|
||||
logic/screenshots/ImgurUpload.cpp
|
||||
logic/screenshots/ImgurAlbumCreation.h
|
||||
logic/screenshots/ImgurAlbumCreation.cpp
|
||||
|
||||
# Icons
|
||||
logic/icons/MMCIcon.h
|
||||
logic/icons/MMCIcon.cpp
|
||||
logic/icons/IconList.h
|
||||
logic/icons/IconList.cpp
|
||||
|
||||
# Tasks
|
||||
logic/tasks/ProgressProvider.h
|
||||
logic/tasks/Task.h
|
||||
logic/tasks/Task.cpp
|
||||
logic/tasks/ThreadTask.h
|
||||
logic/tasks/ThreadTask.cpp
|
||||
logic/tasks/SequentialTask.h
|
||||
logic/tasks/SequentialTask.cpp
|
||||
|
||||
# Settings
|
||||
logic/settings/INIFile.cpp
|
||||
logic/settings/INIFile.h
|
||||
logic/settings/INISettingsObject.cpp
|
||||
logic/settings/INISettingsObject.h
|
||||
logic/settings/OverrideSetting.cpp
|
||||
logic/settings/OverrideSetting.h
|
||||
logic/settings/Setting.cpp
|
||||
logic/settings/Setting.h
|
||||
logic/settings/SettingsObject.cpp
|
||||
logic/settings/SettingsObject.h
|
||||
|
||||
# Java related code
|
||||
logic/java/JavaChecker.h
|
||||
logic/java/JavaChecker.cpp
|
||||
logic/java/JavaUtils.h
|
||||
logic/java/JavaUtils.cpp
|
||||
logic/java/JavaVersionList.h
|
||||
logic/java/JavaVersionList.cpp
|
||||
logic/java/JavaCheckerJob.h
|
||||
logic/java/JavaCheckerJob.cpp
|
||||
|
||||
# Assets
|
||||
logic/assets/AssetsMigrateTask.h
|
||||
logic/assets/AssetsMigrateTask.cpp
|
||||
logic/assets/AssetsUtils.h
|
||||
logic/assets/AssetsUtils.cpp
|
||||
|
||||
# Tools
|
||||
logic/tools/BaseExternalTool.h
|
||||
logic/tools/BaseExternalTool.cpp
|
||||
logic/tools/MCEditTool.h
|
||||
logic/tools/MCEditTool.cpp
|
||||
logic/tools/BaseProfiler.h
|
||||
logic/tools/BaseProfiler.cpp
|
||||
logic/tools/JProfiler.h
|
||||
logic/tools/JProfiler.cpp
|
||||
logic/tools/JVisualVM.h
|
||||
logic/tools/JVisualVM.cpp
|
||||
|
||||
# Forge and all things forge related
|
||||
logic/forge/ForgeVersion.h
|
||||
logic/forge/ForgeVersion.cpp
|
||||
logic/forge/ForgeVersionList.h
|
||||
logic/forge/ForgeVersionList.cpp
|
||||
logic/forge/ForgeMirror.h
|
||||
logic/forge/ForgeMirrors.h
|
||||
logic/forge/ForgeMirrors.cpp
|
||||
logic/forge/ForgeXzDownload.h
|
||||
logic/forge/ForgeXzDownload.cpp
|
||||
logic/forge/LegacyForge.h
|
||||
logic/forge/LegacyForge.cpp
|
||||
logic/forge/ForgeInstaller.h
|
||||
logic/forge/ForgeInstaller.cpp
|
||||
|
||||
# Liteloader and related things
|
||||
logic/liteloader/LiteLoaderInstaller.h
|
||||
logic/liteloader/LiteLoaderInstaller.cpp
|
||||
logic/liteloader/LiteLoaderVersionList.h
|
||||
logic/liteloader/LiteLoaderVersionList.cpp
|
||||
|
||||
# Translations
|
||||
logic/trans/TranslationDownloader.h
|
||||
logic/trans/TranslationDownloader.cpp
|
||||
)
|
||||
|
||||
|
||||
######## UIs ########
|
||||
SET(MULTIMC_UIS
|
||||
# Windows
|
||||
gui/MainWindow.ui
|
||||
|
||||
# Option pages
|
||||
gui/pages/VersionPage.ui
|
||||
gui/pages/ModFolderPage.ui
|
||||
gui/pages/LegacyUpgradePage.ui
|
||||
gui/pages/LegacyJarModPage.ui
|
||||
gui/pages/LogPage.ui
|
||||
gui/pages/InstanceSettingsPage.ui
|
||||
gui/pages/NotesPage.ui
|
||||
gui/pages/ScreenshotsPage.ui
|
||||
gui/pages/OtherLogsPage.ui
|
||||
|
||||
# Global settings pages
|
||||
gui/pages/global/AccountListPage.ui
|
||||
gui/pages/global/ExternalToolsPage.ui
|
||||
gui/pages/global/JavaPage.ui
|
||||
gui/pages/global/MinecraftPage.ui
|
||||
gui/pages/global/MultiMCPage.ui
|
||||
gui/pages/global/ProxyPage.ui
|
||||
|
||||
# Dialogs
|
||||
gui/dialogs/CopyInstanceDialog.ui
|
||||
gui/dialogs/NewInstanceDialog.ui
|
||||
gui/dialogs/AboutDialog.ui
|
||||
gui/dialogs/VersionSelectDialog.ui
|
||||
gui/dialogs/LwjglSelectDialog.ui
|
||||
gui/dialogs/ProgressDialog.ui
|
||||
gui/dialogs/IconPickerDialog.ui
|
||||
gui/dialogs/AccountSelectDialog.ui
|
||||
gui/dialogs/EditAccountDialog.ui
|
||||
gui/dialogs/LoginDialog.ui
|
||||
gui/dialogs/UpdateDialog.ui
|
||||
gui/dialogs/NotificationDialog.ui
|
||||
|
||||
# Widgets/other
|
||||
gui/widgets/MCModInfoFrame.ui
|
||||
)
|
||||
|
||||
set(FILES_TO_TRANSLATE)
|
||||
foreach(file ${MULTIMC_SOURCES})
|
||||
get_filename_component(absfile "${file}" ABSOLUTE)
|
||||
list(APPEND FILES_TO_TRANSLATE "${absfile}")
|
||||
endforeach()
|
||||
|
||||
foreach(file ${MULTIMC_UIS})
|
||||
get_filename_component(absfile "${file}" ABSOLUTE)
|
||||
list(APPEND FILES_TO_TRANSLATE "${absfile}")
|
||||
endforeach()
|
||||
|
||||
set(MULTIMC_QRCS
|
||||
resources/backgrounds/backgrounds.qrc
|
||||
resources/multimc/multimc.qrc
|
||||
resources/pe_dark/pe_dark.qrc
|
||||
resources/pe_light/pe_light.qrc
|
||||
resources/pe_colored/pe_colored.qrc
|
||||
resources/pe_blue/pe_blue.qrc
|
||||
resources/OSX/OSX.qrc
|
||||
resources/iOS/iOS.qrc
|
||||
resources/instances/instances.qrc
|
||||
resources/versions/versions.qrc
|
||||
)
|
||||
|
||||
|
||||
######## Windows resource files ########
|
||||
if(WIN32)
|
||||
set(MULTIMC_RCS resources/multimc.rc)
|
||||
endif()
|
||||
|
||||
####### X11 Stuff #######
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(MultiMC_QT_ADDITIONAL_MODULES ${MultiMC_QT_ADDITIONAL_MODULES} X11Extras)
|
||||
set(MultiMC_LINK_ADDITIONAL_LIBS ${MultiMC_LINK_ADDITIONAL_LIBS} xcb)
|
||||
list(APPEND MULTIMC_SOURCES gui/Platform_X11.cpp)
|
||||
else()
|
||||
list(APPEND MULTIMC_SOURCES gui/Platform_Other.cpp)
|
||||
endif()
|
||||
|
||||
|
||||
################################ COMPILE ################################
|
||||
|
||||
# Link additional libraries
|
||||
if(WIN32)
|
||||
set(MultiMC_LINK_ADDITIONAL_LIBS ${MultiMC_LINK_ADDITIONAL_LIBS} Qt5::WinMain)
|
||||
endif(WIN32)
|
||||
|
||||
# Tell CMake that MultiMCLauncher.jar is generated.
|
||||
#SET_SOURCE_FILES_PROPERTIES(${PROJECT_BINARY_DIR}/depends/launcher/MultiMCLauncher.jar GENERATED)
|
||||
#SET_SOURCE_FILES_PROPERTIES(${PROJECT_BINARY_DIR}/depends/javacheck/JavaCheck.jar GENERATED)
|
||||
|
||||
# Qt 5 stuff
|
||||
qt5_wrap_ui(MULTIMC_UI ${MULTIMC_UIS})
|
||||
qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
|
||||
|
||||
# Add common library
|
||||
add_library(MultiMC_common STATIC ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES})
|
||||
|
||||
# Add executable
|
||||
add_executable(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS})
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC MultiMC_common)
|
||||
target_link_libraries(MultiMC_common xz-embedded unpack200 quazip libUtil LogicalGui ${MultiMC_LINK_ADDITIONAL_LIBS})
|
||||
qt5_use_modules(MultiMC Core Widgets Network Xml Concurrent WebKitWidgets ${MultiMC_QT_ADDITIONAL_MODULES})
|
||||
qt5_use_modules(MultiMC_common Core Widgets Network Xml Concurrent WebKitWidgets ${MultiMC_QT_ADDITIONAL_MODULES})
|
||||
|
||||
################################ INSTALLATION AND PACKAGING ################################
|
||||
|
||||
######## Install ########
|
||||
|
||||
#### Executable ####
|
||||
if(APPLE AND UNIX) ## OSX
|
||||
install(TARGETS MultiMC
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION MultiMC.app/Contents/MacOS COMPONENT Runtime
|
||||
)
|
||||
|
||||
elseif(UNIX) ## LINUX and similar
|
||||
install(TARGETS MultiMC
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION bin COMPONENT Runtime
|
||||
)
|
||||
install(PROGRAMS package/linux/MultiMC DESTINATION .)
|
||||
|
||||
elseif(WIN32) ## WINDOWS
|
||||
install(TARGETS MultiMC
|
||||
BUNDLE DESTINATION . COMPONENT Runtime
|
||||
LIBRARY DESTINATION . COMPONENT Runtime
|
||||
RUNTIME DESTINATION . COMPONENT Runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
#### Dist package logic ####
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
# Image formats
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
)
|
||||
# Icon engines
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
)
|
||||
# Platform plugins
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
)
|
||||
else()
|
||||
# Image formats
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "tga|tiff|mng" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
)
|
||||
# Icon engines
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "fontawesome" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
)
|
||||
# Platform plugins
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "minimal|linuxfb|offscreen" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
)
|
||||
if(APPLE)
|
||||
# Accessible plugin to make buttons look decent on osx
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/accessible"
|
||||
DESTINATION ${PLUGIN_DEST_DIR}
|
||||
COMPONENT Runtime
|
||||
REGEX "quick" EXCLUDE
|
||||
REGEX "d\\." EXCLUDE
|
||||
REGEX "_debug\\." EXCLUDE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# qtconf
|
||||
install(
|
||||
CODE "
|
||||
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${QTCONF_DEST_DIR}/qt.conf\" \"\")
|
||||
"
|
||||
COMPONENT Runtime
|
||||
)
|
||||
|
||||
# ICNS file for OS X
|
||||
if(APPLE)
|
||||
install(FILES resources/MultiMC.icns DESTINATION MultiMC.app/Contents/Resources)
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/install_prereqs.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake"
|
||||
@ONLY
|
||||
)
|
||||
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_prereqs.cmake" COMPONENT Runtime)
|
||||
|
||||
|
||||
|
||||
######## Package ########
|
||||
|
||||
# Package with CPack
|
||||
if(UNIX)
|
||||
if(APPLE)
|
||||
set(CPACK_GENERATOR "ZIP")
|
||||
else()
|
||||
set(CPACK_GENERATOR "TGZ")
|
||||
endif()
|
||||
elseif(WIN32)
|
||||
set(CPACK_GENERATOR "ZIP")
|
||||
endif()
|
||||
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
|
||||
|
||||
set(CPACK_PACKAGE_NAME "MultiMC 5")
|
||||
set(CPACK_PACKAGE_VENDOR "")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "MultiMC - Minecraft launcher and management tool.")
|
||||
set(CPACK_PACKAGE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_REV}.${MultiMC_VERSION_BUILD}")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${MultiMC_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${MultiMC_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${MultiMC_VERSION_REV})
|
||||
|
||||
if(CPACK_GENERATOR STREQUAL "NSIS")
|
||||
set(CPACK_PACKAGE_FILE_NAME "Setup-MultiMC")
|
||||
else()
|
||||
set(CPACK_PACKAGE_FILE_NAME "MultiMC")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "MultiMC 5")
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
||||
include(Coverity)
|
||||
|
||||
# Translations
|
||||
add_subdirectory(translations)
|
||||
|
||||
# Tests
|
||||
add_subdirectory(tests)
|
||||
include(ExternalProject)
|
||||
set_directory_properties(PROPERTIES EP_BASE External)
|
||||
|
||||
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++)
|
||||
add_subdirectory(libraries/libnbtplusplus)
|
||||
|
||||
add_subdirectory(libraries/ganalytics) # google analytics library
|
||||
add_subdirectory(libraries/systeminfo) # system information library
|
||||
add_subdirectory(libraries/hoedown) # markdown parser
|
||||
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
|
||||
add_subdirectory(libraries/javacheck) # java compatibility checker
|
||||
add_subdirectory(libraries/xz-embedded) # xz compression
|
||||
add_subdirectory(libraries/quazip) # zip manipulation library
|
||||
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
|
||||
|
||||
############################### Built Artifacts ###############################
|
||||
|
||||
add_subdirectory(api/logic)
|
||||
add_subdirectory(api/gui)
|
||||
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(wonkoclient)
|
||||
|
||||
205
COPYING.md
Normal file
205
COPYING.md
Normal file
@@ -0,0 +1,205 @@
|
||||
#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
|
||||
|
||||
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.
|
||||
|
||||
#MinGW runtime (Windows)
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
|
||||
#Qt 5
|
||||
|
||||
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Licensed under LGPL v2.1
|
||||
|
||||
#libnbt++
|
||||
|
||||
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 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/>.
|
||||
|
||||
#Group View
|
||||
|
||||
Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
|
||||
Copyright (C) 2007 John Tapsell <tapsell@kde.org>
|
||||
|
||||
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.
|
||||
|
||||
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)
|
||||
|
||||
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 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.
|
||||
|
||||
#Hoedown
|
||||
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#Batch icon set
|
||||
|
||||
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).
|
||||
|
||||
While attribution is optional, it is always appreciated.
|
||||
|
||||
Intellectual property rights are not transferred with the download of the icons.
|
||||
|
||||
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.
|
||||
|
||||
#Pack200
|
||||
|
||||
The GNU General Public License (GPL)
|
||||
|
||||
Version 2, June 1991
|
||||
|
||||
+ "CLASSPATH" EXCEPTION TO THE GPL
|
||||
|
||||
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."
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
#Quazip
|
||||
|
||||
Copyright (C) 2005-2011 Sergey A. Tachenov
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
See COPYING file for the full LGPL text.
|
||||
|
||||
Original ZIP package is copyrighted by Gilles Vollant, see
|
||||
quazip/(un)zip.h files for details, basically it's zlib license.
|
||||
|
||||
#xz-minidec
|
||||
|
||||
XZ decompressor
|
||||
|
||||
Authors: Lasse Collin <lasse.collin@tukaani.org>
|
||||
Igor Pavlov <http://7-zip.org/>
|
||||
|
||||
This file has been put into the public domain.
|
||||
You can do whatever you want with this file.
|
||||
|
||||
#ColumnResizer
|
||||
|
||||
Copyright 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
License: LGPL v2.1 or later (see COPYING)
|
||||
25
MMCError.h
25
MMCError.h
@@ -1,25 +0,0 @@
|
||||
#pragma once
|
||||
#include <exception>
|
||||
#include <QString>
|
||||
#include <logger/QsLog.h>
|
||||
|
||||
class MMCError : public std::exception
|
||||
{
|
||||
public:
|
||||
MMCError(QString cause)
|
||||
{
|
||||
exceptionCause = cause;
|
||||
QLOG_ERROR() << "Exception: " + cause;
|
||||
};
|
||||
virtual ~MMCError() noexcept {}
|
||||
virtual const char *what() const noexcept
|
||||
{
|
||||
return exceptionCause.toLocal8Bit();
|
||||
};
|
||||
virtual QString cause() const
|
||||
{
|
||||
return exceptionCause;
|
||||
}
|
||||
private:
|
||||
QString exceptionCause;
|
||||
};
|
||||
792
MultiMC.cpp
792
MultiMC.cpp
@@ -1,792 +0,0 @@
|
||||
#include "MultiMC.h"
|
||||
#include "BuildConfig.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QTranslator>
|
||||
#include <QLibraryInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QStringList>
|
||||
#include <QDesktopServices>
|
||||
|
||||
#include "gui/dialogs/VersionSelectDialog.h"
|
||||
#include "logic/InstanceList.h"
|
||||
#include "logic/auth/MojangAccountList.h"
|
||||
#include "logic/icons/IconList.h"
|
||||
#include "logic/LwjglVersionList.h"
|
||||
#include "logic/minecraft/MinecraftVersionList.h"
|
||||
#include "logic/liteloader/LiteLoaderVersionList.h"
|
||||
|
||||
#include "logic/forge/ForgeVersionList.h"
|
||||
|
||||
#include "logic/news/NewsChecker.h"
|
||||
|
||||
#include "logic/status/StatusChecker.h"
|
||||
|
||||
#include "logic/InstanceLauncher.h"
|
||||
#include "logic/net/HttpMetaCache.h"
|
||||
#include "logic/net/URLConstants.h"
|
||||
|
||||
#include "logic/java/JavaUtils.h"
|
||||
|
||||
#include "logic/updater/UpdateChecker.h"
|
||||
#include "logic/updater/NotificationChecker.h"
|
||||
|
||||
#include "logic/tools/JProfiler.h"
|
||||
#include "logic/tools/JVisualVM.h"
|
||||
#include "logic/tools/MCEditTool.h"
|
||||
|
||||
#include "logic/URNResolver.h"
|
||||
|
||||
#include "pathutils.h"
|
||||
#include "cmdutils.h"
|
||||
#include "logic/settings/INISettingsObject.h"
|
||||
#include "logic/settings/Setting.h"
|
||||
#include "logger/QsLog.h"
|
||||
#include "logger/QsLogDest.h"
|
||||
|
||||
#include "logic/trans/TranslationDownloader.h"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
static const int APPDATA_BUFFER_SIZE = 1024;
|
||||
#endif
|
||||
|
||||
using namespace Util::Commandline;
|
||||
|
||||
MultiMC::MultiMC(int &argc, char **argv, bool test_mode) : QApplication(argc, argv)
|
||||
{
|
||||
setOrganizationName("MultiMC");
|
||||
setApplicationName("MultiMC5");
|
||||
|
||||
setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
// Don't quit on hiding the last window
|
||||
this->setQuitOnLastWindowClosed(false);
|
||||
|
||||
// Commandline parsing
|
||||
QHash<QString, QVariant> args;
|
||||
{
|
||||
Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals);
|
||||
|
||||
// --help
|
||||
parser.addSwitch("help");
|
||||
parser.addShortOpt("help", 'h');
|
||||
parser.addDocumentation("help", "display this help and exit.");
|
||||
// --version
|
||||
parser.addSwitch("version");
|
||||
parser.addShortOpt("version", 'V');
|
||||
parser.addDocumentation("version", "display program version and exit.");
|
||||
// --dir
|
||||
parser.addOption("dir", applicationDirPath());
|
||||
parser.addShortOpt("dir", 'd');
|
||||
parser.addDocumentation("dir", "use the supplied directory as MultiMC root instead of "
|
||||
"the binary location (use '.' for current)");
|
||||
// WARNING: disabled until further notice
|
||||
/*
|
||||
// --launch
|
||||
parser.addOption("launch");
|
||||
parser.addShortOpt("launch", 'l');
|
||||
parser.addDocumentation("launch", "tries to launch the given instance", "<inst>");
|
||||
*/
|
||||
|
||||
// parse the arguments
|
||||
try
|
||||
{
|
||||
args = parser.parse(arguments());
|
||||
}
|
||||
catch (ParsingError e)
|
||||
{
|
||||
std::cerr << "CommandLineError: " << e.what() << std::endl;
|
||||
std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters."
|
||||
<< std::endl;
|
||||
m_status = MultiMC::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
// display help and exit
|
||||
if (args["help"].toBool())
|
||||
{
|
||||
std::cout << qPrintable(parser.compileHelp(arguments()[0]));
|
||||
m_status = MultiMC::Succeeded;
|
||||
return;
|
||||
}
|
||||
|
||||
// display version and exit
|
||||
if (args["version"].toBool())
|
||||
{
|
||||
std::cout << "Version " << BuildConfig.VERSION_STR.toStdString() << std::endl;
|
||||
std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl;
|
||||
m_status = MultiMC::Succeeded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
origcwdPath = QDir::currentPath();
|
||||
binPath = applicationDirPath();
|
||||
QString adjustedBy;
|
||||
// change directory
|
||||
QString dirParam = args["dir"].toString();
|
||||
if (!dirParam.isEmpty())
|
||||
{
|
||||
// the dir param. it makes multimc data path point to whatever the user specified
|
||||
// on command line
|
||||
adjustedBy += "Command line " + dirParam;
|
||||
dataPath = dirParam;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataPath = applicationDirPath();
|
||||
adjustedBy += "Fallback to binary path " + dataPath;
|
||||
}
|
||||
|
||||
if (!ensureFolderPathExists(dataPath) || !QDir::setCurrent(dataPath))
|
||||
{
|
||||
// BAD STUFF. WHAT DO?
|
||||
initLogger();
|
||||
QLOG_ERROR() << "Failed to set work path. Will exit. NOW.";
|
||||
m_status = MultiMC::Failed;
|
||||
return;
|
||||
}
|
||||
|
||||
// in test mode, root path is the same as the binary path.
|
||||
if (test_mode)
|
||||
{
|
||||
rootPath = binPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef Q_OS_LINUX
|
||||
QDir foo(PathCombine(binPath, ".."));
|
||||
rootPath = foo.absolutePath();
|
||||
#elif defined(Q_OS_WIN32)
|
||||
rootPath = binPath;
|
||||
#elif defined(Q_OS_MAC)
|
||||
QDir foo(PathCombine(binPath, "../.."));
|
||||
rootPath = foo.absolutePath();
|
||||
#endif
|
||||
}
|
||||
|
||||
// static data paths... mostly just for translations
|
||||
#ifdef Q_OS_LINUX
|
||||
QDir foo(PathCombine(binPath, ".."));
|
||||
staticDataPath = foo.absolutePath();
|
||||
#elif defined(Q_OS_WIN32)
|
||||
staticDataPath = binPath;
|
||||
#elif defined(Q_OS_MAC)
|
||||
QDir foo(PathCombine(rootPath, "Contents/Resources"));
|
||||
staticDataPath = foo.absolutePath();
|
||||
#endif
|
||||
|
||||
// init the logger
|
||||
initLogger();
|
||||
|
||||
QLOG_INFO() << "MultiMC 5, (c) 2013-2014 MultiMC Contributors";
|
||||
QLOG_INFO() << "Version : " << BuildConfig.VERSION_STR;
|
||||
QLOG_INFO() << "Git commit : " << BuildConfig.GIT_COMMIT;
|
||||
if (adjustedBy.size())
|
||||
{
|
||||
QLOG_INFO() << "Work dir before adjustment : " << origcwdPath;
|
||||
QLOG_INFO() << "Work dir after adjustment : " << QDir::currentPath();
|
||||
QLOG_INFO() << "Adjusted by : " << adjustedBy;
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "Work dir : " << QDir::currentPath();
|
||||
}
|
||||
QLOG_INFO() << "Binary path : " << binPath;
|
||||
QLOG_INFO() << "Application root path : " << rootPath;
|
||||
QLOG_INFO() << "Static data path : " << staticDataPath;
|
||||
|
||||
// load settings
|
||||
initGlobalSettings(test_mode);
|
||||
|
||||
// load translations
|
||||
initTranslations();
|
||||
|
||||
// initialize the updater
|
||||
m_updateChecker.reset(new UpdateChecker());
|
||||
|
||||
// initialize the notification checker
|
||||
m_notificationChecker.reset(new NotificationChecker());
|
||||
|
||||
// initialize the news checker
|
||||
m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL));
|
||||
|
||||
// initialize the status checker
|
||||
m_statusChecker.reset(new StatusChecker());
|
||||
|
||||
m_translationChecker.reset(new TranslationDownloader());
|
||||
|
||||
// and instances
|
||||
auto InstDirSetting = m_settings->getSetting("InstanceDir");
|
||||
// instance path: check for problems with '!' in instance path and warn the user in the log
|
||||
// and rememer that we have to show him a dialog when the gui starts (if it does so)
|
||||
QString instDir = MMC->settings()->get("InstanceDir").toString();
|
||||
QLOG_INFO() << "Instance path : " << instDir;
|
||||
if (checkProblemticPathJava(QDir(instDir)))
|
||||
{
|
||||
QLOG_WARN()
|
||||
<< "Your instance path contains \'!\' and this is known to cause java problems";
|
||||
}
|
||||
m_instances.reset(new InstanceList(InstDirSetting->get().toString(), this));
|
||||
QLOG_INFO() << "Loading Instances...";
|
||||
m_instances->loadList();
|
||||
connect(InstDirSetting.get(), SIGNAL(SettingChanged(const Setting &, QVariant)),
|
||||
m_instances.get(), SLOT(on_InstFolderChanged(const Setting &, QVariant)));
|
||||
|
||||
// and accounts
|
||||
m_accounts.reset(new MojangAccountList(this));
|
||||
QLOG_INFO() << "Loading accounts...";
|
||||
m_accounts->setListFilePath("accounts.json", true);
|
||||
m_accounts->loadList();
|
||||
|
||||
// init the http meta cache
|
||||
initHttpMetaCache();
|
||||
|
||||
// create the global network manager
|
||||
m_qnam.reset(new QNetworkAccessManager(this));
|
||||
|
||||
m_translationChecker->downloadTranslations();
|
||||
|
||||
// init proxy settings
|
||||
updateProxySettings();
|
||||
|
||||
m_profilers.insert("jprofiler",
|
||||
std::shared_ptr<BaseProfilerFactory>(new JProfilerFactory()));
|
||||
m_profilers.insert("jvisualvm",
|
||||
std::shared_ptr<BaseProfilerFactory>(new JVisualVMFactory()));
|
||||
for (auto profiler : m_profilers.values())
|
||||
{
|
||||
profiler->registerSettings(m_settings);
|
||||
}
|
||||
m_tools.insert("mcedit", std::shared_ptr<BaseDetachedToolFactory>(new MCEditFactory()));
|
||||
for (auto tool : m_tools.values())
|
||||
{
|
||||
tool->registerSettings(m_settings);
|
||||
}
|
||||
|
||||
// launch instance, if that's what should be done
|
||||
// WARNING: disabled until further notice
|
||||
/*
|
||||
if (!args["launch"].isNull())
|
||||
{
|
||||
if (InstanceLauncher(args["launch"].toString()).launch())
|
||||
m_status = MultiMC::Succeeded;
|
||||
else
|
||||
m_status = MultiMC::Failed;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
|
||||
m_status = MultiMC::Initialized;
|
||||
}
|
||||
|
||||
MultiMC::~MultiMC()
|
||||
{
|
||||
if (m_mmc_translator)
|
||||
{
|
||||
removeTranslator(m_mmc_translator.get());
|
||||
}
|
||||
if (m_qt_translator)
|
||||
{
|
||||
removeTranslator(m_qt_translator.get());
|
||||
}
|
||||
}
|
||||
|
||||
void MultiMC::initTranslations()
|
||||
{
|
||||
QLocale locale(m_settings->get("Language").toString());
|
||||
QLocale::setDefault(locale);
|
||||
QLOG_INFO() << "Your language is" << locale.bcp47Name();
|
||||
m_qt_translator.reset(new QTranslator());
|
||||
if (m_qt_translator->load("qt_" + locale.bcp47Name(),
|
||||
QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
|
||||
{
|
||||
QLOG_DEBUG() << "Loading Qt Language File for"
|
||||
<< locale.bcp47Name().toLocal8Bit().constData() << "...";
|
||||
if (!installTranslator(m_qt_translator.get()))
|
||||
{
|
||||
QLOG_ERROR() << "Loading Qt Language File failed.";
|
||||
m_qt_translator.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_qt_translator.reset();
|
||||
}
|
||||
|
||||
m_mmc_translator.reset(new QTranslator());
|
||||
if (m_mmc_translator->load("mmc_" + locale.bcp47Name(),
|
||||
MMC->staticData() + "/translations"))
|
||||
{
|
||||
QLOG_DEBUG() << "Loading MMC Language File for"
|
||||
<< locale.bcp47Name().toLocal8Bit().constData() << "...";
|
||||
if (!installTranslator(m_mmc_translator.get()))
|
||||
{
|
||||
QLOG_ERROR() << "Loading MMC Language File failed.";
|
||||
m_mmc_translator.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mmc_translator.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void moveFile(const QString &oldName, const QString &newName)
|
||||
{
|
||||
QFile::remove(newName);
|
||||
QFile::copy(oldName, newName);
|
||||
QFile::remove(oldName);
|
||||
}
|
||||
void MultiMC::initLogger()
|
||||
{
|
||||
static const QString logBase = "MultiMC-%0.log";
|
||||
|
||||
moveFile(logBase.arg(3), logBase.arg(4));
|
||||
moveFile(logBase.arg(2), logBase.arg(3));
|
||||
moveFile(logBase.arg(1), logBase.arg(2));
|
||||
moveFile(logBase.arg(0), logBase.arg(1));
|
||||
|
||||
// init the logging mechanism
|
||||
QsLogging::Logger &logger = QsLogging::Logger::instance();
|
||||
logger.setLoggingLevel(QsLogging::TraceLevel);
|
||||
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination(logBase.arg(0));
|
||||
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination();
|
||||
logger.addDestination(m_fileDestination.get());
|
||||
logger.addDestination(m_debugDestination.get());
|
||||
// log all the things
|
||||
logger.setLoggingLevel(QsLogging::TraceLevel);
|
||||
}
|
||||
|
||||
void MultiMC::initGlobalSettings(bool test_mode)
|
||||
{
|
||||
m_settings.reset(new INISettingsObject("multimc.cfg", this));
|
||||
// Updates
|
||||
m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
|
||||
m_settings->registerSetting("AutoUpdate", true);
|
||||
m_settings->registerSetting("IconTheme", QString("multimc"));
|
||||
|
||||
// Minecraft Sneaky Updates
|
||||
m_settings->registerSetting("AutoUpdateMinecraftVersions", true);
|
||||
|
||||
// Notifications
|
||||
m_settings->registerSetting("ShownNotifications", QString());
|
||||
|
||||
// Remembered state
|
||||
m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
|
||||
|
||||
QString defaultMonospace;
|
||||
#ifdef Q_OS_WIN32
|
||||
defaultMonospace = "Lucida Console";
|
||||
#elif defined(Q_OS_MAC)
|
||||
defaultMonospace = "Menlo";
|
||||
#else
|
||||
defaultMonospace = "Monospace";
|
||||
#endif
|
||||
if(!test_mode)
|
||||
{
|
||||
// resolve the font so the default actually matches
|
||||
QFont consoleFont;
|
||||
consoleFont.setFamily(defaultMonospace);
|
||||
consoleFont.setStyleHint(QFont::Monospace);
|
||||
consoleFont.setFixedPitch(true);
|
||||
QFontInfo consoleFontInfo(consoleFont);
|
||||
QString resolvedDefaultMonospace = consoleFontInfo.family();
|
||||
QFont resolvedFont(resolvedDefaultMonospace);
|
||||
QLOG_DEBUG() << "Detected default console font:" << resolvedDefaultMonospace
|
||||
<< ", substitutions:" << resolvedFont.substitutions().join(',');
|
||||
m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in test mode, we don't have UI, so we don't do any font resolving
|
||||
m_settings->registerSetting("ConsoleFont", defaultMonospace);
|
||||
}
|
||||
m_settings->registerSetting("ConsoleFontSize", 11);
|
||||
|
||||
// FTB
|
||||
m_settings->registerSetting("TrackFTBInstances", false);
|
||||
QString ftbDataDefault;
|
||||
#ifdef Q_OS_LINUX
|
||||
QString ftbDefault = ftbDataDefault = QDir::home().absoluteFilePath(".ftblauncher");
|
||||
#elif defined(Q_OS_WIN32)
|
||||
wchar_t buf[APPDATA_BUFFER_SIZE];
|
||||
wchar_t newBuf[APPDATA_BUFFER_SIZE];
|
||||
QString ftbDefault, newFtbDefault, oldFtbDefault;
|
||||
if (!GetEnvironmentVariableW(L"LOCALAPPDATA", newBuf, APPDATA_BUFFER_SIZE))
|
||||
{
|
||||
QLOG_FATAL() << "Your LOCALAPPDATA folder is missing! If you are on windows, this "
|
||||
"means your system is broken. If you aren't on windows, how the **** "
|
||||
"are you running the windows build????";
|
||||
}
|
||||
else
|
||||
{
|
||||
newFtbDefault = QDir(QString::fromWCharArray(newBuf)).absoluteFilePath("ftblauncher");
|
||||
}
|
||||
if (!GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE))
|
||||
{
|
||||
QLOG_FATAL() << "Your APPDATA folder is missing! If you are on windows, this means "
|
||||
"your system is broken. If you aren't on windows, how the **** are you "
|
||||
"running the windows build????";
|
||||
}
|
||||
else
|
||||
{
|
||||
oldFtbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher");
|
||||
}
|
||||
if (QFile::exists(QDir(newFtbDefault).absoluteFilePath("ftblaunch.cfg")))
|
||||
{
|
||||
QLOG_INFO() << "Old FTB setup";
|
||||
ftbDefault = ftbDataDefault = oldFtbDefault;
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "New FTB setup";
|
||||
ftbDefault = oldFtbDefault;
|
||||
ftbDataDefault = newFtbDefault;
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
QString ftbDefault = ftbDataDefault =
|
||||
PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
|
||||
#endif
|
||||
m_settings->registerSetting("FTBLauncherDataRoot", ftbDataDefault);
|
||||
m_settings->registerSetting("FTBLauncherRoot", ftbDefault);
|
||||
QLOG_INFO() << "FTB Launcher paths:" << m_settings->get("FTBLauncherDataRoot").toString()
|
||||
<< "and" << m_settings->get("FTBLauncherRoot").toString();
|
||||
|
||||
m_settings->registerSetting("FTBRoot");
|
||||
if (m_settings->get("FTBRoot").isNull())
|
||||
{
|
||||
QString ftbRoot;
|
||||
QFile f(QDir(m_settings->get("FTBLauncherRoot").toString())
|
||||
.absoluteFilePath("ftblaunch.cfg"));
|
||||
QLOG_INFO() << "Attempting to read" << f.fileName();
|
||||
if (f.open(QFile::ReadOnly))
|
||||
{
|
||||
const QString data = QString::fromLatin1(f.readAll());
|
||||
QRegularExpression exp("installPath=(.*)");
|
||||
ftbRoot = QDir::cleanPath(exp.match(data).captured(1));
|
||||
#ifdef Q_OS_WIN32
|
||||
if (!ftbRoot.isEmpty())
|
||||
{
|
||||
if (ftbRoot.at(0).isLetter() && ftbRoot.size() > 1 && ftbRoot.at(1) == '/')
|
||||
{
|
||||
ftbRoot.remove(1, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ftbRoot.isEmpty())
|
||||
{
|
||||
QLOG_INFO() << "Failed to get FTB root path";
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "FTB is installed at" << ftbRoot;
|
||||
m_settings->set("FTBRoot", ftbRoot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_WARN() << "Couldn't open" << f.fileName() << ":" << f.errorString();
|
||||
QLOG_WARN() << "This is perfectly normal if you don't have FTB installed";
|
||||
}
|
||||
}
|
||||
|
||||
// Folders
|
||||
m_settings->registerSetting("InstanceDir", "instances");
|
||||
m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
|
||||
m_settings->registerSetting({"LWJGLDir", "LwjglDir"}, "lwjgl");
|
||||
m_settings->registerSetting("IconsDir", "icons");
|
||||
|
||||
// Editors
|
||||
m_settings->registerSetting("JsonEditor", QString());
|
||||
|
||||
// Language
|
||||
m_settings->registerSetting("Language", QLocale(QLocale::system().language()).bcp47Name());
|
||||
|
||||
// Console
|
||||
m_settings->registerSetting("ShowConsole", true);
|
||||
m_settings->registerSetting("RaiseConsole", true);
|
||||
m_settings->registerSetting("AutoCloseConsole", true);
|
||||
m_settings->registerSetting("LogPrePostOutput", true);
|
||||
|
||||
// Console Colors
|
||||
// m_settings->registerSetting("SysMessageColor", QColor(Qt::blue));
|
||||
// m_settings->registerSetting("StdOutColor", QColor(Qt::black));
|
||||
// m_settings->registerSetting("StdErrColor", QColor(Qt::red));
|
||||
|
||||
// Window Size
|
||||
m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
|
||||
m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
|
||||
m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
|
||||
|
||||
// Proxy Settings
|
||||
m_settings->registerSetting("ProxyType", "Default");
|
||||
m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1");
|
||||
m_settings->registerSetting("ProxyPort", 8080);
|
||||
m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, "");
|
||||
m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, "");
|
||||
|
||||
// Memory
|
||||
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
|
||||
m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
|
||||
m_settings->registerSetting("PermGen", 64);
|
||||
|
||||
// Java Settings
|
||||
m_settings->registerSetting("JavaPath", "");
|
||||
m_settings->registerSetting("LastHostname", "");
|
||||
m_settings->registerSetting("JavaDetectionHack", "");
|
||||
m_settings->registerSetting("JvmArgs", "");
|
||||
|
||||
// Custom Commands
|
||||
m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
|
||||
m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, "");
|
||||
|
||||
// The cat
|
||||
m_settings->registerSetting("TheCat", false);
|
||||
|
||||
m_settings->registerSetting("InstSortMode", "Name");
|
||||
m_settings->registerSetting("SelectedInstance", QString());
|
||||
|
||||
// Window state and geometry
|
||||
m_settings->registerSetting("MainWindowState", "");
|
||||
m_settings->registerSetting("MainWindowGeometry", "");
|
||||
|
||||
m_settings->registerSetting("ConsoleWindowState", "");
|
||||
m_settings->registerSetting("ConsoleWindowGeometry", "");
|
||||
|
||||
m_settings->registerSetting("SettingsGeometry", "");
|
||||
|
||||
m_settings->registerSetting("PagedGeometry", "");
|
||||
}
|
||||
|
||||
void MultiMC::initHttpMetaCache()
|
||||
{
|
||||
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("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir(root()).absolutePath());
|
||||
m_metacache->addBase("translations", QDir(staticData() + "/translations").absolutePath());
|
||||
m_metacache->Load();
|
||||
}
|
||||
|
||||
void MultiMC::updateProxySettings()
|
||||
{
|
||||
QString proxyTypeStr = settings()->get("ProxyType").toString();
|
||||
|
||||
// Get the proxy settings from the settings object.
|
||||
QString addr = settings()->get("ProxyAddr").toString();
|
||||
int port = settings()->get("ProxyPort").value<qint16>();
|
||||
QString user = settings()->get("ProxyUser").toString();
|
||||
QString pass = settings()->get("ProxyPass").toString();
|
||||
|
||||
// Set the application proxy settings.
|
||||
if (proxyTypeStr == "SOCKS5")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, pass));
|
||||
}
|
||||
else if (proxyTypeStr == "HTTP")
|
||||
{
|
||||
QNetworkProxy::setApplicationProxy(
|
||||
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, pass));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
QLOG_INFO() << "Detecting proxy settings...";
|
||||
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
|
||||
if (m_qnam.get())
|
||||
m_qnam->setProxy(proxy);
|
||||
QString proxyDesc;
|
||||
if (proxy.type() == QNetworkProxy::NoProxy)
|
||||
{
|
||||
QLOG_INFO() << "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());
|
||||
QLOG_INFO() << proxyDesc;
|
||||
}
|
||||
|
||||
std::shared_ptr<IconList> MultiMC::icons()
|
||||
{
|
||||
if (!m_icons)
|
||||
{
|
||||
m_icons.reset(new IconList);
|
||||
}
|
||||
return m_icons;
|
||||
}
|
||||
|
||||
std::shared_ptr<LWJGLVersionList> MultiMC::lwjgllist()
|
||||
{
|
||||
if (!m_lwjgllist)
|
||||
{
|
||||
m_lwjgllist.reset(new LWJGLVersionList());
|
||||
}
|
||||
return m_lwjgllist;
|
||||
}
|
||||
|
||||
std::shared_ptr<ForgeVersionList> MultiMC::forgelist()
|
||||
{
|
||||
if (!m_forgelist)
|
||||
{
|
||||
m_forgelist.reset(new ForgeVersionList());
|
||||
}
|
||||
return m_forgelist;
|
||||
}
|
||||
|
||||
std::shared_ptr<LiteLoaderVersionList> MultiMC::liteloaderlist()
|
||||
{
|
||||
if (!m_liteloaderlist)
|
||||
{
|
||||
m_liteloaderlist.reset(new LiteLoaderVersionList());
|
||||
}
|
||||
return m_liteloaderlist;
|
||||
}
|
||||
|
||||
std::shared_ptr<MinecraftVersionList> MultiMC::minecraftlist()
|
||||
{
|
||||
if (!m_minecraftlist)
|
||||
{
|
||||
m_minecraftlist.reset(new MinecraftVersionList());
|
||||
}
|
||||
return m_minecraftlist;
|
||||
}
|
||||
|
||||
std::shared_ptr<JavaVersionList> MultiMC::javalist()
|
||||
{
|
||||
if (!m_javalist)
|
||||
{
|
||||
m_javalist.reset(new JavaVersionList());
|
||||
}
|
||||
return m_javalist;
|
||||
}
|
||||
|
||||
std::shared_ptr<URNResolver> MultiMC::resolver()
|
||||
{
|
||||
if (!m_resolver)
|
||||
{
|
||||
m_resolver.reset(new URNResolver());
|
||||
}
|
||||
return m_resolver;
|
||||
}
|
||||
|
||||
void MultiMC::installUpdates(const QString updateFilesDir, UpdateFlags flags)
|
||||
{
|
||||
// if we are going to update on exit, save the params now
|
||||
if (flags & OnExit)
|
||||
{
|
||||
m_updateOnExitPath = updateFilesDir;
|
||||
m_updateOnExitFlags = flags & ~OnExit;
|
||||
return;
|
||||
}
|
||||
// otherwise if there already were some params for on exit update, clear them and continue
|
||||
else if (m_updateOnExitPath.size())
|
||||
{
|
||||
m_updateOnExitFlags = None;
|
||||
m_updateOnExitPath.clear();
|
||||
}
|
||||
QLOG_INFO() << "Installing updates.";
|
||||
#ifdef WINDOWS
|
||||
QString finishCmd = MMC->applicationFilePath();
|
||||
QString updaterBinary = PathCombine(bin(), "updater.exe");
|
||||
#elif LINUX
|
||||
QString finishCmd = PathCombine(root(), "MultiMC");
|
||||
QString updaterBinary = PathCombine(bin(), "updater");
|
||||
#elif OSX
|
||||
QString finishCmd = MMC->applicationFilePath();
|
||||
QString updaterBinary = PathCombine(bin(), "updater");
|
||||
#else
|
||||
#error Unsupported operating system.
|
||||
#endif
|
||||
|
||||
QStringList args;
|
||||
// ./updater --install-dir $INSTALL_DIR --package-dir $UPDATEFILES_DIR --script
|
||||
// $UPDATEFILES_DIR/file_list.xml --wait $PID --mode main
|
||||
args << "--install-dir" << root();
|
||||
args << "--package-dir" << updateFilesDir;
|
||||
args << "--script" << PathCombine(updateFilesDir, "file_list.xml");
|
||||
args << "--wait" << QString::number(MMC->applicationPid());
|
||||
if (flags & DryRun)
|
||||
args << "--dry-run";
|
||||
if (flags & RestartOnFinish)
|
||||
{
|
||||
args << "--finish-cmd" << finishCmd;
|
||||
args << "--finish-dir" << data();
|
||||
}
|
||||
QLOG_INFO() << "Running updater with command" << updaterBinary << args.join(" ");
|
||||
QFile::setPermissions(updaterBinary, (QFileDevice::Permission)0x7755);
|
||||
|
||||
if (!QProcess::startDetached(updaterBinary, args /*, root()*/))
|
||||
{
|
||||
QLOG_ERROR() << "Failed to start the updater process!";
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we've started the updater, quit MultiMC.
|
||||
MMC->quit();
|
||||
}
|
||||
|
||||
void MultiMC::onExit()
|
||||
{
|
||||
if (m_updateOnExitPath.size())
|
||||
{
|
||||
installUpdates(m_updateOnExitPath, m_updateOnExitFlags);
|
||||
}
|
||||
}
|
||||
|
||||
bool MultiMC::openJsonEditor(const QString &filename)
|
||||
{
|
||||
const QString file = QDir::current().absoluteFilePath(filename);
|
||||
if (m_settings->get("JsonEditor").toString().isEmpty())
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(file));
|
||||
}
|
||||
else
|
||||
{
|
||||
return QProcess::startDetached(m_settings->get("JsonEditor").toString(), QStringList()
|
||||
<< file);
|
||||
}
|
||||
}
|
||||
|
||||
#include "MultiMC.moc"
|
||||
228
MultiMC.h
228
MultiMC.h
@@ -1,228 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <memory>
|
||||
#include "logger/QsLog.h"
|
||||
#include "logger/QsLogDest.h"
|
||||
#include <QFlag>
|
||||
|
||||
class MinecraftVersionList;
|
||||
class LWJGLVersionList;
|
||||
class HttpMetaCache;
|
||||
class SettingsObject;
|
||||
class InstanceList;
|
||||
class MojangAccountList;
|
||||
class IconList;
|
||||
class QNetworkAccessManager;
|
||||
class ForgeVersionList;
|
||||
class LiteLoaderVersionList;
|
||||
class JavaVersionList;
|
||||
class UpdateChecker;
|
||||
class NotificationChecker;
|
||||
class NewsChecker;
|
||||
class StatusChecker;
|
||||
class BaseProfilerFactory;
|
||||
class BaseDetachedToolFactory;
|
||||
class URNResolver;
|
||||
class TranslationDownloader;
|
||||
|
||||
#if defined(MMC)
|
||||
#undef MMC
|
||||
#endif
|
||||
#define MMC (static_cast<MultiMC *>(QCoreApplication::instance()))
|
||||
|
||||
enum UpdateFlag
|
||||
{
|
||||
None = 0x0,
|
||||
RestartOnFinish = 0x1,
|
||||
DryRun = 0x2,
|
||||
OnExit = 0x4
|
||||
};
|
||||
Q_DECLARE_FLAGS(UpdateFlags, UpdateFlag);
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(UpdateFlags);
|
||||
|
||||
class MultiMC : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Status
|
||||
{
|
||||
Failed,
|
||||
Succeeded,
|
||||
Initialized
|
||||
};
|
||||
|
||||
public:
|
||||
MultiMC(int &argc, char **argv, bool test_mode = false);
|
||||
virtual ~MultiMC();
|
||||
|
||||
std::shared_ptr<SettingsObject> settings()
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
std::shared_ptr<InstanceList> instances()
|
||||
{
|
||||
return m_instances;
|
||||
}
|
||||
|
||||
std::shared_ptr<MojangAccountList> accounts()
|
||||
{
|
||||
return m_accounts;
|
||||
}
|
||||
|
||||
std::shared_ptr<IconList> icons();
|
||||
|
||||
Status status()
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
std::shared_ptr<QNetworkAccessManager> qnam()
|
||||
{
|
||||
return m_qnam;
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpMetaCache> metacache()
|
||||
{
|
||||
return m_metacache;
|
||||
}
|
||||
|
||||
std::shared_ptr<UpdateChecker> updateChecker()
|
||||
{
|
||||
return m_updateChecker;
|
||||
}
|
||||
|
||||
std::shared_ptr<NotificationChecker> notificationChecker()
|
||||
{
|
||||
return m_notificationChecker;
|
||||
}
|
||||
|
||||
std::shared_ptr<NewsChecker> newsChecker()
|
||||
{
|
||||
return m_newsChecker;
|
||||
}
|
||||
|
||||
std::shared_ptr<StatusChecker> statusChecker()
|
||||
{
|
||||
return m_statusChecker;
|
||||
}
|
||||
|
||||
std::shared_ptr<LWJGLVersionList> lwjgllist();
|
||||
|
||||
std::shared_ptr<ForgeVersionList> forgelist();
|
||||
|
||||
std::shared_ptr<LiteLoaderVersionList> liteloaderlist();
|
||||
|
||||
std::shared_ptr<MinecraftVersionList> minecraftlist();
|
||||
|
||||
std::shared_ptr<JavaVersionList> javalist();
|
||||
|
||||
std::shared_ptr<URNResolver> resolver();
|
||||
|
||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> profilers()
|
||||
{
|
||||
return m_profilers;
|
||||
}
|
||||
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> tools()
|
||||
{
|
||||
return m_tools;
|
||||
}
|
||||
|
||||
void installUpdates(const QString updateFilesDir, UpdateFlags flags = None);
|
||||
|
||||
/*!
|
||||
* Updates the application proxy settings from the settings object.
|
||||
*/
|
||||
void updateProxySettings();
|
||||
|
||||
/*!
|
||||
* Opens a json file using either a system default editor, or, if note empty, the editor
|
||||
* specified in the settings
|
||||
*/
|
||||
bool openJsonEditor(const QString &filename);
|
||||
|
||||
/// this is the static data. it stores things that don't move.
|
||||
const QString &staticData()
|
||||
{
|
||||
return staticDataPath;
|
||||
}
|
||||
/// this is the root of the 'installation'. Used for automatic updates
|
||||
const QString &root()
|
||||
{
|
||||
return rootPath;
|
||||
}
|
||||
/// this is the where the binary files reside
|
||||
const QString &bin()
|
||||
{
|
||||
return binPath;
|
||||
}
|
||||
/// this is the work/data path. All user data is here.
|
||||
const QString &data()
|
||||
{
|
||||
return dataPath;
|
||||
}
|
||||
/**
|
||||
* this is the original work path before it was changed by the adjustment mechanism
|
||||
*/
|
||||
const QString &origcwd()
|
||||
{
|
||||
return origcwdPath;
|
||||
}
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* Do all the things that should be done before we exit
|
||||
*/
|
||||
void onExit();
|
||||
|
||||
private:
|
||||
void initLogger();
|
||||
|
||||
void initGlobalSettings(bool test_mode);
|
||||
|
||||
void initHttpMetaCache();
|
||||
|
||||
void initTranslations();
|
||||
|
||||
private:
|
||||
friend class UpdateCheckerTest;
|
||||
friend class DownloadUpdateTaskTest;
|
||||
|
||||
std::shared_ptr<QTranslator> m_qt_translator;
|
||||
std::shared_ptr<QTranslator> m_mmc_translator;
|
||||
std::shared_ptr<SettingsObject> m_settings;
|
||||
std::shared_ptr<InstanceList> m_instances;
|
||||
std::shared_ptr<UpdateChecker> m_updateChecker;
|
||||
std::shared_ptr<NotificationChecker> m_notificationChecker;
|
||||
std::shared_ptr<NewsChecker> m_newsChecker;
|
||||
std::shared_ptr<StatusChecker> m_statusChecker;
|
||||
std::shared_ptr<MojangAccountList> m_accounts;
|
||||
std::shared_ptr<IconList> m_icons;
|
||||
std::shared_ptr<QNetworkAccessManager> m_qnam;
|
||||
std::shared_ptr<HttpMetaCache> m_metacache;
|
||||
std::shared_ptr<LWJGLVersionList> m_lwjgllist;
|
||||
std::shared_ptr<ForgeVersionList> m_forgelist;
|
||||
std::shared_ptr<LiteLoaderVersionList> m_liteloaderlist;
|
||||
std::shared_ptr<MinecraftVersionList> m_minecraftlist;
|
||||
std::shared_ptr<JavaVersionList> m_javalist;
|
||||
std::shared_ptr<URNResolver> m_resolver;
|
||||
std::shared_ptr<TranslationDownloader> m_translationChecker;
|
||||
|
||||
QMap<QString, std::shared_ptr<BaseProfilerFactory>> m_profilers;
|
||||
QMap<QString, std::shared_ptr<BaseDetachedToolFactory>> m_tools;
|
||||
|
||||
QsLogging::DestinationPtr m_fileDestination;
|
||||
QsLogging::DestinationPtr m_debugDestination;
|
||||
|
||||
QString m_updateOnExitPath;
|
||||
UpdateFlags m_updateOnExitFlags = None;
|
||||
|
||||
QString rootPath;
|
||||
QString staticDataPath;
|
||||
QString binPath;
|
||||
QString dataPath;
|
||||
QString origcwdPath;
|
||||
|
||||
Status m_status = MultiMC::Failed;
|
||||
};
|
||||
32
README.md
32
README.md
@@ -1,17 +1,33 @@
|
||||

|
||||
|
||||
MultiMC 5 [](https://travis-ci.org/MultiMC/MultiMC5)
|
||||
MultiMC 5
|
||||
=========
|
||||
|
||||
MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping.
|
||||
MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping. Here are the current [features](https://github.com/MultiMC/MultiMC5/wiki#features) of MultiMC.
|
||||
|
||||
## Building
|
||||
Check [BUILD.md](BUILD.md) for build instructions.
|
||||
|
||||
## Contributing
|
||||
The repository is currently managed by @peterix. If you'd like to contribute to the project please talk to us on IRC (Esper/#MultiMC) first! This helps us organise ideas and keep in contact with you, and we're unlikely to accept anything blindly.
|
||||
## Development
|
||||
The project uses C++ and Qt5 as the language and base framework. This might seem odd in the Minecraft community, but allows using 25MB of RAM, where other tools use an excessive amount of resources for no reason.
|
||||
|
||||
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted, but there are issues with it on Windows. If you have trouble setting it up, check [.clang-format](.clang-format) manually. We don't accept pull requests with poor formatting. If you have questions, talk to us on IRC (Esper/#MultiMC) _before_ submitting a pull request.
|
||||
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)
|
||||
|
||||
### Building
|
||||
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
|
||||
|
||||
The ci server is running at [ci.multimc.org](http://ci.multimc.org), where you can watch the builds happen in (or very close to) real time. It can also serve as a nice reference on how to set up the build environment on your end.
|
||||
|
||||
According to travis.ci, the builds are currently [](https://travis-ci.org/MultiMC/MultiMC5)
|
||||
|
||||
### Code formatting
|
||||
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted.
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
## 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.
|
||||
@@ -22,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
|
||||
|
||||
|
||||
## License
|
||||
Copyright © 2013-2014 MultiMC Contributors
|
||||
Copyright © 2013-2017 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,77 +0,0 @@
|
||||
/* Copyright 2013-2014 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.
|
||||
*/
|
||||
|
||||
// CAUTION:
|
||||
// This file contains all manner of hackery and insanity.
|
||||
// I will not be responsible for any loss of sanity due to reading this code.
|
||||
// Here be dragons!
|
||||
|
||||
#include "WinBacktrace.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifndef __i386__
|
||||
#error WinBacktrace is only supported on x86 architectures.
|
||||
#endif
|
||||
|
||||
// We need to do some crazy shit to walk through the stack.
|
||||
// Windows unwinds the stack when an exception is thrown, so we
|
||||
// need to examine the EXCEPTION_POINTERS's CONTEXT.
|
||||
size_t getBacktrace(StackFrame *stack, size_t size, CONTEXT ctx)
|
||||
{
|
||||
// Written using information and a bit of pseudocode from
|
||||
// http://www.eptacom.net/pubblicazioni/pub_eng/except.html
|
||||
// This is probably one of the most horrifying things I've ever written.
|
||||
|
||||
// This tracks whether the current EBP is valid.
|
||||
// When an invalid EBP is encountered, we stop walking the stack.
|
||||
bool validEBP = true;
|
||||
DWORD ebp = ctx.Ebp; // The current EBP (Extended Base Pointer)
|
||||
DWORD eip = ctx.Eip;
|
||||
int i;
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
if (ebp & 3)
|
||||
validEBP = false;
|
||||
// FIXME: This function is obsolete, according to MSDN.
|
||||
else if (IsBadReadPtr((void*) ebp, 8))
|
||||
validEBP = false;
|
||||
|
||||
if (!validEBP) break;
|
||||
|
||||
// Find the caller.
|
||||
// On the first iteration, the caller is whatever EIP points to.
|
||||
// On successive iterations, the caller is the byte after EBP.
|
||||
BYTE* caller = !i ? (BYTE*)eip : *((BYTE**) ebp + 1);
|
||||
// The first ebp is the EBP from the CONTEXT.
|
||||
// On successive iterations, the EBP is the DWORD that the previous EBP points to.
|
||||
ebp = !i ? ebp : *(DWORD*)ebp;
|
||||
|
||||
// Find the caller's module.
|
||||
// We'll use VirtualQuery to get information about the caller's address.
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
VirtualQuery(caller, &mbi, sizeof(mbi));
|
||||
|
||||
// We can get the instance handle from the allocation base.
|
||||
HINSTANCE hInst = (HINSTANCE)mbi.AllocationBase;
|
||||
|
||||
// If the handle is 0, then the EBP is invalid.
|
||||
if (hInst == 0) validEBP = false;
|
||||
// Otherwise, dump info about the caller.
|
||||
else stack[i].address = (void*)caller;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/* Copyright 2013-2014 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 <windows.h>
|
||||
|
||||
#ifndef SF_STR_LEN
|
||||
// The max length of all strings in the StackFrame struct.
|
||||
// Because it must be stack allocated, this must be known at compile time.
|
||||
// Stuff longer than this will be truncated.
|
||||
// Defaults to 4096 (4kB)
|
||||
#define SF_STR_LEN 4096
|
||||
#endif
|
||||
|
||||
// Data structure for holding information about a stack frame.
|
||||
// There's some more hackery in here so it can be allocated on the stack.
|
||||
struct StackFrame
|
||||
{
|
||||
// The address of this stack frame.
|
||||
void* address;
|
||||
|
||||
// The name of the function at this address.
|
||||
char funcName[SF_STR_LEN];
|
||||
};
|
||||
|
||||
// This function walks through the given CONTEXT structure, extracting a
|
||||
// backtrace from it.
|
||||
// The backtrace will be put into the array given by the `stack` argument
|
||||
// with a maximum length of `size`.
|
||||
// This function returns the size of the backtrace retrieved.
|
||||
size_t getBacktrace(StackFrame* stack, size_t size, CONTEXT ctx);
|
||||
28
api/gui/CMakeLists.txt
Normal file
28
api/gui/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
project(MultiMC_gui LANGUAGES CXX)
|
||||
|
||||
set(GUI_SOURCES
|
||||
DesktopServices.h
|
||||
DesktopServices.cpp
|
||||
|
||||
# Icons
|
||||
icons/MMCIcon.h
|
||||
icons/MMCIcon.cpp
|
||||
icons/IconList.h
|
||||
icons/IconList.cpp
|
||||
|
||||
SkinUtils.cpp
|
||||
SkinUtils.h
|
||||
)
|
||||
################################ COMPILE ################################
|
||||
|
||||
add_library(MultiMC_gui SHARED ${GUI_SOURCES})
|
||||
set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
generate_export_header(MultiMC_gui)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
|
||||
qt5_use_modules(MultiMC_gui Gui)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
149
api/gui/DesktopServices.cpp
Normal file
149
api/gui/DesktopServices.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "DesktopServices.h"
|
||||
#include <QDir>
|
||||
#include <QDesktopServices>
|
||||
#include <QProcess>
|
||||
#include <QDebug>
|
||||
|
||||
/**
|
||||
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
|
||||
*/
|
||||
#if defined(Q_OS_LINUX)
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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");
|
||||
|
||||
// open the URL
|
||||
auto status = callable();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
#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()));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &path)
|
||||
{
|
||||
qDebug() << "Opening file" << path;
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
|
||||
{
|
||||
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);
|
||||
#else
|
||||
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(' ');
|
||||
#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);
|
||||
#else
|
||||
return QProcess::startDetached(application, args, workingDirectory, pid);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool openUrl(const QUrl &url)
|
||||
{
|
||||
qDebug() << "Opening URL" << url.toString();
|
||||
auto f = [&]()
|
||||
{
|
||||
return QDesktopServices::openUrl(url);
|
||||
};
|
||||
#if defined(Q_OS_LINUX)
|
||||
return IndirectOpen(f);
|
||||
#else
|
||||
return f();
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
37
api/gui/DesktopServices.h
Normal file
37
api/gui/DesktopServices.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <QUrl>
|
||||
#include <QString>
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
/**
|
||||
* This wraps around QDesktopServices and adds workarounds where needed
|
||||
* Use this instead of QDesktopServices!
|
||||
*/
|
||||
namespace DesktopServices
|
||||
{
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -13,9 +13,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "MultiMC.h"
|
||||
#include "logic/SkinUtils.h"
|
||||
#include "SkinUtils.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "Env.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
@@ -29,7 +29,7 @@ namespace SkinUtils
|
||||
*/
|
||||
QPixmap getFaceFromCache(QString username, int height, int width)
|
||||
{
|
||||
QFile fskin(MMC->metacache()
|
||||
QFile fskin(ENV.metacache()
|
||||
->resolveEntry("skins", username + ".png")
|
||||
->getFullPath());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -17,7 +17,9 @@
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
namespace SkinUtils
|
||||
{
|
||||
QPixmap getFaceFromCache(QString username, int height = 64, int width = 64);
|
||||
QPixmap MULTIMC_GUI_EXPORT getFaceFromCache(QString id, int height = 64, int width = 64);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -14,27 +14,34 @@
|
||||
*/
|
||||
|
||||
#include "IconList.h"
|
||||
#include <pathutils.h>
|
||||
#include "logic/settings/SettingsObject.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QMap>
|
||||
#include <QEventLoop>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <MultiMC.h>
|
||||
#include <logic/settings/Setting.h>
|
||||
#include <QSet>
|
||||
#include <QDebug>
|
||||
|
||||
#define MAX_SIZE 1024
|
||||
|
||||
IconList::IconList(QObject *parent) : QAbstractListModel(parent)
|
||||
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
QSet<QString> builtinNames;
|
||||
|
||||
// add builtin icons
|
||||
QDir instance_icons(":/icons/instances/");
|
||||
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)
|
||||
{
|
||||
QString key = file_info.baseName();
|
||||
addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
|
||||
builtinNames.insert(file_info.baseName());
|
||||
}
|
||||
}
|
||||
for(auto & builtinName : builtinNames)
|
||||
{
|
||||
addThemeIcon(builtinName);
|
||||
}
|
||||
|
||||
m_watcher.reset(new QFileSystemWatcher());
|
||||
@@ -43,10 +50,6 @@ IconList::IconList(QObject *parent) : QAbstractListModel(parent)
|
||||
SLOT(directoryChanged(QString)));
|
||||
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
|
||||
auto setting = MMC->settings()->getSetting("IconsDir");
|
||||
QString path = setting->get().toString();
|
||||
connect(setting.get(), SIGNAL(SettingChanged(const Setting &, QVariant)),
|
||||
SLOT(SettingChanged(const Setting &, QVariant)));
|
||||
directoryChanged(path);
|
||||
}
|
||||
|
||||
@@ -62,7 +65,7 @@ void IconList::directoryChanged(const QString &path)
|
||||
startWatching();
|
||||
}
|
||||
if(!m_dir.exists())
|
||||
if(!ensureFolderPathExists(m_dir.absolutePath()))
|
||||
if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
|
||||
return;
|
||||
m_dir.refresh();
|
||||
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
|
||||
@@ -75,9 +78,9 @@ void IconList::directoryChanged(const QString &path)
|
||||
QList<QString> current_list;
|
||||
for (auto &it : icons)
|
||||
{
|
||||
if (!it.has(MMCIcon::FileBased))
|
||||
if (!it.has(IconType::FileBased))
|
||||
continue;
|
||||
current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
|
||||
current_list.push_back(it.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
QSet<QString> current_set = current_list.toSet();
|
||||
|
||||
@@ -89,14 +92,14 @@ void IconList::directoryChanged(const QString &path)
|
||||
|
||||
for (auto remove : to_remove)
|
||||
{
|
||||
QLOG_INFO() << "Removing " << remove;
|
||||
qDebug() << "Removing " << remove;
|
||||
QFileInfo rmfile(remove);
|
||||
QString key = rmfile.baseName();
|
||||
int idx = getIconIndex(key);
|
||||
if (idx == -1)
|
||||
continue;
|
||||
icons[idx].remove(MMCIcon::FileBased);
|
||||
if (icons[idx].type() == MMCIcon::ToBeDeleted)
|
||||
icons[idx].remove(IconType::FileBased);
|
||||
if (icons[idx].type() == IconType::ToBeDeleted)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
icons.remove(idx);
|
||||
@@ -113,10 +116,10 @@ void IconList::directoryChanged(const QString &path)
|
||||
|
||||
for (auto add : to_add)
|
||||
{
|
||||
QLOG_INFO() << "Adding " << add;
|
||||
qDebug() << "Adding " << add;
|
||||
QFileInfo addfile(add);
|
||||
QString key = addfile.baseName();
|
||||
if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
|
||||
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
|
||||
{
|
||||
m_watcher->addPath(add);
|
||||
emit iconUpdated(key);
|
||||
@@ -126,7 +129,7 @@ void IconList::directoryChanged(const QString &path)
|
||||
|
||||
void IconList::fileChanged(const QString &path)
|
||||
{
|
||||
QLOG_INFO() << "Checking " << path;
|
||||
qDebug() << "Checking " << path;
|
||||
QFileInfo checkfile(path);
|
||||
if (!checkfile.exists())
|
||||
return;
|
||||
@@ -138,7 +141,7 @@ void IconList::fileChanged(const QString &path)
|
||||
if (!icon.availableSizes().size())
|
||||
return;
|
||||
|
||||
icons[idx].m_images[MMCIcon::FileBased].icon = icon;
|
||||
icons[idx].m_images[IconType::FileBased].icon = icon;
|
||||
dataChanged(index(idx), index(idx));
|
||||
emit iconUpdated(key);
|
||||
}
|
||||
@@ -154,15 +157,15 @@ void IconList::SettingChanged(const Setting &setting, QVariant value)
|
||||
void IconList::startWatching()
|
||||
{
|
||||
auto abs_path = m_dir.absolutePath();
|
||||
ensureFolderPathExists(abs_path);
|
||||
FS::ensureFolderPathExists(abs_path);
|
||||
is_watching = m_watcher->addPath(abs_path);
|
||||
if (is_watching)
|
||||
{
|
||||
QLOG_INFO() << "Started watching " << abs_path;
|
||||
qDebug() << "Started watching " << abs_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
QLOG_INFO() << "Failed to start watching " << abs_path;
|
||||
qDebug() << "Failed to start watching " << abs_path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,14 +251,14 @@ int IconList::rowCount(const QModelIndex &parent) const
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(QStringList iconFiles)
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
{
|
||||
for (QString file : iconFiles)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
continue;
|
||||
QString target = PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
|
||||
@@ -266,20 +269,65 @@ void IconList::installIcons(QStringList iconFiles)
|
||||
}
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(QString key)
|
||||
bool IconList::iconFileExists(const QString &key) const
|
||||
{
|
||||
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];
|
||||
}
|
||||
|
||||
bool IconList::deleteIcon(const QString &key)
|
||||
{
|
||||
int iconIdx = getIconIndex(key);
|
||||
if (iconIdx == -1)
|
||||
return false;
|
||||
auto &iconEntry = icons[iconIdx];
|
||||
if (iconEntry.has(MMCIcon::FileBased))
|
||||
if (iconEntry.has(IconType::FileBased))
|
||||
{
|
||||
return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
|
||||
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -310,6 +358,14 @@ bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type ty
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
void IconList::reindex()
|
||||
{
|
||||
name_index.clear();
|
||||
@@ -321,7 +377,7 @@ void IconList::reindex()
|
||||
}
|
||||
}
|
||||
|
||||
QIcon IconList::getIcon(QString key)
|
||||
QIcon IconList::getIcon(const QString &key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
@@ -336,15 +392,12 @@ QIcon IconList::getIcon(QString key)
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon IconList::getBigIcon(QString key)
|
||||
QIcon IconList::getBigIcon(const QString &key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
if (icon_index == -1)
|
||||
key = "infinity";
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex(key);
|
||||
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
|
||||
|
||||
if (icon_index == -1)
|
||||
return QIcon();
|
||||
@@ -353,12 +406,9 @@ QIcon IconList::getBigIcon(QString key)
|
||||
return QIcon(bigone);
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(QString key)
|
||||
int IconList::getIconIndex(const QString &key) const
|
||||
{
|
||||
if (key == "default")
|
||||
key = "infinity";
|
||||
|
||||
auto iter = name_index.find(key);
|
||||
auto iter = name_index.find(key == "default" ? "infinity" : key);
|
||||
if (iter != name_index.end())
|
||||
return *iter;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -22,34 +22,42 @@
|
||||
#include <QtGui/QIcon>
|
||||
#include <memory>
|
||||
#include "MMCIcon.h"
|
||||
#include "logic/settings/Setting.h"
|
||||
#include "settings/Setting.h"
|
||||
#include "Env.h" // there is a global icon list inside Env.
|
||||
#include <icons/IIconList.h>
|
||||
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class IconList : public QAbstractListModel
|
||||
class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconList(QObject *parent = 0);
|
||||
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(QString key);
|
||||
QIcon getBigIcon(QString key);
|
||||
int getIconIndex(QString key);
|
||||
QIcon getIcon(const QString &key) const;
|
||||
QIcon getBigIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
|
||||
bool deleteIcon(QString key);
|
||||
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;
|
||||
virtual Qt::DropActions supportedDropActions() const;
|
||||
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent);
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
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(QStringList iconFiles);
|
||||
void installIcons(const QStringList &iconFiles) override;
|
||||
|
||||
const MMCIcon * icon(const QString &key) const;
|
||||
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
@@ -64,9 +72,10 @@ private:
|
||||
IconList &operator=(const IconList &) = delete;
|
||||
void reindex();
|
||||
|
||||
protected
|
||||
slots:
|
||||
public slots:
|
||||
void directoryChanged(const QString &path);
|
||||
|
||||
protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
104
api/gui/icons/MMCIcon.cpp
Normal file
104
api/gui/icons/MMCIcon.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "MMCIcon.h"
|
||||
#include <QFileInfo>
|
||||
#include <xdgicon.h>
|
||||
|
||||
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 MMCIcon::type() const
|
||||
{
|
||||
return m_current_type;
|
||||
}
|
||||
|
||||
QString MMCIcon::name() const
|
||||
{
|
||||
if (m_name.size())
|
||||
return m_name;
|
||||
return m_key;
|
||||
}
|
||||
|
||||
bool MMCIcon::has(IconType _type) const
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -17,36 +17,33 @@
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QIcon>
|
||||
struct MMCImage
|
||||
#include <icons/IIconList.h>
|
||||
|
||||
#include "multimc_gui_export.h"
|
||||
|
||||
struct MULTIMC_GUI_EXPORT MMCImage
|
||||
{
|
||||
QIcon icon;
|
||||
QString key;
|
||||
QString filename;
|
||||
QDateTime changed;
|
||||
bool present() const
|
||||
{
|
||||
return !icon.isNull();
|
||||
return !icon.isNull() || !key.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct MMCIcon
|
||||
struct MULTIMC_GUI_EXPORT MMCIcon
|
||||
{
|
||||
enum Type : unsigned
|
||||
{
|
||||
Builtin,
|
||||
Transient,
|
||||
FileBased,
|
||||
ICONS_TOTAL,
|
||||
ToBeDeleted
|
||||
};
|
||||
QString m_key;
|
||||
QString m_name;
|
||||
MMCImage m_images[ICONS_TOTAL];
|
||||
Type m_current_type = ToBeDeleted;
|
||||
IconType m_current_type = ToBeDeleted;
|
||||
|
||||
Type type() const;
|
||||
IconType type() const;
|
||||
QString name() const;
|
||||
bool has(Type _type) const;
|
||||
bool has(IconType _type) const;
|
||||
QIcon icon() const;
|
||||
void remove(Type rm_type);
|
||||
void replace(Type new_type, QIcon icon, QString path = QString());
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "logic/BaseInstaller.h"
|
||||
#include "logic/OneSixInstance.h"
|
||||
#include "BaseInstaller.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
|
||||
BaseInstaller::BaseInstaller()
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -17,15 +17,17 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class OneSixInstance;
|
||||
class QDir;
|
||||
class QString;
|
||||
class QObject;
|
||||
class ProgressProvider;
|
||||
struct BaseVersion;
|
||||
class Task;
|
||||
class BaseVersion;
|
||||
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
|
||||
|
||||
class BaseInstaller
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstaller
|
||||
{
|
||||
public:
|
||||
BaseInstaller();
|
||||
@@ -35,7 +37,7 @@ public:
|
||||
virtual bool add(OneSixInstance *to);
|
||||
virtual bool remove(OneSixInstance *from);
|
||||
|
||||
virtual ProgressProvider *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
|
||||
protected:
|
||||
virtual QString id() const = 0;
|
||||
307
api/logic/BaseInstance.cpp
Normal file
307
api/logic/BaseInstance.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "BaseInstance.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
#include "settings/INISettingsObject.h"
|
||||
#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()
|
||||
{
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
|
||||
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
|
||||
}
|
||||
|
||||
QString BaseInstance::getPreLaunchCommand()
|
||||
{
|
||||
return settings()->get("PreLaunchCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getWrapperCommand()
|
||||
{
|
||||
return settings()->get("WrapperCommand").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::getPostExitCommand()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
bool BaseInstance::shouldStopOnConsoleOverflow() const
|
||||
{
|
||||
return settings()->get("ConsoleOverflowStop").toBool();
|
||||
}
|
||||
|
||||
void BaseInstance::iconUpdated(QString key)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
|
||||
{
|
||||
Status status = currentStatus();
|
||||
if(status != newStatus)
|
||||
{
|
||||
m_status = newStatus;
|
||||
emit statusChanged(status, newStatus);
|
||||
}
|
||||
}
|
||||
|
||||
BaseInstance::Status BaseInstance::currentStatus() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
QString BaseInstance::id() const
|
||||
{
|
||||
return QFileInfo(instanceRoot()).fileName();
|
||||
}
|
||||
|
||||
bool BaseInstance::isRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
void BaseInstance::setRunning(bool running)
|
||||
{
|
||||
if(running == m_isRunning)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void BaseInstance::resetTimePlayed()
|
||||
{
|
||||
settings()->reset("totalTimePlayed");
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceType() const
|
||||
{
|
||||
return m_settings->get("InstanceType").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::instanceRoot() const
|
||||
{
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
InstancePtr BaseInstance::getSharedPtr()
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
SettingsObjectPtr BaseInstance::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
}
|
||||
|
||||
bool BaseInstance::canLaunch() const
|
||||
{
|
||||
return (!hasVersionBroken() && !isRunning());
|
||||
}
|
||||
|
||||
bool BaseInstance::reload()
|
||||
{
|
||||
return m_settings->reload();
|
||||
}
|
||||
|
||||
qint64 BaseInstance::lastLaunch() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void BaseInstance::setNotes(QString 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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
QString BaseInstance::iconKey() const
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
QString BaseInstance::name() const
|
||||
{
|
||||
return m_settings->get("name").toString();
|
||||
}
|
||||
|
||||
QString BaseInstance::windowTitle() const
|
||||
{
|
||||
return "MultiMC: " + name();
|
||||
}
|
||||
|
||||
QStringList BaseInstance::extraArguments() const
|
||||
{
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
}
|
||||
|
||||
std::shared_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;
|
||||
}
|
||||
310
api/logic/BaseInstance.h
Normal file
310
api/logic/BaseInstance.h
Normal file
@@ -0,0 +1,310 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <cassert>
|
||||
|
||||
#include <QObject>
|
||||
#include "QObjectPtr.h"
|
||||
#include <QDateTime>
|
||||
#include <QSet>
|
||||
#include <QProcess>
|
||||
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
#include "settings/INIFile.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include "minecraft/auth/MojangAccount.h"
|
||||
#include "MessageLevel.h"
|
||||
#include "pathmatcher/IPathMatcher.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;
|
||||
|
||||
/*!
|
||||
* \brief Base class for instances.
|
||||
* This class implements many functions that are common between instances and
|
||||
* provides a standard interface for all instances.
|
||||
*
|
||||
* To create a new instance type, create a new class inheriting from this class
|
||||
* and implement the pure virtual functions.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
/// no-touchy!
|
||||
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
|
||||
public: /* types */
|
||||
enum class Status
|
||||
{
|
||||
Present,
|
||||
Gone // either nuked or invalidated
|
||||
};
|
||||
|
||||
public:
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
|
||||
virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
|
||||
|
||||
virtual void init() = 0;
|
||||
|
||||
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
|
||||
/// responsible of cleaning up the husk
|
||||
void nuke();
|
||||
|
||||
/***
|
||||
* 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();
|
||||
|
||||
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
|
||||
/// be unique.
|
||||
virtual QString id() const;
|
||||
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const;
|
||||
int64_t totalTimePlayed() const;
|
||||
void resetTimePlayed();
|
||||
|
||||
void setProvider(BaseInstanceProvider * provider);
|
||||
BaseInstanceProvider * provider() const;
|
||||
|
||||
/// get the type of this instance
|
||||
QString instanceType() const;
|
||||
|
||||
/// Path to the instance's root directory.
|
||||
QString instanceRoot() const;
|
||||
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
|
||||
/// Value used for instance window titles
|
||||
QString windowTitle() const;
|
||||
|
||||
QString iconKey() const;
|
||||
void setIconKey(QString val);
|
||||
|
||||
QString notes() const;
|
||||
void setNotes(QString val);
|
||||
|
||||
QString group() const;
|
||||
void setGroupInitial(QString val);
|
||||
void setGroupPost(QString val);
|
||||
|
||||
QString getPreLaunchCommand();
|
||||
QString getPostExitCommand();
|
||||
QString getWrapperCommand();
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
||||
virtual QStringList extraArguments() const;
|
||||
|
||||
virtual QString intendedVersionId() const = 0;
|
||||
virtual bool setIntendedVersionId(QString version) = 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;
|
||||
|
||||
/*!
|
||||
* Whether or not 'the game' should be downloaded when the instance is launched.
|
||||
*/
|
||||
virtual bool shouldUpdate() const = 0;
|
||||
virtual void setShouldUpdate(bool val) = 0;
|
||||
|
||||
/// Traits. Normally inside the version, depends on instance implementation.
|
||||
virtual QSet <QString> traits() = 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());
|
||||
|
||||
InstancePtr getSharedPtr();
|
||||
|
||||
/*!
|
||||
* \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;
|
||||
|
||||
/*!
|
||||
* \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;
|
||||
|
||||
/// returns a valid update task
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
|
||||
/// returns the current launch task (if any)
|
||||
std::shared_ptr<LaunchTask> getLaunchTask();
|
||||
|
||||
/*!
|
||||
* 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;
|
||||
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
*/
|
||||
virtual QProcessEnvironment createEnvironment() = 0;
|
||||
|
||||
/*!
|
||||
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
|
||||
*/
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
|
||||
|
||||
/*!
|
||||
* Returns the root folder to use for looking up log files
|
||||
*/
|
||||
virtual QString getLogFileRoot() = 0;
|
||||
|
||||
virtual QString getStatusbarDescription() = 0;
|
||||
|
||||
/// FIXME: this really should be elsewhere...
|
||||
virtual QString instanceConfigFolder() const = 0;
|
||||
|
||||
/// 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;
|
||||
|
||||
protected:
|
||||
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();
|
||||
|
||||
void launchTaskChanged(std::shared_ptr<LaunchTask>);
|
||||
|
||||
void runningStatusChanged(bool running);
|
||||
|
||||
void statusChanged(Status from, Status to);
|
||||
|
||||
protected slots:
|
||||
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;
|
||||
|
||||
private: /* data */
|
||||
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(BaseInstance::InstanceFlag)
|
||||
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
|
||||
57
api/logic/BaseInstanceProvider.h
Normal file
57
api/logic/BaseInstanceProvider.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#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-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -22,8 +22,9 @@
|
||||
/*!
|
||||
* An abstract base class for versions.
|
||||
*/
|
||||
struct BaseVersion
|
||||
class BaseVersion
|
||||
{
|
||||
public:
|
||||
virtual ~BaseVersion() {}
|
||||
/*!
|
||||
* A string used to identify this version in config files.
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -13,8 +13,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "logic/BaseVersionList.h"
|
||||
#include "logic/BaseVersion.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include "BaseVersion.h"
|
||||
|
||||
BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
@@ -38,6 +38,11 @@ BaseVersionPtr BaseVersionList::getLatestStable() const
|
||||
return at(0);
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getRecommended() const
|
||||
{
|
||||
return getLatestStable();
|
||||
}
|
||||
|
||||
QVariant BaseVersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
@@ -50,63 +55,26 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (index.column())
|
||||
{
|
||||
case NameColumn:
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
|
||||
case VersionRole:
|
||||
return version->name();
|
||||
|
||||
case TypeColumn:
|
||||
case VersionIdRole:
|
||||
return version->descriptor();
|
||||
|
||||
case TypeRole:
|
||||
return version->typeString();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
return version->descriptor();
|
||||
|
||||
case VersionPointerRole:
|
||||
return qVariantFromValue(version);
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant BaseVersionList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("Name");
|
||||
|
||||
case TypeColumn:
|
||||
return tr("Type");
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
switch (section)
|
||||
{
|
||||
case NameColumn:
|
||||
return tr("The name of the version.");
|
||||
|
||||
case TypeColumn:
|
||||
return tr("The version's type.");
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
|
||||
}
|
||||
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
@@ -117,5 +85,20 @@ int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 2;
|
||||
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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -19,9 +19,9 @@
|
||||
#include <QVariant>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "logic/BaseVersion.h"
|
||||
|
||||
class Task;
|
||||
#include "BaseVersion.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
/*!
|
||||
* \brief Class that each instance type's version list derives from.
|
||||
@@ -35,26 +35,25 @@ class Task;
|
||||
* all have a default implementation, but they can be overridden by plugins to
|
||||
* change the behavior of the list.
|
||||
*/
|
||||
class BaseVersionList : public QAbstractListModel
|
||||
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum ModelRoles
|
||||
{
|
||||
VersionPointerRole = 0x34B1CB48
|
||||
};
|
||||
|
||||
enum VListColumns
|
||||
{
|
||||
// First column - Name
|
||||
NameColumn = 0,
|
||||
|
||||
// Second column - Type
|
||||
TypeColumn,
|
||||
|
||||
// Third column - Timestamp
|
||||
TimeColumn
|
||||
VersionPointerRole = Qt::UserRole,
|
||||
VersionRole,
|
||||
VersionIdRole,
|
||||
ParentGameVersionRole,
|
||||
RecommendedRole,
|
||||
LatestRole,
|
||||
TypeRole,
|
||||
BranchRole,
|
||||
PathRole,
|
||||
ArchitectureRole,
|
||||
SortRole
|
||||
};
|
||||
typedef QList<int> RoleList;
|
||||
|
||||
explicit BaseVersionList(QObject *parent = 0);
|
||||
|
||||
@@ -78,9 +77,12 @@ public:
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
virtual QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
//! which roles are provided by this version list?
|
||||
virtual RoleList providesRoles() const;
|
||||
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
@@ -91,16 +93,20 @@ public:
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the latest stable version of this instance type.
|
||||
* This is the version that will be selected by default.
|
||||
* By default, this is simply the first version in the list.
|
||||
* \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;
|
||||
|
||||
/*!
|
||||
* Sorts the version list.
|
||||
*/
|
||||
virtual void sort() = 0;
|
||||
virtual void sortVersions() = 0;
|
||||
|
||||
protected
|
||||
slots:
|
||||
498
api/logic/CMakeLists.txt
Normal file
498
api/logic/CMakeLists.txt
Normal file
@@ -0,0 +1,498 @@
|
||||
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
|
||||
|
||||
# Use tracking separate from memory management
|
||||
Usable.h
|
||||
|
||||
# Prefix tree where node names are strings between separators
|
||||
SeparatorPrefixTree.h
|
||||
|
||||
# WARNING: globals live here
|
||||
Env.h
|
||||
Env.cpp
|
||||
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
|
||||
FileSystem.h
|
||||
FileSystem.cpp
|
||||
|
||||
Exception.h
|
||||
|
||||
# RW lock protected map
|
||||
RWStorage.h
|
||||
|
||||
# A variable that has an implicit default value and keeps track of changes
|
||||
DefaultVariable.h
|
||||
|
||||
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
|
||||
QObjectPtr.h
|
||||
|
||||
# Compression support
|
||||
GZip.h
|
||||
GZip.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
|
||||
)
|
||||
|
||||
add_unit_test(GZip
|
||||
SOURCES GZip_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
set(PATHMATCHER_SOURCES
|
||||
# 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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# Old update system
|
||||
set(UPDATE_SOURCES
|
||||
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
|
||||
)
|
||||
|
||||
add_unit_test(DownloadTask
|
||||
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
|
||||
)
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
# Icon interface
|
||||
set(ICONS_SOURCES
|
||||
# News System
|
||||
icons/IIconList.h
|
||||
icons/IIconList.cpp
|
||||
)
|
||||
|
||||
# Minecraft services status checker
|
||||
set(STATUS_SOURCES
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
)
|
||||
|
||||
add_unit_test(GradleSpecifier
|
||||
SOURCES minecraft/GradleSpecifier_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
add_unit_test(MojangVersionFormat
|
||||
SOURCES minecraft/MojangVersionFormat_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
DATA minecraft/testdata
|
||||
)
|
||||
|
||||
add_unit_test(Library
|
||||
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(ParseUtils
|
||||
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
|
||||
)
|
||||
|
||||
set(TASKS_SOURCES
|
||||
# Tasks
|
||||
tasks/Task.h
|
||||
tasks/Task.cpp
|
||||
tasks/ThreadTask.h
|
||||
tasks/ThreadTask.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
|
||||
)
|
||||
|
||||
add_unit_test(INIFile
|
||||
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
|
||||
)
|
||||
|
||||
add_unit_test(JavaVersion
|
||||
SOURCES java/JavaVersion_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
set(TRANSLATIONS_SOURCES
|
||||
translations/TranslationsModel.h
|
||||
translations/TranslationsModel.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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
add_unit_test(WonkoIndex
|
||||
SOURCES wonko/WonkoIndex_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
################################ COMPILE ################################
|
||||
|
||||
# we need zlib
|
||||
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}
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -15,14 +15,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "include/cmdutils.h"
|
||||
#include "Commandline.h"
|
||||
|
||||
/**
|
||||
* @file libutil/src/cmdutils.cpp
|
||||
*/
|
||||
|
||||
namespace Util
|
||||
{
|
||||
namespace Commandline
|
||||
{
|
||||
|
||||
@@ -483,4 +481,3 @@ ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdS
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -25,15 +25,13 @@
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
|
||||
#include "libutil_config.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
/**
|
||||
* @file libutil/include/cmdutils.h
|
||||
* @brief commandline parsing and processing utilities
|
||||
*/
|
||||
|
||||
namespace Util
|
||||
{
|
||||
namespace Commandline
|
||||
{
|
||||
|
||||
@@ -42,7 +40,7 @@ namespace Commandline
|
||||
* @param args the argument string
|
||||
* @return a QStringList containing all arguments
|
||||
*/
|
||||
LIBUTIL_EXPORT QStringList splitArgs(QString args);
|
||||
MULTIMC_LOGIC_EXPORT QStringList splitArgs(QString args);
|
||||
|
||||
/**
|
||||
* @brief The FlagStyle enum
|
||||
@@ -85,7 +83,7 @@ enum Enum
|
||||
/**
|
||||
* @brief The ParsingError class
|
||||
*/
|
||||
class LIBUTIL_EXPORT ParsingError : public std::runtime_error
|
||||
class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
ParsingError(const QString &what);
|
||||
@@ -94,7 +92,7 @@ public:
|
||||
/**
|
||||
* @brief The Parser class
|
||||
*/
|
||||
class LIBUTIL_EXPORT Parser
|
||||
class MULTIMC_LOGIC_EXPORT Parser
|
||||
{
|
||||
public:
|
||||
/**
|
||||
@@ -252,4 +250,3 @@ private:
|
||||
void getPrefix(QString &opt, QString &flag);
|
||||
};
|
||||
}
|
||||
}
|
||||
204
api/logic/Env.cpp
Normal file
204
api/logic/Env.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
#include "Env.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include <QDir>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include "tasks/Task.h"
|
||||
#include "wonko/WonkoIndex.h"
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
class 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;
|
||||
};
|
||||
|
||||
static Env * instance;
|
||||
|
||||
/*
|
||||
* The *NEW* global rat nest of an object. Handle with care.
|
||||
*/
|
||||
|
||||
Env::Env()
|
||||
{
|
||||
d = new Private();
|
||||
}
|
||||
|
||||
Env::~Env()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
Env& Env::Env::getInstance()
|
||||
{
|
||||
if(!instance)
|
||||
{
|
||||
instance = new Env();
|
||||
}
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void Env::dispose()
|
||||
{
|
||||
delete instance;
|
||||
instance = nullptr;
|
||||
}
|
||||
|
||||
shared_qobject_ptr< HttpMetaCache > Env::metacache()
|
||||
{
|
||||
return d->m_metacache;
|
||||
}
|
||||
|
||||
QNetworkAccessManager& Env::qnam() const
|
||||
{
|
||||
return d->m_qnam;
|
||||
}
|
||||
|
||||
std::shared_ptr<IIconList> Env::icons()
|
||||
{
|
||||
return d->m_iconlist;
|
||||
}
|
||||
|
||||
void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
|
||||
{
|
||||
d->m_iconlist = iconlist;
|
||||
}
|
||||
|
||||
BaseVersionPtr Env::getVersion(QString component, QString version)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return d->m_wonkoRootUrl;
|
||||
}
|
||||
|
||||
void Env::setWonkoRootUrl(const QString& url)
|
||||
{
|
||||
d->m_wonkoRootUrl = url;
|
||||
}
|
||||
|
||||
#include "Env.moc"
|
||||
63
api/logic/Env.h
Normal file
63
api/logic/Env.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "icons/IIconList.h"
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class HttpMetaCache;
|
||||
class BaseVersionList;
|
||||
class BaseVersion;
|
||||
class WonkoIndex;
|
||||
|
||||
#if defined(ENV)
|
||||
#undef ENV
|
||||
#endif
|
||||
#define ENV (Env::getInstance())
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Env
|
||||
{
|
||||
friend class MultiMC;
|
||||
private:
|
||||
class Private;
|
||||
Env();
|
||||
~Env();
|
||||
static void dispose();
|
||||
public:
|
||||
static Env& getInstance();
|
||||
|
||||
QNetworkAccessManager &qnam() const;
|
||||
|
||||
shared_qobject_ptr<HttpMetaCache> metacache();
|
||||
|
||||
std::shared_ptr<IIconList> icons();
|
||||
|
||||
/// 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);
|
||||
|
||||
/// get a version list by name
|
||||
std::shared_ptr<BaseVersionList> getVersionList(QString component);
|
||||
|
||||
/// get a version by list name and version name
|
||||
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
|
||||
|
||||
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
|
||||
|
||||
void registerIconList(std::shared_ptr<IIconList> iconlist);
|
||||
|
||||
shared_qobject_ptr<WonkoIndex> wonkoIndex();
|
||||
|
||||
QString wonkoRootUrl() const;
|
||||
void setWonkoRootUrl(const QString &url);
|
||||
|
||||
protected:
|
||||
Private * d;
|
||||
};
|
||||
34
api/logic/Exception.h
Normal file
34
api/logic/Exception.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <exception>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
};
|
||||
456
api/logic/FileSystem.cpp
Normal file
456
api/logic/FileSystem.cpp
Normal file
@@ -0,0 +1,456 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace FS {
|
||||
|
||||
void ensureExists(const QDir &dir)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
bool ensureFilePathExists(QString filenamepath)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
auto src = PathCombine(m_src.absolutePath(), offset);
|
||||
auto dst = PathCombine(m_dst.absolutePath(), offset);
|
||||
|
||||
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 defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#endif
|
||||
bool deletePath(QString 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);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
#else
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
QString PathCombine(QString path1, QString path2)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return PathCombine(PathCombine(path1, path2), path3);
|
||||
}
|
||||
|
||||
QString AbsolutePath(QString path)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize path
|
||||
*
|
||||
* Any paths inside the current folder will be normalized to relative paths (to current)
|
||||
* Other paths will be made absolute
|
||||
*/
|
||||
QString NormalizePath(QString path)
|
||||
{
|
||||
QDir a = QDir::currentPath();
|
||||
QString currentAbsolute = a.absolutePath();
|
||||
|
||||
QDir b(path);
|
||||
QString newAbsolute = b.absolutePath();
|
||||
|
||||
if (newAbsolute.startsWith(currentAbsolute))
|
||||
{
|
||||
return a.relativeFilePath(newAbsolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
return newAbsolute;
|
||||
}
|
||||
}
|
||||
|
||||
QString badFilenameChars = "\"\\/?<>:*|!";
|
||||
|
||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||
{
|
||||
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);;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#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;
|
||||
|
||||
if (!called_coinit)
|
||||
{
|
||||
hres = CoInitialize(NULL);
|
||||
called_coinit = true;
|
||||
|
||||
if (!SUCCEEDED(hres))
|
||||
{
|
||||
qWarning("Failed to initialize COM. Error 0x%08X", hres);
|
||||
return hres;
|
||||
}
|
||||
}
|
||||
|
||||
IShellLink *link;
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
|
||||
(LPVOID *)&link);
|
||||
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
IPersistFile *persistFile;
|
||||
|
||||
link->SetPath(targetPath);
|
||||
link->SetArguments(args);
|
||||
|
||||
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
|
||||
if (SUCCEEDED(hres))
|
||||
{
|
||||
WCHAR wstr[MAX_PATH];
|
||||
|
||||
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
|
||||
|
||||
hres = persistFile->Save(wstr, TRUE);
|
||||
persistFile->Release();
|
||||
}
|
||||
link->Release();
|
||||
}
|
||||
return hres;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
QString getDesktopDir()
|
||||
{
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
}
|
||||
|
||||
// Cross-platform Shortcut creation
|
||||
bool createShortCut(QString location, QString dest, QStringList args, QString name,
|
||||
QString icon)
|
||||
{
|
||||
#if defined Q_OS_LINUX
|
||||
location = PathCombine(location, name + ".desktop");
|
||||
|
||||
QFile f(location);
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
QTextStream stream(&f);
|
||||
|
||||
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.flush();
|
||||
f.close();
|
||||
|
||||
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
|
||||
QFileDevice::ExeOther);
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
#else
|
||||
qWarning("Desktop Shortcuts not supported on your platform!");
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
127
api/logic/FileSystem.h
Normal file
127
api/logic/FileSystem.h
Normal file
@@ -0,0 +1,127 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Exception.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include <QDir>
|
||||
#include <QFlags>
|
||||
|
||||
namespace FS
|
||||
{
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
|
||||
{
|
||||
public:
|
||||
FileSystemException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* write data to a file safely
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data);
|
||||
|
||||
/**
|
||||
* read data from a file safely\
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename);
|
||||
|
||||
/**
|
||||
* Update the last changed timestamp of an existing file
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool updateTimestamp(const QString & filename);
|
||||
|
||||
/**
|
||||
* Creates all the folders in a path for the specified path
|
||||
* last segment of the path is treated as a file name and is ignored!
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool ensureFilePathExists(QString filenamepath);
|
||||
|
||||
/**
|
||||
* Creates all the folders in a path for the specified path
|
||||
* last segment of the path is treated as a folder name and is created!
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
private:
|
||||
bool operator()(const QString &offset);
|
||||
|
||||
private:
|
||||
bool m_followSymlinks = true;
|
||||
const IPathMatcher * m_blacklist = nullptr;
|
||||
QDir m_src;
|
||||
QDir m_dst;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a folder recursively
|
||||
*/
|
||||
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 AbsolutePath(QString path);
|
||||
|
||||
/**
|
||||
* Resolve an executable
|
||||
*
|
||||
* Will resolve:
|
||||
* single executable (by name)
|
||||
* relative path
|
||||
* absolute path
|
||||
*
|
||||
* @return absolute path to executable or null string
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path);
|
||||
|
||||
/**
|
||||
* Normalize path
|
||||
*
|
||||
* Any paths inside the current directory will be normalized to relative paths (to current)
|
||||
* Other paths will be made absolute
|
||||
*
|
||||
* Returns false if the path logic somehow filed (and normalizedPath in invalid)
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = ".");
|
||||
|
||||
/// Checks if the a given Path contains "!"
|
||||
MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder);
|
||||
|
||||
// Get the Directory representing the User's Desktop
|
||||
MULTIMC_LOGIC_EXPORT QString getDesktopDir();
|
||||
|
||||
// Create a shortcut at *location*, pointing to *dest* called with the arguments *args*
|
||||
// call it *name* and assign it the icon *icon*
|
||||
// return true if operation succeeded
|
||||
MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
|
||||
}
|
||||
164
api/logic/FileSystem_test.cpp
Normal file
164
api/logic/FileSystem_test.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <QTest>
|
||||
#include <QTemporaryDir>
|
||||
#include <QStandardPaths>
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
class FileSystemTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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));
|
||||
|
||||
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");
|
||||
|
||||
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";
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_PathCombine1()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
|
||||
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");
|
||||
|
||||
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";
|
||||
#endif
|
||||
}
|
||||
|
||||
void test_PathCombine2()
|
||||
{
|
||||
QFETCH(QString, result);
|
||||
QFETCH(QString, path1);
|
||||
QFETCH(QString, path2);
|
||||
QFETCH(QString, path3);
|
||||
|
||||
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();
|
||||
|
||||
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"));
|
||||
};
|
||||
|
||||
// first try variant without trailing /
|
||||
QVERIFY(!folder.endsWith('/'));
|
||||
f();
|
||||
|
||||
// then variant with trailing /
|
||||
folder.append('/');
|
||||
QVERIFY(folder.endsWith('/'));
|
||||
f();
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
|
||||
QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation));
|
||||
QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result));
|
||||
|
||||
//QDir().remove(location);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(FileSystemTest)
|
||||
|
||||
#include "FileSystem_test.moc"
|
||||
356
api/logic/FolderInstanceProvider.cpp
Normal file
356
api/logic/FolderInstanceProvider.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
63
api/logic/FolderInstanceProvider.h
Normal file
63
api/logic/FolderInstanceProvider.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#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;
|
||||
};
|
||||
115
api/logic/GZip.cpp
Normal file
115
api/logic/GZip.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include "GZip.h"
|
||||
#include <zlib.h>
|
||||
#include <QByteArray>
|
||||
|
||||
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
|
||||
{
|
||||
if (compressedBytes.size() == 0)
|
||||
{
|
||||
uncompressedBytes = compressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
bool done = false;
|
||||
|
||||
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int err = Z_OK;
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
uncompressedBytes.resize(strm.total_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
|
||||
{
|
||||
if (uncompressedBytes.size() == 0)
|
||||
{
|
||||
compressedBytes = uncompressedBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned compLength = std::min(uncompressedBytes.size(), 16);
|
||||
compressedBytes.clear();
|
||||
compressedBytes.resize(compLength);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
zs.next_in = (Bytef*)uncompressedBytes.data();
|
||||
zs.avail_in = 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);
|
||||
|
||||
compressedBytes.resize(offset);
|
||||
|
||||
if (deflateEnd(&zs) != Z_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ret != Z_STREAM_END)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
12
api/logic/GZip.h
Normal file
12
api/logic/GZip.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <QByteArray>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT GZip
|
||||
{
|
||||
public:
|
||||
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
|
||||
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
|
||||
};
|
||||
|
||||
57
api/logic/GZip_test.cpp
Normal file
57
api/logic/GZip_test.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <QTest>
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "GZip.h"
|
||||
#include <random>
|
||||
|
||||
void fib(int &prev, int &cur)
|
||||
{
|
||||
auto ret = prev + cur;
|
||||
prev = cur;
|
||||
cur = ret;
|
||||
}
|
||||
|
||||
class GZipTest : public QObject
|
||||
{
|
||||
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());
|
||||
|
||||
// initialize random buffer
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
random.append((char)idis(eng));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(GZipTest)
|
||||
|
||||
#include "GZip_test.moc"
|
||||
68
api/logic/InstanceCopyTask.cpp
Normal file
68
api/logic/InstanceCopyTask.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#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)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_origInstance = origInstance;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
|
||||
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());
|
||||
|
||||
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");
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void InstanceCopyTask::copyAborted()
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance folder copy has been aborted."));
|
||||
return;
|
||||
}
|
||||
42
api/logic/InstanceCopyTask.h
Normal file
42
api/logic/InstanceCopyTask.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseInstance.h"
|
||||
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup, bool copySaves);
|
||||
|
||||
protected:
|
||||
//! 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;
|
||||
};
|
||||
|
||||
|
||||
46
api/logic/InstanceCreationTask.cpp
Normal file
46
api/logic/InstanceCreationTask.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#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"
|
||||
|
||||
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
|
||||
const QString& instName, const QString& instIcon, const QString& instGroup)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
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();
|
||||
}
|
||||
31
api/logic/InstanceCreationTask.h
Normal file
31
api/logic/InstanceCreationTask.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
|
||||
protected:
|
||||
//! 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;
|
||||
};
|
||||
|
||||
164
api/logic/InstanceImportTask.cpp
Normal file
164
api/logic/InstanceImportTask.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
|
||||
#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 <QtConcurrentRun>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
|
||||
const QString &instName, const QString &instIcon, const QString &instGroup)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
}
|
||||
|
||||
void InstanceImportTask::executeTask()
|
||||
{
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadFailed(QString reason)
|
||||
{
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current / 2, total);
|
||||
}
|
||||
|
||||
static QFileInfo findRecursive(const QString &dir, const QString &name)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
void InstanceImportTask::extractAndTweak()
|
||||
{
|
||||
setStatus(tr("Extracting modpack"));
|
||||
m_stagingPath = m_target->getStagedInstancePath();
|
||||
QDir extractDir(m_stagingPath);
|
||||
qDebug() << "Attempting to create instance from" << m_archivePath;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void InstanceImportTask::extractAborted()
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
return;
|
||||
}
|
||||
47
api/logic/InstanceImportTask.h
Normal file
47
api/logic/InstanceImportTask.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
void extractAndTweak();
|
||||
|
||||
private slots:
|
||||
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;
|
||||
};
|
||||
342
api/logic/InstanceList.cpp
Normal file
342
api/logic/InstanceList.cpp
Normal file
@@ -0,0 +1,342 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <QDir>
|
||||
#include <QSet>
|
||||
#include <QFile>
|
||||
#include <QThread>
|
||||
#include <QTextStream>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QDebug>
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "BaseInstance.h"
|
||||
|
||||
#include "FolderInstanceProvider.h"
|
||||
|
||||
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
|
||||
: QAbstractListModel(parent), m_instDir(instDir)
|
||||
{
|
||||
m_globalSettings = globalSettings;
|
||||
resumeWatch();
|
||||
}
|
||||
|
||||
InstanceList::~InstanceList()
|
||||
{
|
||||
}
|
||||
|
||||
int InstanceList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return m_instances.count();
|
||||
}
|
||||
|
||||
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (row < 0 || row >= m_instances.size())
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, (void *)m_instances.at(row).get());
|
||||
}
|
||||
|
||||
QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||
switch (role)
|
||||
{
|
||||
case InstancePointerRole:
|
||||
{
|
||||
QVariant v = qVariantFromValue((void *)pdata);
|
||||
return v;
|
||||
}
|
||||
case InstanceIDRole:
|
||||
{
|
||||
return pdata->id();
|
||||
}
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
return pdata->name();
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
return pdata->instanceRoot();
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
{
|
||||
return pdata->iconKey();
|
||||
}
|
||||
// HACK: see GroupView.h in gui!
|
||||
case GroupRole:
|
||||
{
|
||||
return pdata->group();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags f;
|
||||
if (index.isValid())
|
||||
{
|
||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groups.toList();
|
||||
}
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
{
|
||||
for(auto & instance: m_instances)
|
||||
{
|
||||
auto instGroupName = instance->group();
|
||||
if(instGroupName == name)
|
||||
{
|
||||
instance->setGroupPost(QString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
|
||||
{
|
||||
QMap<InstanceId, InstanceLocator> out;
|
||||
int i = 0;
|
||||
for(auto & item: list)
|
||||
{
|
||||
auto id = item->id();
|
||||
if(out.contains(id))
|
||||
{
|
||||
qWarning() << "Duplicate ID" << id << "in instance list";
|
||||
}
|
||||
out[id] = std::make_pair(item, i);
|
||||
i++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
{
|
||||
auto existingIds = getIdMapping(m_instances);
|
||||
|
||||
QList<InstancePtr> newList;
|
||||
|
||||
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
|
||||
{
|
||||
for(auto & id: ids)
|
||||
{
|
||||
if(existingIds.contains(id))
|
||||
{
|
||||
auto instPair = existingIds[id];
|
||||
/*
|
||||
auto & instPtr = instPair.first;
|
||||
auto & instIdx = instPair.second;
|
||||
*/
|
||||
existingIds.remove(id);
|
||||
qDebug() << "Should keep and soft-reload" << id;
|
||||
}
|
||||
else
|
||||
{
|
||||
InstancePtr instPtr = provider->loadInstance(id);
|
||||
if(instPtr)
|
||||
{
|
||||
newList.append(instPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if(complete)
|
||||
{
|
||||
for(auto & item: m_providers)
|
||||
{
|
||||
processIds(item.get(), item->discoverInstances());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto & item: m_updatedProviders)
|
||||
{
|
||||
processIds(item, item->discoverInstances());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||
if(!existingIds.isEmpty())
|
||||
{
|
||||
// get the list of removed instances and sort it by their original index, from last to first
|
||||
auto deadList = existingIds.values();
|
||||
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
|
||||
{
|
||||
return a.second > b.second;
|
||||
};
|
||||
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
|
||||
// remove the contiguous ranges of rows
|
||||
int front_bookmark = -1;
|
||||
int back_bookmark = -1;
|
||||
int currentItem = -1;
|
||||
auto removeNow = [&]()
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
|
||||
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
|
||||
endRemoveRows();
|
||||
front_bookmark = -1;
|
||||
back_bookmark = currentItem;
|
||||
};
|
||||
for(auto & removedItem: deadList)
|
||||
{
|
||||
auto instPtr = removedItem.first;
|
||||
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
instPtr->invalidate();
|
||||
currentItem = removedItem.second;
|
||||
if(back_bookmark == -1)
|
||||
{
|
||||
// no bookmark yet
|
||||
back_bookmark = currentItem;
|
||||
}
|
||||
else if(currentItem == front_bookmark - 1)
|
||||
{
|
||||
// part of contiguous sequence, continue
|
||||
}
|
||||
else
|
||||
{
|
||||
// seam between previous and current item
|
||||
removeNow();
|
||||
}
|
||||
front_bookmark = currentItem;
|
||||
}
|
||||
if(back_bookmark != -1)
|
||||
{
|
||||
removeNow();
|
||||
}
|
||||
}
|
||||
if(newList.size())
|
||||
{
|
||||
add(newList);
|
||||
}
|
||||
m_updatedProviders.clear();
|
||||
return NoError;
|
||||
}
|
||||
|
||||
void InstanceList::add(const QList<InstancePtr> &t)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||
m_instances.append(t);
|
||||
for(auto & ptr : t)
|
||||
{
|
||||
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void InstanceList::resumeWatch()
|
||||
{
|
||||
if(m_watchLevel > 0)
|
||||
{
|
||||
qWarning() << "Bad suspend level resume in instance list";
|
||||
return;
|
||||
}
|
||||
m_watchLevel++;
|
||||
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
|
||||
{
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::suspendWatch()
|
||||
{
|
||||
m_watchLevel --;
|
||||
}
|
||||
|
||||
void InstanceList::providerUpdated()
|
||||
{
|
||||
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
|
||||
if(!provider)
|
||||
{
|
||||
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
|
||||
return;
|
||||
}
|
||||
m_updatedProviders.insert(provider);
|
||||
if(m_watchLevel == 1)
|
||||
{
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::groupsPublished(QSet<QString> newGroups)
|
||||
{
|
||||
m_groups.unite(newGroups);
|
||||
}
|
||||
|
||||
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
|
||||
{
|
||||
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
|
||||
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
|
||||
m_providers.append(provider);
|
||||
}
|
||||
|
||||
InstancePtr InstanceList::getInstanceById(QString instId) const
|
||||
{
|
||||
if(instId.isEmpty())
|
||||
return InstancePtr();
|
||||
for(auto & inst: m_instances)
|
||||
{
|
||||
if (inst->id() == instId)
|
||||
{
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
return InstancePtr();
|
||||
}
|
||||
|
||||
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
|
||||
{
|
||||
return index(getInstIndex(getInstanceById(id).get()));
|
||||
}
|
||||
|
||||
int InstanceList::getInstIndex(BaseInstance *inst) const
|
||||
{
|
||||
int count = m_instances.count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (inst == m_instances[i].get())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InstanceList::propertiesChanged(BaseInstance *inst)
|
||||
{
|
||||
int i = getInstIndex(inst);
|
||||
if (i != -1)
|
||||
{
|
||||
emit dataChanged(index(i), index(i));
|
||||
}
|
||||
}
|
||||
|
||||
#include "InstanceList.moc"
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -18,49 +18,25 @@
|
||||
#include <QObject>
|
||||
#include <QAbstractListModel>
|
||||
#include <QSet>
|
||||
#include <gui/groupview/GroupedProxyModel.h>
|
||||
#include <QIcon>
|
||||
#include <QList>
|
||||
|
||||
#include "logic/BaseInstance.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class BaseInstance;
|
||||
|
||||
class QDir;
|
||||
|
||||
struct FTBRecord
|
||||
{
|
||||
QString dirName;
|
||||
QString name;
|
||||
QString logo;
|
||||
QString mcVersion;
|
||||
QString description;
|
||||
QString instanceDir;
|
||||
QString templateDir;
|
||||
bool operator ==(const FTBRecord other) const
|
||||
{
|
||||
return instanceDir == other.instanceDir;
|
||||
}
|
||||
};
|
||||
|
||||
inline uint qHash(FTBRecord record)
|
||||
{
|
||||
return qHash(record.instanceDir);
|
||||
}
|
||||
|
||||
class InstanceList : public QAbstractListModel
|
||||
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
void loadGroupList(QMap<QString, QString> &groupList);
|
||||
QSet<FTBRecord> discoverFTBInstances();
|
||||
void loadFTBInstances(QMap<QString, QString> &groupMap, QList<InstancePtr> & tempList);
|
||||
|
||||
private
|
||||
slots:
|
||||
void saveGroupList();
|
||||
|
||||
public:
|
||||
explicit InstanceList(const QString &instDir, QObject *parent = 0);
|
||||
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
|
||||
public:
|
||||
@@ -71,6 +47,7 @@ public:
|
||||
|
||||
enum AdditionalRoles
|
||||
{
|
||||
GroupRole = Qt::UserRole,
|
||||
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
|
||||
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
|
||||
};
|
||||
@@ -85,77 +62,47 @@ public:
|
||||
UnknownError
|
||||
};
|
||||
|
||||
QString instDir() const
|
||||
{
|
||||
return m_instDir;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get the instance at index
|
||||
*/
|
||||
InstancePtr at(int i) const
|
||||
{
|
||||
return m_instances.at(i);
|
||||
}
|
||||
;
|
||||
|
||||
/*!
|
||||
* \brief Get the count of loaded instances
|
||||
*/
|
||||
int count() const
|
||||
{
|
||||
return m_instances.count();
|
||||
}
|
||||
;
|
||||
|
||||
/// Clear all instances. Triggers notifications.
|
||||
void clear();
|
||||
InstListError loadList(bool complete = false);
|
||||
|
||||
/// Add an instance. Triggers notifications, returns the new index
|
||||
int add(InstancePtr t);
|
||||
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
|
||||
void addInstanceProvider(BaseInstanceProvider * provider);
|
||||
|
||||
/// Get an instance by ID
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
|
||||
// FIXME: instead of iterating through all instances and forming a set, keep the set around
|
||||
QStringList getGroups();
|
||||
|
||||
void deleteGroup(const QString & name);
|
||||
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
|
||||
public
|
||||
slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
/*!
|
||||
* \brief Loads the instance list. Triggers notifications.
|
||||
*/
|
||||
InstListError loadList();
|
||||
|
||||
private
|
||||
slots:
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
void instanceNuked(BaseInstance *inst);
|
||||
void groupChanged();
|
||||
void groupsPublished(QSet<QString>);
|
||||
void providerUpdated();
|
||||
|
||||
private:
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
|
||||
bool continueProcessInstance(InstancePtr instPtr, const int error, const QDir &dir,
|
||||
QMap<QString, QString> &groupMap);
|
||||
void suspendWatch();
|
||||
void resumeWatch();
|
||||
void add(const QList<InstancePtr> &list);
|
||||
|
||||
protected:
|
||||
int m_watchLevel = 0;
|
||||
QSet<BaseInstanceProvider *> m_updatedProviders;
|
||||
QString m_instDir;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
};
|
||||
|
||||
class InstanceProxyModel : public GroupedProxyModel
|
||||
{
|
||||
public:
|
||||
explicit InstanceProxyModel(QObject *parent = 0);
|
||||
|
||||
protected:
|
||||
virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
|
||||
};
|
||||
272
api/logic/Json.cpp
Normal file
272
api/logic/Json.cpp
Normal file
@@ -0,0 +1,272 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <math.h>
|
||||
|
||||
namespace Json
|
||||
{
|
||||
void write(const QJsonDocument &doc, const QString &filename)
|
||||
{
|
||||
FS::write(filename, doc.toJson());
|
||||
}
|
||||
void write(const QJsonObject &object, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(object), filename);
|
||||
}
|
||||
void write(const QJsonArray &array, const QString &filename)
|
||||
{
|
||||
write(QJsonDocument(array), filename);
|
||||
}
|
||||
|
||||
QByteArray toBinary(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toBinaryData();
|
||||
}
|
||||
QByteArray toBinary(const QJsonArray &array)
|
||||
{
|
||||
return QJsonDocument(array).toBinaryData();
|
||||
}
|
||||
QByteArray toText(const QJsonObject &obj)
|
||||
{
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
QByteArray toText(const QJsonArray &array)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
QJsonDocument requireDocument(const QString &filename, const QString &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();
|
||||
}
|
||||
QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url)
|
||||
{
|
||||
return QJsonValue(url.toString(QUrl::FullyEncoded));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QByteArray>(const QByteArray &data)
|
||||
{
|
||||
return QJsonValue(QString::fromLatin1(data.toHex()));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDateTime>(const QDateTime &datetime)
|
||||
{
|
||||
return QJsonValue(datetime.toString(Qt::ISODate));
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QDir>(const QDir &dir)
|
||||
{
|
||||
return QDir::current().relativeFilePath(dir.absolutePath());
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUuid>(const QUuid &uuid)
|
||||
{
|
||||
return uuid.toString();
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QVariant>(const QVariant &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());
|
||||
}
|
||||
|
||||
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined())
|
||||
{
|
||||
throw JsonException(what + " is null or undefined");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
249
api/logic/Json.h
Normal file
249
api/logic/Json.h
Normal file
@@ -0,0 +1,249 @@
|
||||
// Licensed under the Apache-2.0 license. See README.md for details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include <QDir>
|
||||
#include <QUuid>
|
||||
#include <QVariant>
|
||||
#include <memory>
|
||||
|
||||
#include "Exception.h"
|
||||
|
||||
namespace Json
|
||||
{
|
||||
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
|
||||
{
|
||||
public:
|
||||
JsonException(const QString &message) : Exception(message) {}
|
||||
};
|
||||
|
||||
/// @throw FileSystemException
|
||||
MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename);
|
||||
/// @throw FileSystemException
|
||||
MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename);
|
||||
/// @throw FileSystemException
|
||||
MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonObject &obj);
|
||||
MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonArray &array);
|
||||
MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonObject &obj);
|
||||
MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonArray &array);
|
||||
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
|
||||
/// @throw JsonException
|
||||
MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
|
||||
|
||||
/////////////////// WRITING ////////////////////
|
||||
|
||||
void writeString(QJsonObject & to, const QString &key, const QString &value);
|
||||
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
|
||||
|
||||
template<typename T>
|
||||
QJsonValue toJson(const T &t)
|
||||
{
|
||||
return QJsonValue(t);
|
||||
}
|
||||
template<>
|
||||
QJsonValue toJson<QUrl>(const QUrl &url);
|
||||
template<>
|
||||
QJsonValue toJson<QByteArray>(const QByteArray &data);
|
||||
template<>
|
||||
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
|
||||
template<>
|
||||
QJsonValue toJson<QDir>(const QDir &dir);
|
||||
template<>
|
||||
QJsonValue toJson<QUuid>(const QUuid &uuid);
|
||||
template<>
|
||||
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;
|
||||
}
|
||||
|
||||
////////////////// READING ////////////////////
|
||||
|
||||
/// @throw JsonException
|
||||
template <typename T>
|
||||
T requireIsType(const QJsonValue &value, const QString &what = "Value");
|
||||
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT double requireIsType<double>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT bool requireIsType<bool>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT int requireIsType<int>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QString requireIsType<QString>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
|
||||
/// @throw JsonException
|
||||
template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
|
||||
|
||||
// the following functions are higher level functions, that make use of the above functions for
|
||||
// type conversion
|
||||
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_;
|
||||
}
|
||||
}
|
||||
|
||||
/// @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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// @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);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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); \
|
||||
}
|
||||
|
||||
JSON_HELPERFUNCTIONS(Array, QJsonArray)
|
||||
JSON_HELPERFUNCTIONS(Object, QJsonObject)
|
||||
JSON_HELPERFUNCTIONS(JsonValue, QJsonValue)
|
||||
JSON_HELPERFUNCTIONS(String, QString)
|
||||
JSON_HELPERFUNCTIONS(Boolean, bool)
|
||||
JSON_HELPERFUNCTIONS(Double, double)
|
||||
JSON_HELPERFUNCTIONS(Integer, int)
|
||||
JSON_HELPERFUNCTIONS(DateTime, QDateTime)
|
||||
JSON_HELPERFUNCTIONS(Url, QUrl)
|
||||
JSON_HELPERFUNCTIONS(ByteArray, QByteArray)
|
||||
JSON_HELPERFUNCTIONS(Dir, QDir)
|
||||
JSON_HELPERFUNCTIONS(Uuid, QUuid)
|
||||
JSON_HELPERFUNCTIONS(Variant, QVariant)
|
||||
|
||||
#undef JSON_HELPERFUNCTIONS
|
||||
|
||||
}
|
||||
using JSONValidationError = Json::JsonException;
|
||||
176
api/logic/LoggedProcess.cpp
Normal file
176
api/logic/LoggedProcess.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#include "LoggedProcess.h"
|
||||
#include "MessageLevel.h"
|
||||
#include <QDebug>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
LoggedProcess::~LoggedProcess()
|
||||
{
|
||||
if(m_is_detachable)
|
||||
{
|
||||
setProcessState(QProcess::NotRunning);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList reprocess(const QByteArray & data, QString & leftover)
|
||||
{
|
||||
QString str = leftover + QString::fromLocal8Bit(data);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void LoggedProcess::on_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;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void LoggedProcess::kill()
|
||||
{
|
||||
m_is_aborting = true;
|
||||
QProcess::kill();
|
||||
}
|
||||
|
||||
int LoggedProcess::exitCode() const
|
||||
{
|
||||
return m_exit_code;
|
||||
}
|
||||
|
||||
void LoggedProcess::changeState(LoggedProcess::State state)
|
||||
{
|
||||
if(state == m_state)
|
||||
return;
|
||||
m_state = state;
|
||||
emit stateChanged(m_state);
|
||||
}
|
||||
|
||||
LoggedProcess::State LoggedProcess::state() const
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
qint64 LoggedProcess::processId() const
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return pid() ? pid()->dwProcessId : 0;
|
||||
#else
|
||||
return pid();
|
||||
#endif
|
||||
}
|
||||
|
||||
void LoggedProcess::setDetachable(bool detachable)
|
||||
{
|
||||
m_is_detachable = detachable;
|
||||
}
|
||||
80
api/logic/LoggedProcess.h
Normal file
80
api/logic/LoggedProcess.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <QProcess>
|
||||
#include "MessageLevel.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
/*
|
||||
* This is a basic process.
|
||||
* It has line-based logging support and hides some of the nasty bits.
|
||||
*/
|
||||
class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
NotRunning,
|
||||
Starting,
|
||||
FailedToStart,
|
||||
Running,
|
||||
Finished,
|
||||
Crashed,
|
||||
Aborted
|
||||
};
|
||||
|
||||
public:
|
||||
explicit LoggedProcess(QObject* parent = 0);
|
||||
virtual ~LoggedProcess();
|
||||
|
||||
State state() const;
|
||||
int exitCode() const;
|
||||
qint64 processId() const;
|
||||
|
||||
void setDetachable(bool detachable);
|
||||
|
||||
signals:
|
||||
void log(QStringList lines, MessageLevel::Enum level);
|
||||
void stateChanged(LoggedProcess::State state);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @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);
|
||||
|
||||
private:
|
||||
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;
|
||||
};
|
||||
76
api/logic/MMCStrings.cpp
Normal file
76
api/logic/MMCStrings.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "MMCStrings.h"
|
||||
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/// 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);
|
||||
|
||||
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);
|
||||
}
|
||||
10
api/logic/MMCStrings.h
Normal file
10
api/logic/MMCStrings.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace Strings
|
||||
{
|
||||
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
|
||||
}
|
||||
491
api/logic/MMCZip.cpp
Normal file
491
api/logic/MMCZip.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <quazip.h>
|
||||
#include <JlCompress.h>
|
||||
#include <quazipdir.h>
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
bool copyData(QIODevice &inFile, QIODevice &outFile)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
bool MMCZip::noFilter(QString)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
88
api/logic/MMCZip.h
Normal file
88
api/logic/MMCZip.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "SeparatorPrefixTree.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QuaZip;
|
||||
|
||||
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.
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
|
||||
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = 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);
|
||||
|
||||
/// filter function for @mergeZipFiles - passthrough
|
||||
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
|
||||
|
||||
/// filter function for @mergeZipFiles - ignores METAINF
|
||||
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 = 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);
|
||||
}
|
||||
36
api/logic/MessageLevel.cpp
Normal file
36
api/logic/MessageLevel.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "MessageLevel.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
28
api/logic/MessageLevel.h
Normal file
28
api/logic/MessageLevel.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @brief the MessageLevel Enum
|
||||
* defines what level a log message is
|
||||
*/
|
||||
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 */
|
||||
};
|
||||
MessageLevel::Enum getLevel(const QString &levelName);
|
||||
|
||||
/* Get message level from a line. Line is modified if it was successful. */
|
||||
MessageLevel::Enum fromLine(QString &line);
|
||||
}
|
||||
93
api/logic/NullInstance.h
Normal file
93
api/logic/NullInstance.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
|
||||
class NullInstance: public BaseInstance
|
||||
{
|
||||
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;
|
||||
}
|
||||
};
|
||||
83
api/logic/QObjectPtr.h
Normal file
83
api/logic/QObjectPtr.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace details
|
||||
{
|
||||
struct DeleteQObjectLater
|
||||
{
|
||||
void operator()(QObject *obj) const
|
||||
{
|
||||
obj->deleteLater();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A unique pointer class with unique pointer semantics intended for derivates of QObject
|
||||
* Calls deleteLater() instead of destroying the contained object immediately
|
||||
*/
|
||||
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
|
||||
|
||||
/**
|
||||
* A shared pointer class with shared pointer semantics intended for derivates of QObject
|
||||
* Calls deleteLater() instead of destroying the contained object immediately
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr <T> m_ptr;
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <QDebug>
|
||||
|
||||
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent)
|
||||
: QObject(parent), m_exp(".*"), m_watcher(new QFileSystemWatcher(this))
|
||||
: QObject(parent), m_watcher(new QFileSystemWatcher(this))
|
||||
{
|
||||
connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
|
||||
&RecursiveFileSystemWatcher::fileChange);
|
||||
@@ -82,16 +82,20 @@ void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir)
|
||||
QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory)
|
||||
{
|
||||
QStringList ret;
|
||||
QRegularExpression exp(m_exp);
|
||||
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
|
||||
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))
|
||||
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
|
||||
{
|
||||
if (exp.match(file).hasMatch())
|
||||
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
|
||||
if (m_matcher->matches(relPath))
|
||||
{
|
||||
ret.append(m_root.relativeFilePath(directory.absoluteFilePath(file)));
|
||||
ret.append(relPath);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -2,24 +2,38 @@
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDir>
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
class RecursiveFileSystemWatcher : public QObject
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RecursiveFileSystemWatcher(QObject *parent);
|
||||
|
||||
void setRootDir(const QDir &root);
|
||||
QDir rootDir() const { return m_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; }
|
||||
bool watchFiles() const
|
||||
{
|
||||
return m_watchFiles;
|
||||
}
|
||||
|
||||
void setFileExpression(const QString &exp) { m_exp = exp; }
|
||||
QString fileExpression() const { return m_exp; }
|
||||
void setMatcher(IPathMatcher::Ptr matcher)
|
||||
{
|
||||
m_matcher = matcher;
|
||||
}
|
||||
|
||||
QStringList files() const { return m_files; }
|
||||
QStringList files() const
|
||||
{
|
||||
return m_files;
|
||||
}
|
||||
|
||||
signals:
|
||||
void filesChanged();
|
||||
@@ -33,7 +47,7 @@ private:
|
||||
QDir m_root;
|
||||
bool m_watchFiles = false;
|
||||
bool m_isEnabled = false;
|
||||
QString m_exp;
|
||||
IPathMatcher::Ptr m_matcher;
|
||||
|
||||
QFileSystemWatcher *m_watcher;
|
||||
|
||||
298
api/logic/SeparatorPrefixTree.h
Normal file
298
api/logic/SeparatorPrefixTree.h
Normal file
@@ -0,0 +1,298 @@
|
||||
#pragma once
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QStringList>
|
||||
|
||||
template <char Tseparator>
|
||||
class SeparatorPrefixTree
|
||||
{
|
||||
public:
|
||||
SeparatorPrefixTree(QStringList paths)
|
||||
{
|
||||
insert(paths);
|
||||
}
|
||||
|
||||
SeparatorPrefixTree(bool contained = false)
|
||||
{
|
||||
m_contained = contained;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 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;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private:
|
||||
QMap<QString,SeparatorPrefixTree<Tseparator>> children;
|
||||
bool m_contained = false;
|
||||
};
|
||||
58
api/logic/Usable.h
Normal file
58
api/logic/Usable.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
class Usable;
|
||||
|
||||
/**
|
||||
* Base class for things that can be used by multiple other things and we want to track the use count.
|
||||
*
|
||||
* @see UseLock
|
||||
*/
|
||||
class Usable
|
||||
{
|
||||
friend class UseLock;
|
||||
public:
|
||||
std::size_t useCount()
|
||||
{
|
||||
return m_useCount;
|
||||
}
|
||||
bool isInUse()
|
||||
{
|
||||
return m_useCount > 0;
|
||||
}
|
||||
protected:
|
||||
virtual void decrementUses()
|
||||
{
|
||||
m_useCount--;
|
||||
}
|
||||
virtual void incrementUses()
|
||||
{
|
||||
m_useCount++;
|
||||
}
|
||||
private:
|
||||
std::size_t m_useCount = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lock class to use for keeping track of uses of other things derived from Usable
|
||||
*
|
||||
* @see Usable
|
||||
*/
|
||||
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();
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<Usable> m_usable;
|
||||
};
|
||||
@@ -1,23 +1,23 @@
|
||||
#include "include/modutils.h"
|
||||
#include "Version.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
Util::Version::Version(const QString &str) : m_string(str)
|
||||
Version::Version(const QString &str) : m_string(str)
|
||||
{
|
||||
parse();
|
||||
}
|
||||
|
||||
bool Util::Version::operator<(const Version &other) const
|
||||
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", 0) : m_sections.at(i);
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 < sec2;
|
||||
@@ -26,18 +26,18 @@ bool Util::Version::operator<(const Version &other) const
|
||||
|
||||
return false;
|
||||
}
|
||||
bool Util::Version::operator<=(const Util::Version &other) const
|
||||
bool Version::operator<=(const Version &other) const
|
||||
{
|
||||
return *this < other || *this == other;
|
||||
}
|
||||
bool Util::Version::operator>(const Version &other) const
|
||||
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", 0) : m_sections.at(i);
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return sec1 > sec2;
|
||||
@@ -46,18 +46,18 @@ bool Util::Version::operator>(const Version &other) const
|
||||
|
||||
return false;
|
||||
}
|
||||
bool Util::Version::operator>=(const Version &other) const
|
||||
bool Version::operator>=(const Version &other) const
|
||||
{
|
||||
return *this > other || *this == other;
|
||||
}
|
||||
bool Util::Version::operator==(const Version &other) const
|
||||
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", 0) : m_sections.at(i);
|
||||
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
|
||||
const Section sec2 =
|
||||
(i >= other.m_sections.size()) ? Section("0", 0) : other.m_sections.at(i);
|
||||
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
|
||||
if (sec1 != sec2)
|
||||
{
|
||||
return false;
|
||||
@@ -66,37 +66,28 @@ bool Util::Version::operator==(const Version &other) const
|
||||
|
||||
return true;
|
||||
}
|
||||
bool Util::Version::operator!=(const Version &other) const
|
||||
bool Version::operator!=(const Version &other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
void Util::Version::parse()
|
||||
void Version::parse()
|
||||
{
|
||||
m_sections.clear();
|
||||
|
||||
QStringList parts = m_string.split('.');
|
||||
|
||||
for (const auto part : parts)
|
||||
{
|
||||
bool ok = false;
|
||||
int num = part.toInt(&ok);
|
||||
if (ok)
|
||||
{
|
||||
m_sections.append(Section(part, num));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sections.append(Section(part));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Util::versionIsInInterval(const QString &version, const QString &interval)
|
||||
bool versionIsInInterval(const QString &version, const QString &interval)
|
||||
{
|
||||
return versionIsInInterval(Util::Version(version), interval);
|
||||
return versionIsInInterval(Version(version), interval);
|
||||
}
|
||||
bool Util::versionIsInInterval(const Version &version, const QString &interval)
|
||||
bool versionIsInInterval(const Version &version, const QString &interval)
|
||||
{
|
||||
if (interval.isEmpty() || version.toString() == interval)
|
||||
{
|
||||
@@ -117,7 +108,7 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
|
||||
// check if in range (bottom)
|
||||
if (!bottom.isEmpty())
|
||||
{
|
||||
const auto bottomVersion = Util::Version(bottom);
|
||||
const auto bottomVersion = Version(bottom);
|
||||
if ((start == '[') && !(version >= bottomVersion))
|
||||
{
|
||||
return false;
|
||||
@@ -131,7 +122,7 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
|
||||
// check if in range (top)
|
||||
if (!top.isEmpty())
|
||||
{
|
||||
const auto topVersion = Util::Version(top);
|
||||
const auto topVersion = Version(top);
|
||||
if ((end == ']') && !(version <= topVersion))
|
||||
{
|
||||
return false;
|
||||
@@ -147,4 +138,3 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
110
api/logic/Version.h
Normal file
110
api/logic/Version.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QUrl;
|
||||
|
||||
struct MULTIMC_LOGIC_EXPORT Version
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
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-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include "modutils.h"
|
||||
#include "TestUtil.h"
|
||||
#include <Version.h>
|
||||
|
||||
class ModUtilsTest : public QObject
|
||||
{
|
||||
@@ -95,59 +95,29 @@ private slots:
|
||||
QFETCH(QString, interval);
|
||||
QFETCH(bool, result);
|
||||
|
||||
QCOMPARE(Util::versionIsInInterval(version, interval), result);
|
||||
QCOMPARE(versionIsInInterval(version, interval), result);
|
||||
}
|
||||
|
||||
void test_versionCompareLessThan_data()
|
||||
void test_versionCompare_data()
|
||||
{
|
||||
setupVersions();
|
||||
}
|
||||
void test_versionCompareLessThan()
|
||||
void test_versionCompare()
|
||||
{
|
||||
QFETCH(QString, first);
|
||||
QFETCH(QString, second);
|
||||
QFETCH(bool, lessThan);
|
||||
QFETCH(bool, equal);
|
||||
|
||||
const auto v1 = Util::Version(first);
|
||||
const auto v2 = Util::Version(second);
|
||||
const auto v1 = Version(first);
|
||||
const auto v2 = Version(second);
|
||||
|
||||
QCOMPARE(v1 < v2, lessThan);
|
||||
}
|
||||
void test_versionCompareGreaterThan_data()
|
||||
{
|
||||
setupVersions();
|
||||
}
|
||||
void test_versionCompareGreaterThan()
|
||||
{
|
||||
QFETCH(QString, first);
|
||||
QFETCH(QString, second);
|
||||
QFETCH(bool, lessThan);
|
||||
QFETCH(bool, equal);
|
||||
|
||||
const auto v1 = Util::Version(first);
|
||||
const auto v2 = Util::Version(second);
|
||||
|
||||
QCOMPARE(v1 > v2, !lessThan && !equal);
|
||||
}
|
||||
void test_versionCompareEqual_data()
|
||||
{
|
||||
setupVersions();
|
||||
}
|
||||
void test_versionCompareEqual()
|
||||
{
|
||||
QFETCH(QString, first);
|
||||
QFETCH(QString, second);
|
||||
QFETCH(bool, lessThan);
|
||||
QFETCH(bool, equal);
|
||||
|
||||
const auto v1 = Util::Version(first);
|
||||
const auto v2 = Util::Version(second);
|
||||
|
||||
QCOMPARE(v1 == v2, equal);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(ModUtilsTest)
|
||||
|
||||
#include "tst_modutils.moc"
|
||||
#include "Version_test.moc"
|
||||
7
api/logic/icons/IIconList.cpp
Normal file
7
api/logic/icons/IIconList.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "IIconList.h"
|
||||
|
||||
// blargh
|
||||
IIconList::~IIconList()
|
||||
{
|
||||
}
|
||||
|
||||
25
api/logic/icons/IIconList.h
Normal file
25
api/logic/icons/IIconList.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
enum IconType : unsigned
|
||||
{
|
||||
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;
|
||||
};
|
||||
160
api/logic/java/JavaChecker.cpp
Normal file
160
api/logic/java/JavaChecker.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "JavaChecker.h"
|
||||
#include <FileSystem.h>
|
||||
#include <Commandline.h>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QMap>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void JavaChecker::performCheck()
|
||||
{
|
||||
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
args.append({"-jar", checkerJar});
|
||||
process->setArguments(args);
|
||||
process->setProgram(m_path);
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
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();
|
||||
}
|
||||
|
||||
void JavaChecker::stdoutReady()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||
{
|
||||
killTimer.stop();
|
||||
QProcessPtr _process;
|
||||
_process.swap(process);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
emit checkFinished(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void JavaChecker::timeout()
|
||||
{
|
||||
// NO MERCY. NO ABUSE.
|
||||
if(process)
|
||||
{
|
||||
qDebug() << "Java checker has been killed by timeout.";
|
||||
process->kill();
|
||||
}
|
||||
}
|
||||
@@ -3,40 +3,58 @@
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "JavaVersion.h"
|
||||
|
||||
class JavaChecker;
|
||||
|
||||
|
||||
struct JavaCheckResult
|
||||
struct MULTIMC_LOGIC_EXPORT JavaCheckResult
|
||||
{
|
||||
QString path;
|
||||
QString mojangPlatform;
|
||||
QString realPlatform;
|
||||
QString javaVersion;
|
||||
bool valid = false;
|
||||
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;
|
||||
class JavaChecker : public QObject
|
||||
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JavaChecker(QObject *parent = 0);
|
||||
void performCheck();
|
||||
|
||||
QString path;
|
||||
int id;
|
||||
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);
|
||||
private:
|
||||
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();
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -14,15 +14,13 @@
|
||||
*/
|
||||
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "pathutils.h"
|
||||
#include "MultiMC.h"
|
||||
|
||||
#include "logger/QsLog.h"
|
||||
#include <QDebug>
|
||||
|
||||
void JavaCheckerJob::partFinished(JavaCheckResult result)
|
||||
{
|
||||
num_finished++;
|
||||
QLOG_INFO() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
|
||||
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
|
||||
<< javacheckers.size();
|
||||
emit progress(num_finished, javacheckers.size());
|
||||
|
||||
@@ -34,9 +32,9 @@ void JavaCheckerJob::partFinished(JavaCheckResult result)
|
||||
}
|
||||
}
|
||||
|
||||
void JavaCheckerJob::start()
|
||||
void JavaCheckerJob::executeTask()
|
||||
{
|
||||
QLOG_INFO() << m_job_name.toLocal8Bit() << " started.";
|
||||
qDebug() << m_job_name.toLocal8Bit() << " started.";
|
||||
m_running = true;
|
||||
for (auto iter : javacheckers)
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -16,18 +16,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtNetwork>
|
||||
#include <QLabel>
|
||||
#include "JavaChecker.h"
|
||||
#include "logic/tasks/ProgressProvider.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class JavaCheckerJob;
|
||||
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
|
||||
class JavaCheckerJob : public ProgressProvider
|
||||
class JavaCheckerJob : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JavaCheckerJob(QString job_name) : ProgressProvider(), m_job_name(job_name) {};
|
||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
||||
|
||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||
{
|
||||
@@ -36,7 +35,7 @@ public:
|
||||
// if this is already running, the action needs to be started right away!
|
||||
if (isRunning())
|
||||
{
|
||||
emit progress(current_progress, total_progress);
|
||||
setProgress(current_progress, total_progress);
|
||||
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
|
||||
base->performCheck();
|
||||
@@ -59,24 +58,21 @@ public:
|
||||
{
|
||||
return javacheckers.size();
|
||||
}
|
||||
virtual bool isRunning() const
|
||||
virtual bool isRunning() const override
|
||||
{
|
||||
return m_running;
|
||||
}
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void progress(int current, int total);
|
||||
void finished(QList<JavaCheckResult>);
|
||||
public
|
||||
slots:
|
||||
virtual void start();
|
||||
// FIXME: implement
|
||||
virtual void abort() {};
|
||||
private
|
||||
slots:
|
||||
|
||||
private slots:
|
||||
void partFinished(JavaCheckResult result);
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
QString m_job_name;
|
||||
QList<JavaCheckerPtr> javacheckers;
|
||||
28
api/logic/java/JavaInstall.cpp
Normal file
28
api/logic/java/JavaInstall.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "JavaInstall.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator==(const JavaInstall &rhs)
|
||||
{
|
||||
return arch == rhs.arch && id == rhs.id && path == rhs.path;
|
||||
}
|
||||
|
||||
bool JavaInstall::operator>(const JavaInstall &rhs)
|
||||
{
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
}
|
||||
38
api/logic/java/JavaInstall.h
Normal file
38
api/logic/java/JavaInstall.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseVersion.h"
|
||||
#include "JavaVersion.h"
|
||||
|
||||
struct JavaInstall : public BaseVersion
|
||||
{
|
||||
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 typeString() const
|
||||
{
|
||||
return arch;
|
||||
}
|
||||
|
||||
bool operator<(const JavaInstall & rhs);
|
||||
bool operator==(const JavaInstall & rhs);
|
||||
bool operator>(const JavaInstall & rhs);
|
||||
|
||||
JavaVersion id;
|
||||
QString arch;
|
||||
QString path;
|
||||
bool recommended = false;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;
|
||||
186
api/logic/java/JavaInstallList.cpp
Normal file
186
api/logic/java/JavaInstallList.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <QtNetwork>
|
||||
#include <QtXml>
|
||||
#include <QRegExp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "java/JavaCheckerJob.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "minecraft/VersionFilterData.h"
|
||||
|
||||
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Task *JavaInstallList::getLoadTask()
|
||||
{
|
||||
return new JavaListLoadTask(this);
|
||||
}
|
||||
|
||||
const BaseVersionPtr JavaInstallList::at(int i) const
|
||||
{
|
||||
return m_vlist.at(i);
|
||||
}
|
||||
|
||||
bool JavaInstallList::isLoaded()
|
||||
{
|
||||
return m_loaded;
|
||||
}
|
||||
|
||||
int JavaInstallList::count() const
|
||||
{
|
||||
return m_vlist.count();
|
||||
}
|
||||
|
||||
QVariant JavaInstallList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
BaseVersionList::RoleList JavaInstallList::providesRoles() const
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void JavaInstallList::sortVersions()
|
||||
{
|
||||
beginResetModel();
|
||||
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task()
|
||||
{
|
||||
m_list = vlist;
|
||||
m_currentRecommended = NULL;
|
||||
}
|
||||
|
||||
JavaListLoadTask::~JavaListLoadTask()
|
||||
{
|
||||
}
|
||||
|
||||
void JavaListLoadTask::executeTask()
|
||||
{
|
||||
setStatus(tr("Detecting Java installations..."));
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
id++;
|
||||
}
|
||||
|
||||
m_job->start();
|
||||
}
|
||||
|
||||
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
|
||||
{
|
||||
QList<JavaInstallPtr> candidates;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
if (bp_java)
|
||||
{
|
||||
javas_bvp.append(java);
|
||||
}
|
||||
}
|
||||
|
||||
m_list->updateListData(javas_bvp);
|
||||
emitSucceeded();
|
||||
}
|
||||
71
api/logic/java/JavaInstallList.h
Normal file
71
api/logic/java/JavaInstallList.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <QObject>
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "BaseVersionList.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaInstall.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class JavaListLoadTask;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
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;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
virtual RoleList providesRoles() const override;
|
||||
|
||||
public slots:
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
|
||||
protected:
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
|
||||
bool m_loaded = false;
|
||||
};
|
||||
|
||||
class JavaListLoadTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit JavaListLoadTask(JavaInstallList *vlist);
|
||||
~JavaListLoadTask();
|
||||
|
||||
virtual void executeTask();
|
||||
public slots:
|
||||
void javaCheckerFinished(QList<JavaCheckResult> results);
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
JavaInstallList *m_list;
|
||||
JavaInstall *m_currentRecommended;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -18,23 +18,21 @@
|
||||
#include <QDir>
|
||||
#include <QStringList>
|
||||
|
||||
#include <logic/settings/Setting.h>
|
||||
#include <pathutils.h>
|
||||
#include <settings/Setting.h>
|
||||
|
||||
#include "MultiMC.h"
|
||||
|
||||
#include "logger/QsLog.h"
|
||||
#include "logic/java/JavaUtils.h"
|
||||
#include "logic/java/JavaCheckerJob.h"
|
||||
#include "logic/java/JavaVersionList.h"
|
||||
#include <QDebug>
|
||||
#include "java/JavaUtils.h"
|
||||
#include "java/JavaCheckerJob.h"
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
JavaUtils::JavaUtils()
|
||||
{
|
||||
}
|
||||
|
||||
JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
|
||||
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
|
||||
{
|
||||
JavaVersionPtr javaVersion(new JavaVersion());
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = id;
|
||||
javaVersion->arch = arch;
|
||||
@@ -43,21 +41,25 @@ JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
JavaVersionPtr JavaUtils::GetDefaultJava()
|
||||
JavaInstallPtr JavaUtils::GetDefaultJava()
|
||||
{
|
||||
JavaVersionPtr javaVersion(new JavaVersion());
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = "java";
|
||||
javaVersion->arch = "unknown";
|
||||
#if defined(Q_OS_WIN32)
|
||||
javaVersion->path = "javaw";
|
||||
#else
|
||||
javaVersion->path = "java";
|
||||
#endif
|
||||
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
|
||||
#if defined(Q_OS_WIN32)
|
||||
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
|
||||
{
|
||||
QList<JavaVersionPtr> javas;
|
||||
QList<JavaInstallPtr> javas;
|
||||
|
||||
QString archType = "unknown";
|
||||
if (keyType == KEY_WOW64_64KEY)
|
||||
@@ -117,12 +119,12 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
&valueSz);
|
||||
|
||||
// Now, we construct the version object and add it to the list.
|
||||
JavaVersionPtr javaVersion(new JavaVersion());
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
|
||||
javaVersion->id = subKeyName;
|
||||
javaVersion->arch = archType;
|
||||
javaVersion->path =
|
||||
QDir(PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
|
||||
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
|
||||
javas.append(javaVersion);
|
||||
}
|
||||
|
||||
@@ -140,29 +142,31 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QList<JavaVersionPtr> java_candidates;
|
||||
QList<JavaInstallPtr> java_candidates;
|
||||
|
||||
QList<JavaVersionPtr> JRE64s = this->FindJavaFromRegistryKey(
|
||||
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaVersionPtr> JDK64s = this->FindJavaFromRegistryKey(
|
||||
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
|
||||
QList<JavaVersionPtr> JRE32s = this->FindJavaFromRegistryKey(
|
||||
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
|
||||
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
|
||||
QList<JavaVersionPtr> JDK32s = this->FindJavaFromRegistryKey(
|
||||
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));
|
||||
|
||||
QList<QString> candidates;
|
||||
for(JavaVersionPtr java_candidate : java_candidates)
|
||||
for(JavaInstallPtr java_candidate : java_candidates)
|
||||
{
|
||||
if(!candidates.contains(java_candidate->path))
|
||||
{
|
||||
@@ -173,7 +177,7 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
return candidates;
|
||||
}
|
||||
|
||||
#elif OSX
|
||||
#elif defined(Q_OS_MAC)
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QList<QString> javas;
|
||||
@@ -196,22 +200,49 @@ QList<QString> JavaUtils::FindJavaPaths()
|
||||
return javas;
|
||||
}
|
||||
|
||||
#elif LINUX
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QList<QString> JavaUtils::FindJavaPaths()
|
||||
{
|
||||
QLOG_INFO() << "Linux Java detection incomplete - defaulting to \"java\"";
|
||||
qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
|
||||
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
javas.append("/opt/java/bin/java");
|
||||
javas.append("/usr/bin/java");
|
||||
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();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
QLOG_INFO() << "Unknown operating system build - defaulting to \"java\"";
|
||||
qDebug() << "Unknown operating system build - defaulting to \"java\"";
|
||||
|
||||
QList<QString> javas;
|
||||
javas.append(this->GetDefaultJava()->path);
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -16,28 +16,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <osutils.h>
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaChecker.h"
|
||||
#include "JavaVersionList.h"
|
||||
#include "JavaInstallList.h"
|
||||
|
||||
#if WINDOWS
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class JavaUtils : public QObject
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
JavaUtils();
|
||||
|
||||
JavaVersionPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
|
||||
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
|
||||
QList<QString> FindJavaPaths();
|
||||
JavaVersionPtr GetDefaultJava();
|
||||
JavaInstallPtr GetDefaultJava();
|
||||
|
||||
#if WINDOWS
|
||||
QList<JavaVersionPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
|
||||
#ifdef Q_OS_WIN
|
||||
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
|
||||
#endif
|
||||
};
|
||||
112
api/logic/java/JavaVersion.cpp
Normal file
112
api/logic/java/JavaVersion.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include "JavaVersion.h"
|
||||
#include <MMCStrings.h>
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
JavaVersion & JavaVersion::operator=(const QString & 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();
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
JavaVersion::JavaVersion(const QString &rhs)
|
||||
{
|
||||
operator=(rhs);
|
||||
}
|
||||
|
||||
QString JavaVersion::toString()
|
||||
{
|
||||
return m_string;
|
||||
}
|
||||
|
||||
bool JavaVersion::requiresPermGen()
|
||||
{
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool JavaVersion::operator>(const JavaVersion &rhs)
|
||||
{
|
||||
return (!operator<(rhs)) && (!operator==(rhs));
|
||||
}
|
||||
42
api/logic/java/JavaVersion.h
Normal file
42
api/logic/java/JavaVersion.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include <QString>
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaVersion
|
||||
{
|
||||
friend class JavaVersionTest;
|
||||
public:
|
||||
JavaVersion() {};
|
||||
JavaVersion(const QString & rhs);
|
||||
|
||||
JavaVersion & operator=(const QString & rhs);
|
||||
|
||||
bool operator<(const JavaVersion & rhs);
|
||||
bool operator==(const JavaVersion & rhs);
|
||||
bool operator>(const JavaVersion & rhs);
|
||||
|
||||
bool requiresPermGen();
|
||||
|
||||
QString toString();
|
||||
|
||||
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;
|
||||
};
|
||||
116
api/logic/java/JavaVersion_test.cpp
Normal file
116
api/logic/java/JavaVersion_test.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#include <QTest>
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "java/JavaVersion.h"
|
||||
|
||||
class JavaVersionTest : public QObject
|
||||
{
|
||||
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");
|
||||
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
// 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)
|
||||
|
||||
#include "JavaVersion_test.moc"
|
||||
136
api/logic/java/launch/CheckJava.cpp
Normal file
136
api/logic/java/launch/CheckJava.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "CheckJava.h"
|
||||
#include <launch/LaunchTask.h>
|
||||
#include <FileSystem.h>
|
||||
#include <QStandardPaths>
|
||||
#include <QFileInfo>
|
||||
#include <sys.h>
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
api/logic/java/launch/CheckJava.h
Normal file
45
api/logic/java/launch/CheckJava.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 <launch/LaunchStep.h>
|
||||
#include <LoggedProcess.h>
|
||||
#include <java/JavaChecker.h>
|
||||
|
||||
class CheckJava: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
|
||||
virtual ~CheckJava() {};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
private slots:
|
||||
void checkJavaFinished(JavaCheckResult result);
|
||||
|
||||
private:
|
||||
void printJavaInfo(const QString & version, const QString & architecture);
|
||||
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
|
||||
|
||||
private:
|
||||
QString m_javaPath;
|
||||
qlonglong m_javaUnixTime;
|
||||
JavaCheckerPtr m_JavaChecker;
|
||||
};
|
||||
27
api/logic/launch/LaunchStep.cpp
Normal file
27
api/logic/launch/LaunchStep.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "LaunchStep.h"
|
||||
#include "LaunchTask.h"
|
||||
|
||||
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);
|
||||
}
|
||||
50
api/logic/launch/LaunchStep.h
Normal file
50
api/logic/launch/LaunchStep.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "tasks/Task.h"
|
||||
#include "MessageLevel.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
class LaunchTask;
|
||||
class LaunchStep: public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public: /* methods */
|
||||
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
|
||||
{
|
||||
bind(parent);
|
||||
};
|
||||
virtual ~LaunchStep() {};
|
||||
|
||||
protected: /* methods */
|
||||
virtual void bind(LaunchTask *parent);
|
||||
|
||||
signals:
|
||||
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() {};
|
||||
|
||||
protected: /* data */
|
||||
LaunchTask *m_parent;
|
||||
};
|
||||
280
api/logic/launch/LaunchTask.cpp
Normal file
280
api/logic/launch/LaunchTask.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
* 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 "launch/LaunchTask.h"
|
||||
#include "MessageLevel.h"
|
||||
#include "MMCStrings.h"
|
||||
#include "java/JavaChecker.h"
|
||||
#include "tasks/Task.h"
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QEventLoop>
|
||||
#include <QRegularExpression>
|
||||
#include <QCoreApplication>
|
||||
#include <QStandardPaths>
|
||||
#include <assert.h>
|
||||
|
||||
void LaunchTask::init()
|
||||
{
|
||||
m_instance->setRunning(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
{
|
||||
std::shared_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)
|
||||
{
|
||||
m_steps.append(step);
|
||||
}
|
||||
|
||||
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.prepend(step);
|
||||
}
|
||||
|
||||
void LaunchTask::executeTask()
|
||||
{
|
||||
m_instance->setCrashed(false);
|
||||
if(!m_steps.size())
|
||||
{
|
||||
state = LaunchTask::Finished;
|
||||
emitSucceeded();
|
||||
}
|
||||
state = LaunchTask::Running;
|
||||
onStepFinished();
|
||||
}
|
||||
|
||||
void LaunchTask::onReadyForLaunch()
|
||||
{
|
||||
state = LaunchTask::Waiting;
|
||||
emit readyForLaunch();
|
||||
}
|
||||
|
||||
void LaunchTask::onStepFinished()
|
||||
{
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void LaunchTask::onProgressReportingRequested()
|
||||
{
|
||||
state = LaunchTask::Waiting;
|
||||
emit requestProgress(m_steps[currentStep].get());
|
||||
}
|
||||
|
||||
void LaunchTask::setCensorFilter(QMap<QString, QString> 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;
|
||||
}
|
||||
|
||||
void LaunchTask::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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum 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 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);
|
||||
|
||||
auto &model = *getLogModel();
|
||||
model.append(level, line);
|
||||
}
|
||||
|
||||
void LaunchTask::emitSucceeded()
|
||||
{
|
||||
m_instance->setRunning(false);
|
||||
Task::emitSucceeded();
|
||||
}
|
||||
|
||||
void LaunchTask::emitFailed(QString 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;
|
||||
}
|
||||
|
||||
125
api/logic/launch/LaunchTask.h
Normal file
125
api/logic/launch/LaunchTask.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
* 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 <QProcess>
|
||||
#include <QObjectPtr.h>
|
||||
#include "LogModel.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "MessageLevel.h"
|
||||
#include "LoggedProcess.h"
|
||||
#include "LaunchStep.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT LaunchTask: public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
explicit LaunchTask(InstancePtr instance);
|
||||
void init();
|
||||
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
NotStarted,
|
||||
Running,
|
||||
Waiting,
|
||||
Failed,
|
||||
Aborted,
|
||||
Finished
|
||||
};
|
||||
|
||||
public: /* methods */
|
||||
static std::shared_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);
|
||||
|
||||
InstancePtr instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void setPid(qint64 pid)
|
||||
{
|
||||
m_pid = pid;
|
||||
}
|
||||
|
||||
qint64 pid()
|
||||
{
|
||||
return m_pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief prepare the process for launch (for multi-stage launch)
|
||||
*/
|
||||
virtual void executeTask() override;
|
||||
|
||||
/**
|
||||
* @brief launch the armed instance
|
||||
*/
|
||||
void proceed();
|
||||
|
||||
/**
|
||||
* @brief abort launch
|
||||
*/
|
||||
bool abort() override;
|
||||
|
||||
bool canAbort() const override;
|
||||
|
||||
shared_qobject_ptr<LogModel> getLogModel();
|
||||
|
||||
public:
|
||||
QString substituteVariables(const QString &cmd) const;
|
||||
QString censorPrivateInfo(QString in);
|
||||
|
||||
protected: /* methods */
|
||||
virtual void emitFailed(QString reason) override;
|
||||
virtual void emitSucceeded() override;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief emitted when the launch preparations are done
|
||||
*/
|
||||
void readyForLaunch();
|
||||
|
||||
void requestProgress(Task *task);
|
||||
|
||||
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();
|
||||
|
||||
private: /*methods */
|
||||
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;
|
||||
};
|
||||
149
api/logic/launch/LogModel.cpp
Normal file
149
api/logic/launch/LogModel.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include "LogModel.h"
|
||||
|
||||
LogModel::LogModel(QObject *parent):QAbstractListModel(parent)
|
||||
{
|
||||
m_content.resize(m_maxLines);
|
||||
}
|
||||
|
||||
int LogModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
|
||||
return m_numLines;
|
||||
}
|
||||
|
||||
QVariant LogModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void LogModel::suspend(bool suspend)
|
||||
{
|
||||
m_suspended = suspend;
|
||||
}
|
||||
|
||||
void LogModel::clear()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int LogModel::getMaxLines()
|
||||
{
|
||||
return m_maxLines;
|
||||
}
|
||||
|
||||
void LogModel::setStopOnOverflow(bool stop)
|
||||
{
|
||||
m_stopOnOverflow = stop;
|
||||
}
|
||||
|
||||
void LogModel::setOverflowMessage(const QString& overflowMessage)
|
||||
{
|
||||
m_overflowMessage = overflowMessage;
|
||||
}
|
||||
54
api/logic/launch/LogModel.h
Normal file
54
api/logic/launch/LogModel.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include "MessageLevel.h"
|
||||
|
||||
#include <multimc_logic_export.h>
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LogModel(QObject *parent = 0);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
|
||||
void append(MessageLevel::Enum, QString line);
|
||||
void clear();
|
||||
void suspend(bool suspend);
|
||||
|
||||
QString toPlainText();
|
||||
|
||||
int getMaxLines();
|
||||
void setMaxLines(int maxLines);
|
||||
void setStopOnOverflow(bool stop);
|
||||
void setOverflowMessage(const QString & overflowMessage);
|
||||
|
||||
enum Roles
|
||||
{
|
||||
LevelRole = Qt::UserRole
|
||||
};
|
||||
|
||||
private /* types */:
|
||||
struct entry
|
||||
{
|
||||
MessageLevel::Enum level;
|
||||
QString line;
|
||||
};
|
||||
|
||||
private: /* data */
|
||||
QVector <entry> m_content;
|
||||
int m_maxLines = 1000;
|
||||
// first line in the circular buffer
|
||||
int m_firstLine = 0;
|
||||
// number of lines occupied in the circular buffer
|
||||
int m_numLines = 0;
|
||||
bool m_stopOnOverflow = false;
|
||||
QString m_overflowMessage = "OVERFLOW";
|
||||
bool m_suspended = false;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(LogModel)
|
||||
};
|
||||
84
api/logic/launch/steps/PostLaunchCommand.cpp
Normal file
84
api/logic/launch/steps/PostLaunchCommand.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/* Copyright 2013-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
|
||||
*
|
||||
* 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 "PostLaunchCommand.h"
|
||||
#include <launch/LaunchTask.h>
|
||||
|
||||
PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
|
||||
{
|
||||
auto instance = m_parent->instance();
|
||||
m_command = instance->getPostExitCommand();
|
||||
m_process.setProcessEnvironment(instance->createEnvironment());
|
||||
connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines);
|
||||
connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state);
|
||||
}
|
||||
|
||||
void PostLaunchCommand::executeTask()
|
||||
{
|
||||
QString postlaunch_cmd = m_parent->substituteVariables(m_command);
|
||||
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC);
|
||||
m_process.start(postlaunch_cmd);
|
||||
}
|
||||
|
||||
void PostLaunchCommand::on_state(LoggedProcess::State state)
|
||||
{
|
||||
auto getError = [&]()
|
||||
{
|
||||
return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode());
|
||||
};
|
||||
switch(state)
|
||||
{
|
||||
case LoggedProcess::Aborted:
|
||||
case LoggedProcess::Crashed:
|
||||
case LoggedProcess::FailedToStart:
|
||||
{
|
||||
auto error = getError();
|
||||
emit logLine(error, MessageLevel::Fatal);
|
||||
emitFailed(error);
|
||||
return;
|
||||
}
|
||||
case LoggedProcess::Finished:
|
||||
{
|
||||
if(m_process.exitCode() != 0)
|
||||
{
|
||||
auto error = getError();
|
||||
emit logLine(error, MessageLevel::Fatal);
|
||||
emitFailed(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC);
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PostLaunchCommand::setWorkingDirectory(const QString &wd)
|
||||
{
|
||||
m_process.setWorkingDirectory(wd);
|
||||
}
|
||||
|
||||
bool PostLaunchCommand::abort()
|
||||
{
|
||||
auto state = m_process.state();
|
||||
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
|
||||
{
|
||||
m_process.kill();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2014 MultiMC Contributors
|
||||
/* Copyright 2013-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.
|
||||
@@ -15,27 +15,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <launch/LaunchStep.h>
|
||||
#include <LoggedProcess.h>
|
||||
|
||||
class ProgressProvider : public QObject
|
||||
class PostLaunchCommand: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
explicit ProgressProvider(QObject *parent = 0) : QObject(parent)
|
||||
{
|
||||
}
|
||||
signals:
|
||||
void started();
|
||||
void progress(qint64 current, qint64 total);
|
||||
void succeeded();
|
||||
void failed(QString reason);
|
||||
void status(QString status);
|
||||
|
||||
public:
|
||||
virtual ~ProgressProvider() {}
|
||||
virtual bool isRunning() const = 0;
|
||||
public
|
||||
slots:
|
||||
virtual void start() = 0;
|
||||
virtual void abort() = 0;
|
||||
explicit PostLaunchCommand(LaunchTask *parent);
|
||||
virtual void executeTask();
|
||||
virtual bool abort();
|
||||
virtual bool canAbort() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void setWorkingDirectory(const QString &wd);
|
||||
private slots:
|
||||
void on_state(LoggedProcess::State state);
|
||||
|
||||
private:
|
||||
LoggedProcess m_process;
|
||||
QString m_command;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user