mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-13 20:22:13 +00:00
Compare commits
327 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6284f070c1 | ||
|
|
be9063317e | ||
|
|
8ec36e2cb9 | ||
|
|
8cefc76108 | ||
|
|
172f83c7e2 | ||
|
|
b1e0cbf852 | ||
|
|
9b7f82ff26 | ||
|
|
67cef79d81 | ||
|
|
7e1c5d439a | ||
|
|
38ed0c2a1f | ||
|
|
9b7564e967 | ||
|
|
15926b2b4a | ||
|
|
6323aae56f | ||
|
|
c9c6037f50 | ||
|
|
97b74ef56a | ||
|
|
df6e66101c | ||
|
|
bbd523acb8 | ||
|
|
67d2f283da | ||
|
|
4530d9064b | ||
|
|
3406335cd8 | ||
|
|
c9832d0d86 | ||
|
|
fa8f19b046 | ||
|
|
370bbada87 | ||
|
|
1ef416cb56 | ||
|
|
b46a34d0ae | ||
|
|
6188c577e3 | ||
|
|
40a30b67f4 | ||
|
|
12b304ea73 | ||
|
|
8e44ab2338 | ||
|
|
4c7ea0f99a | ||
|
|
a1c713811c | ||
|
|
106155dd62 | ||
|
|
303842a19e | ||
|
|
ea151ca9d4 | ||
|
|
6e69370fbf | ||
|
|
82208be49e | ||
|
|
b497aee920 | ||
|
|
0812e3a87b | ||
|
|
b8ca36372b | ||
|
|
2d295d5afb | ||
|
|
ab3fe74c97 | ||
|
|
1a43f28297 | ||
|
|
093dd22826 | ||
|
|
06ccff0f47 | ||
|
|
65bca65489 | ||
|
|
a7957f24ba | ||
|
|
2ea22d407d | ||
|
|
22b32fce12 | ||
|
|
2c219df061 | ||
|
|
604295e6d5 | ||
|
|
f259e9f727 | ||
|
|
38e669dbf5 | ||
|
|
ca11765436 | ||
|
|
354ed2e7f0 | ||
|
|
a08afb8172 | ||
|
|
2dac9d02d8 | ||
|
|
b3fb437f8e | ||
|
|
f115bdf5b8 | ||
|
|
41aef8414a | ||
|
|
83649b5d52 | ||
|
|
595d157180 | ||
|
|
3843a4ddb9 | ||
|
|
088e8e0eff | ||
|
|
418251bd86 | ||
|
|
0bcb24502e | ||
|
|
3277b820a7 | ||
|
|
d66ae206dd | ||
|
|
cd55674b36 | ||
|
|
166e5a03d6 | ||
|
|
0c2e2094ee | ||
|
|
c33b4e252f | ||
|
|
0942867ecc | ||
|
|
e8336babad | ||
|
|
d0e58acd84 | ||
|
|
360d877abf | ||
|
|
7ea1d68244 | ||
|
|
b7f28a92d4 | ||
|
|
1dbc4e16f7 | ||
|
|
0636c03d7c | ||
|
|
ee341b78ba | ||
|
|
9510a1bbf2 | ||
|
|
03a93b8e4a | ||
|
|
b08ec1e97a | ||
|
|
d7ed472e68 | ||
|
|
7cd13302c5 | ||
|
|
4340068a84 | ||
|
|
c7032ce68a | ||
|
|
58ead6a1f4 | ||
|
|
5937b1c3d4 | ||
|
|
6c30c84b11 | ||
|
|
75c0046f41 | ||
|
|
719f3e863a | ||
|
|
50ca6cbb4d | ||
|
|
257f8ca9fd | ||
|
|
642c3f1d09 | ||
|
|
c2726037ce | ||
|
|
9eb0525dab | ||
|
|
fb7897a6f4 | ||
|
|
5858483592 | ||
|
|
6fd91be137 | ||
|
|
245a19e9df | ||
|
|
16e390d2e7 | ||
|
|
daf9d0eaa7 | ||
|
|
f18afd3d1e | ||
|
|
ef2cbe16e6 | ||
|
|
57accb1cbb | ||
|
|
6d034bda82 | ||
|
|
44475350eb | ||
|
|
df1ec1f7c2 | ||
|
|
00814830c4 | ||
|
|
c2766e5c6e | ||
|
|
90a3997d2c | ||
|
|
d6fc37e486 | ||
|
|
8eb1397a8a | ||
|
|
4bae6fe491 | ||
|
|
95e6f37d39 | ||
|
|
e0bea1e46a | ||
|
|
6a462d0778 | ||
|
|
0a56b56286 | ||
|
|
30fad998a6 | ||
|
|
34de313feb | ||
|
|
85ae710d40 | ||
|
|
17c8f31a09 | ||
|
|
3470158943 | ||
|
|
b000b33661 | ||
|
|
322922e013 | ||
|
|
fede712a26 | ||
|
|
87edaa7dcd | ||
|
|
e5da2e36c7 | ||
|
|
e44a0cb944 | ||
|
|
e6d734d9ac | ||
|
|
1489720b90 | ||
|
|
b76bdf9368 | ||
|
|
7add9de1cf | ||
|
|
a6a642eb7e | ||
|
|
c78498f40c | ||
|
|
4b80d34be4 | ||
|
|
d4b82f11ca | ||
|
|
a87d96349a | ||
|
|
3eebc641f9 | ||
|
|
ab870648bd | ||
|
|
1388751fd4 | ||
|
|
eba8e61ce9 | ||
|
|
b88206907e | ||
|
|
4b90a078de | ||
|
|
d8c8a41dfa | ||
|
|
e2a4fbc589 | ||
|
|
edfca7da66 | ||
|
|
a1d501d394 | ||
|
|
ea71281629 | ||
|
|
c51512f940 | ||
|
|
9a2d203c0d | ||
|
|
79d208795c | ||
|
|
d276da1359 | ||
|
|
464bc0f770 | ||
|
|
0595a00090 | ||
|
|
1a38587877 | ||
|
|
719f112f64 | ||
|
|
2b998bb8cc | ||
|
|
eac892965e | ||
|
|
678c7ab271 | ||
|
|
f26ca143c4 | ||
|
|
a269e0c92f | ||
|
|
f8c5cee982 | ||
|
|
c82042dcfa | ||
|
|
b5b16d0972 | ||
|
|
76c7e0fe1c | ||
|
|
fce2b0ce5f | ||
|
|
e9434fce3d | ||
|
|
c707042dd7 | ||
|
|
102804ef82 | ||
|
|
ba3cbb7330 | ||
|
|
9a6c2b0e2c | ||
|
|
b2b0487600 | ||
|
|
9491396292 | ||
|
|
13628e7a82 | ||
|
|
b29382c748 | ||
|
|
4c01983f47 | ||
|
|
3fb4ce713f | ||
|
|
32a2cb5a0d | ||
|
|
a1ef043030 | ||
|
|
b61407a75d | ||
|
|
d80382180e | ||
|
|
b8adbb9b73 | ||
|
|
6381bfdb88 | ||
|
|
3ed990861a | ||
|
|
2c1ca040f8 | ||
|
|
300aa9e6d6 | ||
|
|
8227725adb | ||
|
|
53bf21ddcd | ||
|
|
59b681a174 | ||
|
|
6e25624623 | ||
|
|
60d10201e9 | ||
|
|
0cd55d943e | ||
|
|
48274e889f | ||
|
|
9d3a847555 | ||
|
|
ced67a7400 | ||
|
|
4d54d2662a | ||
|
|
140c31293c | ||
|
|
8cf88ffc58 | ||
|
|
117bfef151 | ||
|
|
632f232e36 | ||
|
|
bea1b5de5e | ||
|
|
c19f6d4dcd | ||
|
|
afb0db24a8 | ||
|
|
5ea170db78 | ||
|
|
e5b4b5d295 | ||
|
|
fbeceaa98c | ||
|
|
50697735b5 | ||
|
|
1797f45e8f | ||
|
|
8dd9987a9c | ||
|
|
259021bc98 | ||
|
|
26f7f017d4 | ||
|
|
71b129538b | ||
|
|
d6ab4b4a7f | ||
|
|
74c455ff35 | ||
|
|
36f3e24cf3 | ||
|
|
89d3a66658 | ||
|
|
9583d0d8ee | ||
|
|
2973b11d3e | ||
|
|
8a1da91219 | ||
|
|
cdc9bed83f | ||
|
|
7acf1998eb | ||
|
|
50b8412a26 | ||
|
|
2a81e21f5e | ||
|
|
3597a50854 | ||
|
|
4133247bff | ||
|
|
c329bc73c0 | ||
|
|
d432d8ecfc | ||
|
|
8bd8be95f0 | ||
|
|
572a6026b5 | ||
|
|
d70c783de8 | ||
|
|
e1a03e8724 | ||
|
|
868669a497 | ||
|
|
4cf4110d9d | ||
|
|
e029b7764e | ||
|
|
8a526fab0e | ||
|
|
8fe18cfabc | ||
|
|
0f311e12ee | ||
|
|
01058b1db1 | ||
|
|
c407004e3a | ||
|
|
3fb15e4a4d | ||
|
|
a379d43d7c | ||
|
|
3263b52e0c | ||
|
|
15c829fd3c | ||
|
|
349381cb2b | ||
|
|
4183cc203f | ||
|
|
2b3e87b7d1 | ||
|
|
e5147e6b65 | ||
|
|
9d4c188fd4 | ||
|
|
e854894a3c | ||
|
|
0ce44dbd41 | ||
|
|
35836c7709 | ||
|
|
e76e6329cd | ||
|
|
0efa714ba5 | ||
|
|
907aa36704 | ||
|
|
6a8bb3691b | ||
|
|
0132fd9929 | ||
|
|
9bde1c8512 | ||
|
|
b20688a18d | ||
|
|
21df531db1 | ||
|
|
f06ac02396 | ||
|
|
794102b32c | ||
|
|
1be99b075a | ||
|
|
e1465f4848 | ||
|
|
243f7e4fb4 | ||
|
|
edc25dab17 | ||
|
|
6dc1bc65e1 | ||
|
|
6fe9258161 | ||
|
|
4fa3e2a714 | ||
|
|
d25a7ad3a6 | ||
|
|
3f24c4cfe5 | ||
|
|
b414bbe395 | ||
|
|
8084f27ec8 | ||
|
|
249baf6fcf | ||
|
|
8ace5fa91d | ||
|
|
30863a88ab | ||
|
|
77a1d39f6b | ||
|
|
6a525db78d | ||
|
|
1d71214d4a | ||
|
|
ab5045b54c | ||
|
|
f3c46dbf11 | ||
|
|
581460dcf9 | ||
|
|
6bd2605a79 | ||
|
|
e9a6199507 | ||
|
|
60777ad8ce | ||
|
|
88041783e6 | ||
|
|
47e075babd | ||
|
|
5565a2f85e | ||
|
|
c4c8e99681 | ||
|
|
fc28aacdea | ||
|
|
07cde802e4 | ||
|
|
2aaf9827a6 | ||
|
|
910766458d | ||
|
|
be53eb66f8 | ||
|
|
db7357d008 | ||
|
|
795889d934 | ||
|
|
8e58d61150 | ||
|
|
dddc5cedf3 | ||
|
|
d864c95e2b | ||
|
|
dff307557b | ||
|
|
643d74f66c | ||
|
|
f565798650 | ||
|
|
e0596d3c86 | ||
|
|
2ac0edbbdb | ||
|
|
53188386b8 | ||
|
|
af3384c649 | ||
|
|
22735f275e | ||
|
|
5fabb4f254 | ||
|
|
6f2a87167a | ||
|
|
3aa28bd64a | ||
|
|
da4ae1bc1e | ||
|
|
77f27a628f | ||
|
|
f557c13679 | ||
|
|
2660418d58 | ||
|
|
e46aba9da5 | ||
|
|
0060b50625 | ||
|
|
40cf38bc32 | ||
|
|
f6eb8fa1e4 | ||
|
|
2980322c3b | ||
|
|
8321187a20 | ||
|
|
ab868df50e | ||
|
|
1fbe03f982 | ||
|
|
7382360771 | ||
|
|
8cbe13c656 | ||
|
|
160b5033a7 | ||
|
|
01cb5ebb35 |
3
.github/ISSUE_TEMPLATE.md
vendored
3
.github/ISSUE_TEMPLATE.md
vendored
@@ -9,8 +9,9 @@ Before submitting this issue, please make sure you have:
|
||||
- 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?!".
|
||||
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
|
||||
5. Place all information below the ---- of lines.
|
||||
- It makes the issue look pretty
|
||||
- It makes the issue look pretty
|
||||
|
||||
If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
|
||||
-->
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +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
|
||||
|
||||
41
.travis.yml
41
.travis.yml
@@ -2,29 +2,28 @@
|
||||
language: cpp
|
||||
cache: apt
|
||||
|
||||
# Build matrix set up
|
||||
compiler:
|
||||
- gcc
|
||||
# - clang
|
||||
os:
|
||||
- linux
|
||||
# - osx
|
||||
env:
|
||||
- QT_VERSION=5.4.2
|
||||
- QT_VERSION=5.5.1
|
||||
# - QT_VERSION=5.6.2
|
||||
matrix:
|
||||
exclude:
|
||||
# only use clang on OS X
|
||||
- os: osx
|
||||
include:
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
# only use the qt available from homebrew
|
||||
- os: osx
|
||||
env: QT_VERSION=5.4.2
|
||||
- os: osx
|
||||
env: QT_VERSION=5.5.1
|
||||
# - os: osx
|
||||
# env: QT_VERSION=5.6
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: precise
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=precise QT_VERSION=5.6.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
|
||||
|
||||
# Install dependencies
|
||||
install:
|
||||
|
||||
20
BUILD.md
20
BUILD.md
@@ -32,11 +32,11 @@ git submodule update
|
||||
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
|
||||
|
||||
## Build dependencies
|
||||
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
|
||||
* Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution.
|
||||
* A C++ compiler capable of building C++11 code.
|
||||
* Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version.
|
||||
* cmake 3.1 or newer
|
||||
* zlib (for example, `zlib1g-dev`)
|
||||
* java (for example, `openjdk-8-jdk`)
|
||||
* Java JDK 8 (for example, `openjdk-8-jdk`)
|
||||
* GL headers (for example, `libgl1-mesa-dev`)
|
||||
|
||||
### Building from command line
|
||||
@@ -64,7 +64,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
1. Run the Qt installer.
|
||||
2. Choose a place to install Qt.
|
||||
3. Choose the components you want to install.
|
||||
- You need Qt 5.4.1/gcc 64-bit ticked.
|
||||
- You need Qt 5.6.x 64-bit ticked.
|
||||
- You need Tools/Qt Creator ticked.
|
||||
- Other components are selected by default, you can untick them if you don't need them.
|
||||
4. Accept the license agreements.
|
||||
@@ -77,7 +77,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
|
||||
4. Read the instructions that just popped up about a build location and choose one.
|
||||
5. You should see "Run CMake" in the window.
|
||||
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)".
|
||||
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
|
||||
- Hit the "Run CMake" button.
|
||||
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
|
||||
- Hit "Finish" if CMake ran successfully.
|
||||
@@ -91,13 +91,13 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
|
||||
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
|
||||
|
||||
## Dependencies
|
||||
* [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
|
||||
* [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
|
||||
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
|
||||
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
|
||||
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
|
||||
* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
|
||||
* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
|
||||
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
|
||||
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
|
||||
* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
|
||||
|
||||
Put it somewhere on the `PATH`, so that it is accessible from the console.
|
||||
|
||||
@@ -107,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
1. Run the Qt installer
|
||||
2. Choose a place to install Qt (C:\Qt is the default),
|
||||
3. Choose the components you want to install
|
||||
- You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked,
|
||||
- You need Qt 5.6 (32 bit) ticked,
|
||||
- You need Tools/Qt Creator ticked,
|
||||
- Other components are selected by default, you can untick them if you don't need them.
|
||||
4. Accept the license agreements,
|
||||
@@ -132,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
|
||||
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
|
||||
- Otherwise you can skip this step.
|
||||
6. You should see "Run CMake" in the window,
|
||||
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)",
|
||||
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)",
|
||||
- Hit the "Run CMake" button,
|
||||
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
|
||||
- Hit "Finish" if CMake ran successfully.
|
||||
|
||||
204
CMakeLists.txt
204
CMakeLists.txt
@@ -13,7 +13,7 @@ endif()
|
||||
project(MultiMC)
|
||||
enable_testing()
|
||||
|
||||
######## Set CMake options ########
|
||||
##################################### Set CMake options #####################################
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
@@ -38,16 +38,55 @@ if(UNIX AND APPLE)
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
|
||||
|
||||
##################################### Set Application options #####################################
|
||||
|
||||
######## 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 6)
|
||||
set(MultiMC_VERSION_HOTFIX 2)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
|
||||
# Build platform.
|
||||
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
|
||||
|
||||
# Channel list URL
|
||||
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
|
||||
|
||||
# Notification URL
|
||||
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||
|
||||
# paste.ee API key
|
||||
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
|
||||
|
||||
# Google analytics ID
|
||||
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
|
||||
|
||||
#### Check the current Git commit and branch
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
|
||||
|
||||
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
|
||||
message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
|
||||
|
||||
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
|
||||
|
||||
#### Custom target to just print the version.
|
||||
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
|
||||
|
||||
################################ 3rd Party Libs ################################
|
||||
|
||||
# Find the required Qt parts
|
||||
find_package(Qt5Core)
|
||||
find_package(Qt5Widgets)
|
||||
find_package(Qt5Concurrent)
|
||||
find_package(Qt5Network)
|
||||
find_package(Qt5Test)
|
||||
find_package(Qt5Xml)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5Concurrent REQUIRED)
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(Qt5Test REQUIRED)
|
||||
find_package(Qt5Xml REQUIRED)
|
||||
|
||||
# The Qt5 cmake files don't provide its install paths, so ask qmake.
|
||||
include(QMakeQuery)
|
||||
@@ -62,34 +101,143 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
####################################### Install layout #######################################
|
||||
|
||||
# How to install the build results
|
||||
set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
|
||||
set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
|
||||
|
||||
if(MultiMC_LAYOUT STREQUAL "auto")
|
||||
if(UNIX AND APPLE)
|
||||
set(MultiMC_LAYOUT_REAL "mac-bundle")
|
||||
elseif(UNIX)
|
||||
set(MultiMC_LAYOUT_REAL "lin-nodeps")
|
||||
elseif(WIN32)
|
||||
set(MultiMC_LAYOUT_REAL "win-bundle")
|
||||
else()
|
||||
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
|
||||
endif()
|
||||
else()
|
||||
set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
|
||||
endif()
|
||||
|
||||
if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
|
||||
set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
|
||||
set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
|
||||
set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
|
||||
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
|
||||
|
||||
# Mac bundle settings
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
|
||||
set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
|
||||
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2018 MultiMC Contributors")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
|
||||
# Add the icon
|
||||
install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
|
||||
set(BINARY_DEST_DIR "bin")
|
||||
set(LIBRARY_DEST_DIR "bin")
|
||||
set(PLUGIN_DEST_DIR "plugins")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "bin/jars")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
|
||||
# Set RPATH
|
||||
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
|
||||
|
||||
# Install basic runner script
|
||||
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
|
||||
set(BINARY_DEST_DIR "bin")
|
||||
set(LIBRARY_DEST_DIR "bin")
|
||||
set(PLUGIN_DEST_DIR "plugins")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "bin/jars")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
|
||||
# Set RPATH
|
||||
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
|
||||
|
||||
# Install basic runner script
|
||||
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
|
||||
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
|
||||
set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
|
||||
set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
|
||||
set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
|
||||
set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
|
||||
|
||||
set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
|
||||
set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
|
||||
|
||||
MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
|
||||
SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
|
||||
|
||||
# install as bundle with no dependencies included
|
||||
set(INSTALL_BUNDLE "nodeps")
|
||||
|
||||
elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
|
||||
set(BINARY_DEST_DIR ".")
|
||||
set(LIBRARY_DEST_DIR ".")
|
||||
set(PLUGIN_DEST_DIR ".")
|
||||
set(BUNDLE_DEST_DIR ".")
|
||||
set(RESOURCES_DEST_DIR ".")
|
||||
set(JARS_DEST_DIR "jars")
|
||||
|
||||
# Apps to bundle
|
||||
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# install as bundle
|
||||
set(INSTALL_BUNDLE "full")
|
||||
else()
|
||||
message(FATAL_ERROR "No sensible install layout set.")
|
||||
endif()
|
||||
|
||||
################################ Included Libs ################################
|
||||
|
||||
include(ExternalProject)
|
||||
set_directory_properties(PROPERTIES EP_BASE External)
|
||||
|
||||
# Add quazip
|
||||
add_definitions(-DQUAZIP_STATIC)
|
||||
set(QUAZIP_VERSION "0.7.1")
|
||||
if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
|
||||
file(DOWNLOAD http://downloads.sourceforge.net/project/quazip/quazip/${QUAZIP_VERSION}/quazip-${QUAZIP_VERSION}.tar.gz ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
|
||||
endif()
|
||||
ExternalProject_Add(QuaZIP
|
||||
SOURCE_DIR <BINARY_DIR>/../Source/quazip-${QUAZIP_VERSION}
|
||||
DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E chdir <SOURCE_DIR>/.. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz
|
||||
PATCH_COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/quazip.patch
|
||||
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
)
|
||||
include_directories("${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/include/quazip")
|
||||
if(UNIX)
|
||||
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip z)
|
||||
else()
|
||||
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip)
|
||||
endif()
|
||||
|
||||
option(NBT_BUILD_SHARED "Build NBT shared library" ON)
|
||||
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
|
||||
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
|
||||
set(NBT_NAME MultiMC_nbt++)
|
||||
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
|
||||
add_subdirectory(libraries/libnbtplusplus)
|
||||
|
||||
add_subdirectory(libraries/ganalytics) # google analytics library
|
||||
@@ -98,15 +246,17 @@ 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
|
||||
add_subdirectory(libraries/classparser) # google analytics library
|
||||
|
||||
############################### Built Artifacts ###############################
|
||||
|
||||
add_subdirectory(api/logic)
|
||||
add_subdirectory(api/gui)
|
||||
|
||||
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
|
||||
add_subdirectory(application)
|
||||
add_subdirectory(wonkoclient)
|
||||
|
||||
117
COPYING.md
117
COPYING.md
@@ -1,6 +1,6 @@
|
||||
#MultiMC
|
||||
# MultiMC
|
||||
|
||||
Copyright 2012-2017 MultiMC Contributors
|
||||
Copyright 2012-2018 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
|
||||
@@ -13,7 +13,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
#MinGW runtime (Windows)
|
||||
# MinGW runtime (Windows)
|
||||
|
||||
Copyright (c) 2012 MinGW.org project
|
||||
|
||||
@@ -35,14 +35,14 @@
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#Qt 5
|
||||
# Qt 5
|
||||
|
||||
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Licensed under LGPL v2.1
|
||||
|
||||
#libnbt++
|
||||
# libnbt++
|
||||
|
||||
libnbt++ - A library for the Minecraft Named Binary Tag format.
|
||||
Copyright (C) 2013, 2015 ljfa-ag
|
||||
@@ -60,27 +60,7 @@
|
||||
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)
|
||||
# rainbow (KGuiAddons)
|
||||
|
||||
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
|
||||
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
|
||||
@@ -103,7 +83,7 @@
|
||||
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
#Hoedown
|
||||
# Hoedown
|
||||
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
@@ -121,7 +101,7 @@
|
||||
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
|
||||
# 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
|
||||
@@ -137,7 +117,18 @@
|
||||
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
|
||||
# Material Design Icons
|
||||
|
||||
Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
|
||||
with Reserved Font Name Material Design Icons.
|
||||
Copyright (c) 2014, Google (http://www.google.com/design/)
|
||||
uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
# Pack200
|
||||
|
||||
The GNU General Public License (GPL)
|
||||
|
||||
@@ -166,7 +157,7 @@
|
||||
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
|
||||
# Quazip
|
||||
|
||||
Copyright (C) 2005-2011 Sergey A. Tachenov
|
||||
|
||||
@@ -189,7 +180,7 @@
|
||||
Original ZIP package is copyrighted by Gilles Vollant, see
|
||||
quazip/(un)zip.h files for details, basically it's zlib license.
|
||||
|
||||
#xz-minidec
|
||||
# xz-minidec
|
||||
|
||||
XZ decompressor
|
||||
|
||||
@@ -199,7 +190,65 @@
|
||||
This file has been put into the public domain.
|
||||
You can do whatever you want with this file.
|
||||
|
||||
#ColumnResizer
|
||||
# ColumnResizer
|
||||
|
||||
Copyright 2011 Aurélien Gâteau <agateau@kde.org>
|
||||
License: LGPL v2.1 or later (see COPYING)
|
||||
Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted (subject to the limitations in the
|
||||
disclaimer below) provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* The name of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
|
||||
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
|
||||
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# lionshead
|
||||
|
||||
Code has been taken from https://github.com/natefoo/lionshead and loosely
|
||||
translated to C++ laced with Qt.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Nate Coraor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
As a special exception to the GNU Lesser General Public License version 2.1, the object code form of a "work
|
||||
that uses the Library" may incorporate material from a header file that is part of the Library. You may
|
||||
distribute such object code under terms of your choice, provided that the incorporated material (i) does not
|
||||
exceed more than 5% of the total size of the Library; and (ii) is limited to numerical parameters, data
|
||||
structure layouts, accessors, macros, inline functions and templates.
|
||||
502
LICENSE.LPGLv21
502
LICENSE.LPGLv21
@@ -1,502 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
0the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library 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.
|
||||
|
||||
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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
@@ -1,4 +1,6 @@
|
||||

|
||||
<p align="center">
|
||||
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
|
||||
</p>
|
||||
|
||||
MultiMC 5
|
||||
=========
|
||||
@@ -11,7 +13,7 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
|
||||
|
||||
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
|
||||
|
||||
If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [](http://waffle.io/MultiMC/MultiMC5)
|
||||
If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
|
||||
|
||||
### Building
|
||||
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
|
||||
@@ -38,7 +40,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
|
||||
|
||||
|
||||
## License
|
||||
Copyright © 2013-2017 MultiMC Contributors
|
||||
Copyright © 2013-2018 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).
|
||||
|
||||
|
||||
@@ -21,8 +21,15 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
|
||||
generate_export_header(MultiMC_gui)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
|
||||
target_link_libraries(MultiMC_gui MultiMC_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}")
|
||||
|
||||
# Install it
|
||||
install(
|
||||
TARGETS MultiMC_gui
|
||||
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -261,7 +261,7 @@ void IconList::installIcons(const QStringList &iconFiles)
|
||||
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
|
||||
|
||||
QString suffix = fileinfo.suffix();
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
@@ -269,6 +269,17 @@ void IconList::installIcons(const QStringList &iconFiles)
|
||||
}
|
||||
}
|
||||
|
||||
void IconList::installIcon(const QString &file, const QString &name)
|
||||
{
|
||||
QFileInfo fileinfo(file);
|
||||
if(!fileinfo.isReadable() || !fileinfo.isFile())
|
||||
return;
|
||||
|
||||
QString target = FS::PathCombine(m_dir.dirName(), name);
|
||||
|
||||
QFile::copy(file, target);
|
||||
}
|
||||
|
||||
bool IconList::iconFileExists(const QString &key) const
|
||||
{
|
||||
auto iconEntry = icon(key);
|
||||
@@ -331,7 +342,7 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
|
||||
{
|
||||
// replace the icon even? is the input valid?
|
||||
QIcon icon(path);
|
||||
if (!icon.availableSizes().size())
|
||||
if (icon.isNull())
|
||||
return false;
|
||||
auto iter = name_index.find(key);
|
||||
if (iter != name_index.end())
|
||||
@@ -392,20 +403,6 @@ QIcon IconList::getIcon(const QString &key) const
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
QIcon IconList::getBigIcon(const QString &key) const
|
||||
{
|
||||
int icon_index = getIconIndex(key);
|
||||
|
||||
// Fallback for icons that don't exist.
|
||||
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
|
||||
|
||||
if (icon_index == -1)
|
||||
return QIcon();
|
||||
|
||||
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
|
||||
return QIcon(bigone);
|
||||
}
|
||||
|
||||
int IconList::getIconIndex(const QString &key) const
|
||||
{
|
||||
auto iter = name_index.find(key == "default" ? "infinity" : key);
|
||||
@@ -415,4 +412,9 @@ int IconList::getIconIndex(const QString &key) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString IconList::getDirectory() const
|
||||
{
|
||||
return m_dir.absolutePath();
|
||||
}
|
||||
|
||||
//#include "IconList.moc"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,8 +38,8 @@ public:
|
||||
virtual ~IconList() {};
|
||||
|
||||
QIcon getIcon(const QString &key) const;
|
||||
QIcon getBigIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
QString getDirectory() const;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
void installIcons(const QStringList &iconFiles) override;
|
||||
void installIcon(const QString &file, const QString &name) override;
|
||||
|
||||
const MMCIcon * icon(const QString &key) const;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 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,19 +16,19 @@
|
||||
#include <QFile>
|
||||
|
||||
#include "BaseInstaller.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
|
||||
BaseInstaller::BaseInstaller()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool BaseInstaller::isApplied(OneSixInstance *on)
|
||||
bool BaseInstaller::isApplied(MinecraftInstance *on)
|
||||
{
|
||||
return QFile::exists(filename(on->instanceRoot()));
|
||||
}
|
||||
|
||||
bool BaseInstaller::add(OneSixInstance *to)
|
||||
bool BaseInstaller::add(MinecraftInstance *to)
|
||||
{
|
||||
if (!patchesDir(to->instanceRoot()).exists())
|
||||
{
|
||||
@@ -46,7 +46,7 @@ bool BaseInstaller::add(OneSixInstance *to)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseInstaller::remove(OneSixInstance *from)
|
||||
bool BaseInstaller::remove(MinecraftInstance *from)
|
||||
{
|
||||
return QFile::remove(filename(from->instanceRoot()));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class OneSixInstance;
|
||||
class MinecraftInstance;
|
||||
class QDir;
|
||||
class QString;
|
||||
class QObject;
|
||||
@@ -32,12 +32,12 @@ class MULTIMC_LOGIC_EXPORT BaseInstaller
|
||||
public:
|
||||
BaseInstaller();
|
||||
virtual ~BaseInstaller(){};
|
||||
bool isApplied(OneSixInstance *on);
|
||||
bool isApplied(MinecraftInstance *on);
|
||||
|
||||
virtual bool add(OneSixInstance *to);
|
||||
virtual bool remove(OneSixInstance *from);
|
||||
virtual bool add(MinecraftInstance *to);
|
||||
virtual bool remove(MinecraftInstance *from);
|
||||
|
||||
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
|
||||
|
||||
protected:
|
||||
virtual QString id() const = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "settings/Setting.h"
|
||||
#include "settings/OverrideSetting.h"
|
||||
|
||||
#include "minecraft/MinecraftVersionList.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Commandline.h"
|
||||
|
||||
@@ -198,7 +197,7 @@ bool BaseInstance::canLaunch() const
|
||||
return (!hasVersionBroken() && !isRunning());
|
||||
}
|
||||
|
||||
bool BaseInstance::reload()
|
||||
bool BaseInstance::reloadSettings()
|
||||
{
|
||||
return m_settings->reload();
|
||||
}
|
||||
@@ -280,6 +279,7 @@ QString BaseInstance::windowTitle() const
|
||||
return "MultiMC: " + name();
|
||||
}
|
||||
|
||||
// FIXME: why is this here? move it to MinecraftInstance!!!
|
||||
QStringList BaseInstance::extraArguments() const
|
||||
{
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "MessageLevel.h"
|
||||
#include "pathmatcher/IPathMatcher.h"
|
||||
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QDir;
|
||||
@@ -67,9 +69,8 @@ public:
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
|
||||
virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
|
||||
|
||||
virtual void init() = 0;
|
||||
virtual void saveNow() = 0;
|
||||
|
||||
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
|
||||
/// responsible of cleaning up the husk
|
||||
@@ -129,25 +130,8 @@ public:
|
||||
|
||||
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;
|
||||
virtual QSet <QString> traits() const = 0;
|
||||
|
||||
/**
|
||||
* Gets the time that the instance was last launched.
|
||||
@@ -159,12 +143,6 @@ public:
|
||||
|
||||
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.
|
||||
@@ -173,7 +151,7 @@ public:
|
||||
virtual SettingsObjectPtr settings() const;
|
||||
|
||||
/// returns a valid update task
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
|
||||
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
|
||||
|
||||
/// returns a valid launcher (task container)
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
@@ -181,12 +159,6 @@ public:
|
||||
/// 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
|
||||
*/
|
||||
@@ -251,10 +223,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool canLaunch() const;
|
||||
virtual bool canLaunch() const;
|
||||
virtual bool canEdit() const = 0;
|
||||
virtual bool canExport() const = 0;
|
||||
|
||||
virtual bool reload();
|
||||
bool reloadSettings();
|
||||
|
||||
/**
|
||||
* 'print' a verbose desription of the instance into a QStringList
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
|
||||
virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -30,7 +30,7 @@ BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
|
||||
return BaseVersionPtr();
|
||||
}
|
||||
|
||||
BaseVersionPtr BaseVersionList::getLatestStable() const
|
||||
BaseVersionPtr BaseVersionList::getRecommended() const
|
||||
{
|
||||
if (count() <= 0)
|
||||
return BaseVersionPtr();
|
||||
@@ -38,11 +38,6 @@ 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())
|
||||
@@ -93,7 +88,7 @@ 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(ParentVersionRole, "parentGameVersion");
|
||||
roles.insert(RecommendedRole, "recommended");
|
||||
roles.insert(LatestRole, "latest");
|
||||
roles.insert(TypeRole, "type");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "BaseVersion.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
/*!
|
||||
* \brief Class that each instance type's version list derives from.
|
||||
@@ -44,7 +45,7 @@ public:
|
||||
VersionPointerRole = Qt::UserRole,
|
||||
VersionRole,
|
||||
VersionIdRole,
|
||||
ParentGameVersionRole,
|
||||
ParentVersionRole,
|
||||
RecommendedRole,
|
||||
LatestRole,
|
||||
TypeRole,
|
||||
@@ -63,7 +64,7 @@ public:
|
||||
* The task returned by this function should reset the model when it's done.
|
||||
* \return A pointer to a task that reloads the version list.
|
||||
*/
|
||||
virtual Task *getLoadTask() = 0;
|
||||
virtual shared_qobject_ptr<Task> getLoadTask() = 0;
|
||||
|
||||
//! Checks whether or not the list is loaded. If this returns false, the list should be
|
||||
//loaded.
|
||||
@@ -76,27 +77,22 @@ public:
|
||||
virtual int count() const = 0;
|
||||
|
||||
//////// List Model Functions ////////
|
||||
virtual QVariant data(const QModelIndex &index, int role) const;
|
||||
virtual int rowCount(const QModelIndex &parent) const;
|
||||
virtual int columnCount(const QModelIndex &parent) const;
|
||||
virtual QHash<int, QByteArray> roleNames() const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
//! which roles are provided by this version list?
|
||||
virtual RoleList providesRoles() const;
|
||||
|
||||
/*!
|
||||
* \brief Finds a version by its descriptor.
|
||||
* \param The descriptor of the version to find.
|
||||
* \param descriptor The descriptor of the version to find.
|
||||
* \return A const pointer to the version with the given descriptor. NULL if
|
||||
* one doesn't exist.
|
||||
*/
|
||||
virtual BaseVersionPtr findVersion(const QString &descriptor);
|
||||
|
||||
/*!
|
||||
* \brief Gets the latest stable version from this list
|
||||
*/
|
||||
virtual BaseVersionPtr getLatestStable() const;
|
||||
|
||||
/*!
|
||||
* \brief Gets the recommended version from this list
|
||||
* If the list doesn't support recommended versions, this works exactly as getLatestStable
|
||||
|
||||
@@ -8,14 +8,10 @@ set(CORE_SOURCES
|
||||
BaseInstaller.cpp
|
||||
BaseVersionList.h
|
||||
BaseVersionList.cpp
|
||||
InstanceCreationTask.h
|
||||
InstanceCreationTask.cpp
|
||||
InstanceCopyTask.h
|
||||
InstanceCopyTask.cpp
|
||||
InstanceImportTask.h
|
||||
InstanceImportTask.cpp
|
||||
InstanceList.h
|
||||
InstanceList.cpp
|
||||
InstanceTask.h
|
||||
InstanceTask.cpp
|
||||
LoggedProcess.h
|
||||
LoggedProcess.cpp
|
||||
MessageLevel.cpp
|
||||
@@ -32,6 +28,14 @@ set(CORE_SOURCES
|
||||
MMCStrings.h
|
||||
MMCStrings.cpp
|
||||
|
||||
# Basic instance manipulation tasks (derived from InstanceTask)
|
||||
InstanceCreationTask.h
|
||||
InstanceCreationTask.cpp
|
||||
InstanceCopyTask.h
|
||||
InstanceCopyTask.cpp
|
||||
InstanceImportTask.h
|
||||
InstanceImportTask.cpp
|
||||
|
||||
# Use tracking separate from memory management
|
||||
Usable.h
|
||||
|
||||
@@ -42,6 +46,10 @@ set(CORE_SOURCES
|
||||
Env.h
|
||||
Env.cpp
|
||||
|
||||
# String filters
|
||||
Filter.h
|
||||
Filter.cpp
|
||||
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
@@ -206,22 +214,14 @@ set(MINECRAFT_SOURCES
|
||||
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/update/AssetUpdateTask.h
|
||||
minecraft/update/AssetUpdateTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.h
|
||||
minecraft/update/FoldersTask.cpp
|
||||
minecraft/update/FoldersTask.h
|
||||
minecraft/update/LibrariesTask.cpp
|
||||
minecraft/update/LibrariesTask.h
|
||||
minecraft/launch/ClaimAccount.cpp
|
||||
minecraft/launch/ClaimAccount.h
|
||||
minecraft/launch/CreateServerResourcePacksFolder.cpp
|
||||
@@ -238,40 +238,42 @@ set(MINECRAFT_SOURCES
|
||||
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/legacy/LegacyUpgradeTask.h
|
||||
minecraft/legacy/LegacyUpgradeTask.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/LaunchProfile.cpp
|
||||
minecraft/LaunchProfile.h
|
||||
minecraft/Component.cpp
|
||||
minecraft/Component.h
|
||||
minecraft/ComponentList.cpp
|
||||
minecraft/ComponentList.h
|
||||
minecraft/ComponentUpdateTask.cpp
|
||||
minecraft/ComponentUpdateTask.h
|
||||
minecraft/MinecraftLoadAndCheck.h
|
||||
minecraft/MinecraftLoadAndCheck.cpp
|
||||
minecraft/MinecraftUpdate.h
|
||||
minecraft/MinecraftUpdate.cpp
|
||||
minecraft/MojangVersionFormat.cpp
|
||||
minecraft/MojangVersionFormat.h
|
||||
minecraft/Rule.cpp
|
||||
minecraft/Rule.h
|
||||
minecraft/OneSixVersionFormat.cpp
|
||||
minecraft/OneSixVersionFormat.h
|
||||
minecraft/OpSys.cpp
|
||||
minecraft/OpSys.h
|
||||
minecraft/ParseUtils.cpp
|
||||
minecraft/ParseUtils.h
|
||||
minecraft/ProfileUtils.cpp
|
||||
minecraft/ProfileUtils.h
|
||||
minecraft/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
|
||||
@@ -283,39 +285,15 @@ set(MINECRAFT_SOURCES
|
||||
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
|
||||
# Skin upload utilities
|
||||
minecraft/SkinUpload.cpp
|
||||
minecraft/SkinUpload.h
|
||||
)
|
||||
@@ -361,8 +339,6 @@ set(TASKS_SOURCES
|
||||
# Tasks
|
||||
tasks/Task.h
|
||||
tasks/Task.cpp
|
||||
tasks/ThreadTask.h
|
||||
tasks/ThreadTask.cpp
|
||||
tasks/SequentialTask.h
|
||||
tasks/SequentialTask.cpp
|
||||
)
|
||||
@@ -430,32 +406,39 @@ set(TOOLS_SOURCES
|
||||
tools/MCEditTool.h
|
||||
)
|
||||
|
||||
set(WONKO_SOURCES
|
||||
# Wonko
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp
|
||||
wonko/tasks/BaseWonkoEntityLocalLoadTask.h
|
||||
wonko/format/WonkoFormatV1.cpp
|
||||
wonko/format/WonkoFormatV1.h
|
||||
wonko/format/WonkoFormat.cpp
|
||||
wonko/format/WonkoFormat.h
|
||||
wonko/BaseWonkoEntity.cpp
|
||||
wonko/BaseWonkoEntity.h
|
||||
wonko/WonkoVersionList.cpp
|
||||
wonko/WonkoVersionList.h
|
||||
wonko/WonkoVersion.cpp
|
||||
wonko/WonkoVersion.h
|
||||
wonko/WonkoIndex.cpp
|
||||
wonko/WonkoIndex.h
|
||||
wonko/WonkoUtil.cpp
|
||||
wonko/WonkoUtil.h
|
||||
wonko/WonkoReference.cpp
|
||||
wonko/WonkoReference.h
|
||||
set(META_SOURCES
|
||||
# Metadata sources
|
||||
meta/JsonFormat.cpp
|
||||
meta/JsonFormat.h
|
||||
meta/BaseEntity.cpp
|
||||
meta/BaseEntity.h
|
||||
meta/VersionList.cpp
|
||||
meta/VersionList.h
|
||||
meta/Version.cpp
|
||||
meta/Version.h
|
||||
meta/Index.cpp
|
||||
meta/Index.h
|
||||
)
|
||||
|
||||
add_unit_test(WonkoIndex
|
||||
SOURCES wonko/WonkoIndex_test.cpp
|
||||
set(FTB_SOURCES
|
||||
modplatform/ftb/FtbPackFetchTask.h
|
||||
modplatform/ftb/FtbPackFetchTask.cpp
|
||||
modplatform/ftb/FtbPackInstallTask.h
|
||||
modplatform/ftb/FtbPackInstallTask.cpp
|
||||
|
||||
modplatform/ftb/PackHelpers.h
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
# Flame
|
||||
modplatform/flame/PackManifest.h
|
||||
modplatform/flame/PackManifest.cpp
|
||||
modplatform/flame/FileResolvingTask.h
|
||||
modplatform/flame/FileResolvingTask.cpp
|
||||
)
|
||||
|
||||
add_unit_test(Index
|
||||
SOURCES meta/Index_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
|
||||
@@ -480,8 +463,10 @@ set(LOGIC_SOURCES
|
||||
${JAVA_SOURCES}
|
||||
${TRANSLATIONS_SOURCES}
|
||||
${TOOLS_SOURCES}
|
||||
${WONKO_SOURCES}
|
||||
${META_SOURCES}
|
||||
${ICONS_SOURCES}
|
||||
${FTB_SOURCES}
|
||||
${FLAME_SOURCES}
|
||||
)
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
@@ -490,9 +475,15 @@ set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISI
|
||||
generate_export_header(MultiMC_logic)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_logic xz-embedded unpack200 systeminfo ${QUAZIP_LIBRARIES} ${NBT_NAME} ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
|
||||
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
|
||||
add_dependencies(MultiMC_logic QuaZIP)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
|
||||
|
||||
# Install it
|
||||
install(
|
||||
TARGETS MultiMC_logic
|
||||
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
|
||||
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -44,7 +44,7 @@ QStringList splitArgs(QString args)
|
||||
}
|
||||
else if (!inquotes.isNull())
|
||||
{
|
||||
if (cchar == 0x5C)
|
||||
if (cchar == '\\')
|
||||
escape = true;
|
||||
else if (cchar == inquotes)
|
||||
inquotes = 0;
|
||||
@@ -54,7 +54,7 @@ QStringList splitArgs(QString args)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cchar == 0x20)
|
||||
if (cchar == ' ')
|
||||
{
|
||||
if (!current.isEmpty())
|
||||
{
|
||||
@@ -62,7 +62,7 @@ QStringList splitArgs(QString args)
|
||||
current.clear();
|
||||
}
|
||||
}
|
||||
else if (cchar == 0x22 || cchar == 0x27)
|
||||
else if (cchar == '"' || cchar == '\'')
|
||||
inquotes = cchar;
|
||||
else
|
||||
current += cchar;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QDebug>
|
||||
#include "tasks/Task.h"
|
||||
#include "wonko/WonkoIndex.h"
|
||||
#include "meta/Index.h"
|
||||
#include "FileSystem.h"
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
class Env::Private
|
||||
struct Env::Private
|
||||
{
|
||||
public:
|
||||
QNetworkAccessManager m_qnam;
|
||||
shared_qobject_ptr<HttpMetaCache> m_metacache;
|
||||
std::shared_ptr<IIconList> m_iconlist;
|
||||
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
|
||||
shared_qobject_ptr<WonkoIndex> m_wonkoIndex;
|
||||
QString m_wonkoRootUrl;
|
||||
shared_qobject_ptr<Meta::Index> m_metadataIndex;
|
||||
QString m_jarsPath;
|
||||
};
|
||||
|
||||
static Env * instance;
|
||||
@@ -73,39 +73,13 @@ void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
|
||||
d->m_iconlist = iconlist;
|
||||
}
|
||||
|
||||
BaseVersionPtr Env::getVersion(QString component, QString version)
|
||||
shared_qobject_ptr<Meta::Index> Env::metadataIndex()
|
||||
{
|
||||
auto list = getVersionList(component);
|
||||
if(!list)
|
||||
if (!d->m_metadataIndex)
|
||||
{
|
||||
return nullptr;
|
||||
d->m_metadataIndex.reset(new Meta::Index());
|
||||
}
|
||||
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;
|
||||
return d->m_metadataIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,11 +95,12 @@ void Env::initHttpMetaCache()
|
||||
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
|
||||
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
|
||||
m_metacache->addBase("general", QDir("cache").absolutePath());
|
||||
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
|
||||
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
|
||||
m_metacache->addBase("root", QDir::currentPath());
|
||||
m_metacache->addBase("translations", QDir("translations").absolutePath());
|
||||
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
|
||||
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
|
||||
m_metacache->addBase("meta", QDir("meta").absolutePath());
|
||||
m_metacache->Load();
|
||||
}
|
||||
|
||||
@@ -191,14 +166,16 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
|
||||
qDebug() << proxyDesc;
|
||||
}
|
||||
|
||||
QString Env::wonkoRootUrl() const
|
||||
QString Env::getJarsPath()
|
||||
{
|
||||
return d->m_wonkoRootUrl;
|
||||
if(d->m_jarsPath.isEmpty())
|
||||
{
|
||||
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
|
||||
}
|
||||
return d->m_jarsPath;
|
||||
}
|
||||
|
||||
void Env::setWonkoRootUrl(const QString& url)
|
||||
void Env::setJarsPath(const QString& path)
|
||||
{
|
||||
d->m_wonkoRootUrl = url;
|
||||
d->m_jarsPath = path;
|
||||
}
|
||||
|
||||
#include "Env.moc"
|
||||
@@ -13,18 +13,23 @@ class QNetworkAccessManager;
|
||||
class HttpMetaCache;
|
||||
class BaseVersionList;
|
||||
class BaseVersion;
|
||||
class WonkoIndex;
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
class Index;
|
||||
}
|
||||
|
||||
#if defined(ENV)
|
||||
#undef ENV
|
||||
#endif
|
||||
#define ENV (Env::getInstance())
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Env
|
||||
{
|
||||
friend class MultiMC;
|
||||
private:
|
||||
class Private;
|
||||
struct Private;
|
||||
Env();
|
||||
~Env();
|
||||
static void dispose();
|
||||
@@ -43,21 +48,12 @@ public:
|
||||
/// 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);
|
||||
shared_qobject_ptr<Meta::Index> metadataIndex();
|
||||
|
||||
QString getJarsPath();
|
||||
void setJarsPath(const QString & path);
|
||||
protected:
|
||||
Private * d;
|
||||
};
|
||||
|
||||
@@ -3,11 +3,27 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QSaveFile>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <sys/utime.h>
|
||||
#include <winnls.h>
|
||||
#include <shobjidl.h>
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
#include <shlobj.h>
|
||||
#else
|
||||
#include <utime.h>
|
||||
#endif
|
||||
|
||||
namespace FS {
|
||||
|
||||
@@ -62,21 +78,13 @@ QByteArray read(const QString &filename)
|
||||
|
||||
bool updateTimestamp(const QString& filename)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.exists())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!file.open(QIODevice::ReadWrite))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const quint64 size = file.size();
|
||||
file.seek(size);
|
||||
file.write( QByteArray(1, '0') );
|
||||
file.resize(size);
|
||||
return true;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
std::wstring filename_utf_16 = filename.toStdWString();
|
||||
return (_wutime64(filename_utf_16.c_str(), nullptr) == 0);
|
||||
#else
|
||||
QByteArray filenameBA = QFile::encodeName(filename);
|
||||
return (utime(filenameBA.data(), nullptr) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ensureFilePathExists(QString filenamepath)
|
||||
@@ -163,11 +171,6 @@ bool copy::operator()(const QString &offset)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#if defined Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#endif
|
||||
bool deletePath(QString path)
|
||||
{
|
||||
bool OK = true;
|
||||
@@ -225,7 +228,7 @@ bool deletePath(QString path)
|
||||
}
|
||||
|
||||
|
||||
QString PathCombine(QString path1, QString path2)
|
||||
QString PathCombine(const QString & path1, const QString & path2)
|
||||
{
|
||||
if(!path1.size())
|
||||
return path2;
|
||||
@@ -234,11 +237,16 @@ QString PathCombine(QString path1, QString path2)
|
||||
return QDir::cleanPath(path1 + QDir::separator() + path2);
|
||||
}
|
||||
|
||||
QString PathCombine(QString path1, QString path2, QString path3)
|
||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
|
||||
{
|
||||
return PathCombine(PathCombine(path1, path2), path3);
|
||||
}
|
||||
|
||||
QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
|
||||
{
|
||||
return PathCombine(PathCombine(path1, path2, path3), path4);
|
||||
}
|
||||
|
||||
QString AbsolutePath(QString path)
|
||||
{
|
||||
return QFileInfo(path).absolutePath();
|
||||
@@ -332,21 +340,9 @@ bool checkProblemticPathJava(QDir folder)
|
||||
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)
|
||||
|
||||
@@ -83,8 +83,9 @@ private:
|
||||
*/
|
||||
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
|
||||
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
|
||||
|
||||
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
|
||||
|
||||
|
||||
31
api/logic/Filter.cpp
Normal file
31
api/logic/Filter.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "Filter.h"
|
||||
|
||||
Filter::~Filter(){}
|
||||
|
||||
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
|
||||
ContainsFilter::~ContainsFilter(){}
|
||||
bool ContainsFilter::accepts(const QString& value)
|
||||
{
|
||||
return value.contains(pattern);
|
||||
}
|
||||
|
||||
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
|
||||
ExactFilter::~ExactFilter(){}
|
||||
bool ExactFilter::accepts(const QString& value)
|
||||
{
|
||||
return value.contains(pattern);
|
||||
}
|
||||
|
||||
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
|
||||
:invert(invert)
|
||||
{
|
||||
pattern.setPattern(regexp);
|
||||
pattern.optimize();
|
||||
}
|
||||
RegexpFilter::~RegexpFilter(){}
|
||||
bool RegexpFilter::accepts(const QString& value)
|
||||
{
|
||||
auto match = pattern.match(value);
|
||||
bool matched = match.hasMatch();
|
||||
return invert ? (!matched) : (matched);
|
||||
}
|
||||
44
api/logic/Filter.h
Normal file
44
api/logic/Filter.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Filter
|
||||
{
|
||||
public:
|
||||
virtual ~Filter();
|
||||
virtual bool accepts(const QString & value) = 0;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ContainsFilter(const QString &pattern);
|
||||
virtual ~ContainsFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ExactFilter(const QString &pattern);
|
||||
virtual ~ExactFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter
|
||||
{
|
||||
public:
|
||||
RegexpFilter(const QString ®exp, bool invert);
|
||||
virtual ~RegexpFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QRegularExpression pattern;
|
||||
bool invert = false;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "FolderInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "NullInstance.h"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
@@ -33,11 +34,13 @@ struct WatchLock
|
||||
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
|
||||
: BaseInstanceProvider(settings)
|
||||
{
|
||||
m_instDir = instDir;
|
||||
if (!QDir::current().exists(m_instDir))
|
||||
// Create aand normalize path
|
||||
if (!QDir::current().exists(instDir))
|
||||
{
|
||||
QDir::current().mkpath(m_instDir);
|
||||
QDir::current().mkpath(instDir);
|
||||
}
|
||||
// NOTE: canonicalPath requires the path to exist. Do not move this above the creation block!
|
||||
m_instDir = QDir(instDir).canonicalPath();
|
||||
m_watcher = new QFileSystemWatcher(this);
|
||||
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
|
||||
m_watcher->addPath(m_instDir);
|
||||
@@ -46,7 +49,7 @@ FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const
|
||||
QList< InstanceId > FolderInstanceProvider::discoverInstances()
|
||||
{
|
||||
QList<InstanceId> out;
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
@@ -88,7 +91,7 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
|
||||
|
||||
if (inst_type == "OneSix" || inst_type == "Nostalgia")
|
||||
{
|
||||
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else if (inst_type == "Legacy")
|
||||
{
|
||||
@@ -110,24 +113,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
|
||||
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);
|
||||
@@ -298,7 +283,7 @@ void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
|
||||
|
||||
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
QString newInstDir = value.toString();
|
||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||
if(newInstDir != m_instDir)
|
||||
{
|
||||
if(m_groupsLoaded)
|
||||
@@ -311,6 +296,131 @@ void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVaria
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void clamp(T& current, T min, T max)
|
||||
{
|
||||
if (current < min)
|
||||
{
|
||||
current = min;
|
||||
}
|
||||
else if(current > max)
|
||||
{
|
||||
current = max;
|
||||
}
|
||||
}
|
||||
|
||||
// List of numbers from min to max. Next is exponent times bigger than previous.
|
||||
class ExponentialSeries
|
||||
{
|
||||
public:
|
||||
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
|
||||
{
|
||||
m_current = m_min = min;
|
||||
m_max = max;
|
||||
m_exponent = exponent;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_current = m_min;
|
||||
}
|
||||
unsigned operator()()
|
||||
{
|
||||
unsigned retval = m_current;
|
||||
m_current *= m_exponent;
|
||||
clamp(m_current, m_min, m_max);
|
||||
return retval;
|
||||
}
|
||||
unsigned m_current;
|
||||
unsigned m_min;
|
||||
unsigned m_max;
|
||||
unsigned m_exponent;
|
||||
};
|
||||
|
||||
/*
|
||||
* WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
|
||||
* Basically, it starts messing things up while MultiMC is extracting/creating instances
|
||||
* and causes that horrible failure that is NTFS to lock files in place because they are open.
|
||||
*/
|
||||
class FolderInstanceStaging : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
const unsigned minBackoff = 1;
|
||||
const unsigned maxBackoff = 16;
|
||||
public:
|
||||
FolderInstanceStaging (
|
||||
FolderInstanceProvider * parent,
|
||||
Task * child,
|
||||
const QString & stagingPath,
|
||||
const QString& instanceName,
|
||||
const QString& groupName )
|
||||
: backoff(minBackoff, maxBackoff)
|
||||
{
|
||||
m_parent = parent;
|
||||
m_child.reset(child);
|
||||
connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded);
|
||||
connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed);
|
||||
connect(child, &Task::status, this, &FolderInstanceStaging::setStatus);
|
||||
connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress);
|
||||
m_instanceName = instanceName;
|
||||
m_groupName = groupName;
|
||||
m_stagingPath = stagingPath;
|
||||
m_backoffTimer.setSingleShot(true);
|
||||
connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void executeTask() override
|
||||
{
|
||||
m_child->start();
|
||||
}
|
||||
QStringList warnings() const override
|
||||
{
|
||||
return m_child->warnings();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void childSucceded()
|
||||
{
|
||||
unsigned sleepTime = backoff();
|
||||
if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
|
||||
{
|
||||
emitSucceeded();
|
||||
return;
|
||||
}
|
||||
// we actually failed, retry?
|
||||
if(sleepTime == maxBackoff)
|
||||
{
|
||||
emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
|
||||
return;
|
||||
}
|
||||
qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
|
||||
m_backoffTimer.start(sleepTime * 500);
|
||||
}
|
||||
void childFailed(const QString & reason)
|
||||
{
|
||||
m_parent->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
private:
|
||||
ExponentialSeries backoff;
|
||||
QString m_stagingPath;
|
||||
FolderInstanceProvider * m_parent;
|
||||
unique_qobject_ptr<Task> m_child;
|
||||
QString m_instanceName;
|
||||
QString m_groupName;
|
||||
QTimer m_backoffTimer;
|
||||
};
|
||||
|
||||
#include "InstanceTask.h"
|
||||
Task * FolderInstanceProvider::wrapInstanceTask(InstanceTask * task)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
task->setStagingPath(stagingPath);
|
||||
task->setParentSettings(m_globalSettings);
|
||||
return new FolderInstanceStaging(this, task, stagingPath, task->name(), task->group());
|
||||
}
|
||||
|
||||
QString FolderInstanceProvider::getStagedInstancePath()
|
||||
{
|
||||
QString key = QUuid::createUuid().toString();
|
||||
@@ -324,21 +434,16 @@ QString FolderInstanceProvider::getStagedInstancePath()
|
||||
return path;
|
||||
}
|
||||
|
||||
bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
|
||||
const QString& groupName)
|
||||
bool FolderInstanceProvider::commitStagedInstance(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)))
|
||||
QString destination = FS::PathCombine(m_instDir, instID);
|
||||
if(!dir.rename(path, destination))
|
||||
{
|
||||
destroyStagingPath(keyPath);
|
||||
qWarning() << "Failed to move" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
groupMap[instID] = groupName;
|
||||
@@ -354,3 +459,4 @@ bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
|
||||
return FS::deletePath(keyPath);
|
||||
}
|
||||
|
||||
#include "FolderInstanceProvider.moc"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QMap>
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class InstanceTask;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
|
||||
{
|
||||
@@ -19,6 +20,7 @@ public:
|
||||
InstancePtr loadInstance(const InstanceId& id) override;
|
||||
|
||||
|
||||
/*
|
||||
// create instance in this provider
|
||||
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
@@ -28,6 +30,16 @@ public:
|
||||
// import zipped instance into this provider
|
||||
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
//create FtbInstance
|
||||
Task * ftbCreationTask(FtbPackDownloader *downloader, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
// migrate an instance to the current format
|
||||
Task * legacyUpgradeTask(const InstancePtr& oldInstance);
|
||||
*/
|
||||
|
||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||
Task * wrapInstanceTask(InstanceTask * task);
|
||||
|
||||
/**
|
||||
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
|
||||
* Used by instance manipulation tasks.
|
||||
@@ -37,7 +49,7 @@ public:
|
||||
* 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;
|
||||
bool commitStagedInstance(const QString & keyPath, 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.
|
||||
|
||||
@@ -6,14 +6,9 @@
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_origInstance = origInstance;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
|
||||
if(!copySaves)
|
||||
{
|
||||
@@ -27,7 +22,7 @@ InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvi
|
||||
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());
|
||||
|
||||
@@ -42,7 +37,6 @@ void InstanceCopyTask::copyFinished()
|
||||
auto successful = m_copyFuture.result();
|
||||
if(!successful)
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Instance folder copy failed."));
|
||||
return;
|
||||
}
|
||||
@@ -50,19 +44,14 @@ void InstanceCopyTask::copyFinished()
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -9,16 +9,15 @@
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "BaseInstance.h"
|
||||
|
||||
#include "InstanceTask.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup, bool copySaves);
|
||||
explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
@@ -27,16 +26,8 @@ protected:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -4,43 +4,30 @@
|
||||
#include "FileSystem.h"
|
||||
|
||||
//FIXME: remove this
|
||||
#include "minecraft/MinecraftVersion.h"
|
||||
#include "minecraft/onesix/OneSixInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
|
||||
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
|
||||
const QString& instName, const QString& instIcon, const QString& instGroup)
|
||||
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
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 ;
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
|
||||
instanceSettings->suspendSave();
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto components = inst.getComponentList();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
||||
inst.setName(m_instName);
|
||||
inst.setIconKey(m_instIcon);
|
||||
inst.init();
|
||||
instanceSettings->resumeSave();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -6,26 +6,18 @@
|
||||
#include <QUrl>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "InstanceTask.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
explicit InstanceCreationTask(BaseVersionPtr version);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
BaseInstanceProvider * m_target;
|
||||
BaseVersionPtr m_version;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "InstanceImportTask.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
@@ -10,15 +9,16 @@
|
||||
#include "icons/IIconList.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
|
||||
const QString &instName, const QString &instIcon, const QString &instGroup)
|
||||
// FIXME: this does not belong here, it's Minecraft/Flame specific
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "modplatform/flame/PackManifest.h"
|
||||
#include "Json.h"
|
||||
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_target = target;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
}
|
||||
|
||||
void InstanceImportTask::executeTask()
|
||||
@@ -28,7 +28,7 @@ void InstanceImportTask::executeTask()
|
||||
if (m_sourceUrl.isLocalFile())
|
||||
{
|
||||
m_archivePath = m_sourceUrl.toLocalFile();
|
||||
extractAndTweak();
|
||||
processZipPack();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -51,12 +51,14 @@ void InstanceImportTask::executeTask()
|
||||
|
||||
void InstanceImportTask::downloadSucceeded()
|
||||
{
|
||||
extractAndTweak();
|
||||
processZipPack();
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadFailed(QString reason)
|
||||
{
|
||||
emitFailed(reason);
|
||||
m_filesNetJob.reset();
|
||||
}
|
||||
|
||||
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
|
||||
@@ -64,34 +66,47 @@ 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()
|
||||
void InstanceImportTask::processZipPack()
|
||||
{
|
||||
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());
|
||||
// open the zip and find relevant files in it
|
||||
m_packZip.reset(new QuaZip(m_archivePath));
|
||||
if (!m_packZip->open(QuaZip::mdUnzip))
|
||||
{
|
||||
emitFailed(tr("Unable to open supplied modpack zip file."));
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList blacklist = {"instance.cfg", "manifest.json"};
|
||||
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
|
||||
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
|
||||
QString root;
|
||||
if(!mmcFound.isNull())
|
||||
{
|
||||
// process as MultiMC instance/pack
|
||||
qDebug() << "MultiMC:" << mmcFound;
|
||||
root = mmcFound;
|
||||
m_modpackType = ModpackType::MultiMC;
|
||||
}
|
||||
else if(!flameFound.isNull())
|
||||
{
|
||||
// process as Flame pack
|
||||
qDebug() << "Flame:" << flameFound;
|
||||
root = flameFound;
|
||||
m_modpackType = ModpackType::Flame;
|
||||
}
|
||||
|
||||
if(m_modpackType == ModpackType::Unknown)
|
||||
{
|
||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we extract just the pack
|
||||
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
|
||||
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
|
||||
m_extractFutureWatcher.setFuture(m_extractFuture);
|
||||
@@ -99,27 +114,272 @@ void InstanceImportTask::extractAndTweak()
|
||||
|
||||
void InstanceImportTask::extractFinished()
|
||||
{
|
||||
m_packZip.reset();
|
||||
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())
|
||||
|
||||
qDebug() << "Fixing permissions for extracted pack files...";
|
||||
QDirIterator it(extractDir, QDirIterator::Subdirectories);
|
||||
while (it.hasNext())
|
||||
{
|
||||
m_target->destroyStagingPath(m_stagingPath);
|
||||
emitFailed(tr("Archive does not contain instance.cfg"));
|
||||
return;
|
||||
auto filepath = it.next();
|
||||
QFileInfo file(filepath);
|
||||
auto permissions = QFile::permissions(filepath);
|
||||
auto origPermissions = permissions;
|
||||
if(file.isDir())
|
||||
{
|
||||
// Folder +rwx for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
|
||||
}
|
||||
else
|
||||
{
|
||||
// File +rw for current user
|
||||
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
|
||||
}
|
||||
if(origPermissions != permissions)
|
||||
{
|
||||
if(!QFile::setPermissions(filepath, permissions))
|
||||
{
|
||||
logWarning(tr("Could not fix permissions for %1").arg(filepath));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Fixed" << filepath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(m_modpackType)
|
||||
{
|
||||
case ModpackType::Flame:
|
||||
processFlame();
|
||||
return;
|
||||
case ModpackType::MultiMC:
|
||||
processMultiMC();
|
||||
return;
|
||||
case ModpackType::Unknown:
|
||||
emitFailed(tr("Archive does not contain a recognized modpack type."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceImportTask::extractAborted()
|
||||
{
|
||||
emitFailed(tr("Instance import has been aborted."));
|
||||
return;
|
||||
}
|
||||
|
||||
void InstanceImportTask::processFlame()
|
||||
{
|
||||
const static QMap<QString,QString> forgemap = {
|
||||
{"1.2.5", "3.4.9.171"},
|
||||
{"1.4.2", "6.0.1.355"},
|
||||
{"1.4.7", "6.6.2.534"},
|
||||
{"1.5.2", "7.8.1.737"}
|
||||
};
|
||||
Flame::Manifest pack;
|
||||
try
|
||||
{
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
|
||||
Flame::loadManifest(pack, configPath);
|
||||
QFile::remove(configPath);
|
||||
}
|
||||
catch (JSONValidationError & e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
}
|
||||
if(!pack.overrides.isEmpty())
|
||||
{
|
||||
QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
|
||||
if (QFile::exists(overridePath))
|
||||
{
|
||||
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
|
||||
if (!QFile::rename(overridePath, mcPath))
|
||||
{
|
||||
emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
|
||||
}
|
||||
}
|
||||
|
||||
QString forgeVersion;
|
||||
for(auto &loader: pack.minecraft.modLoaders)
|
||||
{
|
||||
auto id = loader.id;
|
||||
if(id.startsWith("forge-"))
|
||||
{
|
||||
id.remove("forge-");
|
||||
forgeVersion = id;
|
||||
continue;
|
||||
}
|
||||
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
|
||||
}
|
||||
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
instanceSettings->set("InstanceType", "OneSix");
|
||||
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
auto mcVersion = pack.minecraft.version;
|
||||
// Hack to correct some 'special sauce'...
|
||||
if(mcVersion.endsWith('.'))
|
||||
{
|
||||
mcVersion.remove(QRegExp("[.]+$"));
|
||||
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
|
||||
}
|
||||
auto components = instance.getComponentList();
|
||||
components->buildingFromScratch();
|
||||
components->setComponentVersion("net.minecraft", mcVersion, true);
|
||||
if(!forgeVersion.isEmpty())
|
||||
{
|
||||
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
|
||||
if(forgeVersion == "recommended")
|
||||
{
|
||||
if(forgemap.contains(mcVersion))
|
||||
{
|
||||
forgeVersion = forgemap[mcVersion];
|
||||
}
|
||||
else
|
||||
{
|
||||
logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion));
|
||||
}
|
||||
}
|
||||
components->setComponentVersion("net.minecraftforge", forgeVersion);
|
||||
}
|
||||
if (m_instIcon != "default")
|
||||
{
|
||||
instance.setIconKey(m_instIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(pack.name.contains("Direwolf20"))
|
||||
{
|
||||
instance.setIconKey("steve");
|
||||
}
|
||||
else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast"))
|
||||
{
|
||||
instance.setIconKey("ftb_logo");
|
||||
}
|
||||
else
|
||||
{
|
||||
// default to something other than the MultiMC default to distinguish these
|
||||
instance.setIconKey("flame");
|
||||
}
|
||||
}
|
||||
instance.init();
|
||||
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
||||
QFileInfo jarmodsInfo(jarmodsPath);
|
||||
if(jarmodsInfo.isDir())
|
||||
{
|
||||
// install all the jar mods
|
||||
qDebug() << "Found jarmods:";
|
||||
QDir jarmodsDir(jarmodsPath);
|
||||
QStringList jarMods;
|
||||
for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
|
||||
{
|
||||
qDebug() << info.fileName();
|
||||
jarMods.push_back(info.absoluteFilePath());
|
||||
}
|
||||
auto profile = instance.getComponentList();
|
||||
profile->installJarMods(jarMods);
|
||||
// nuke the original files
|
||||
FS::deletePath(jarmodsPath);
|
||||
}
|
||||
instance.setName(m_instName);
|
||||
m_modIdResolver.reset(new Flame::FileResolvingTask(pack));
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
|
||||
{
|
||||
auto results = m_modIdResolver->getResults();
|
||||
m_filesNetJob.reset(new NetJob(tr("Mod download")));
|
||||
for(auto result: results.files)
|
||||
{
|
||||
QString filename = result.fileName;
|
||||
if(!result.required)
|
||||
{
|
||||
filename += ".disabled";
|
||||
}
|
||||
|
||||
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
|
||||
auto path = FS::PathCombine(m_stagingPath , relpath);
|
||||
|
||||
switch(result.type)
|
||||
{
|
||||
case Flame::File::Type::Folder:
|
||||
{
|
||||
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
|
||||
// fall-through intentional, we treat these as plain old mods and dump them wherever.
|
||||
}
|
||||
case Flame::File::Type::SingleFile:
|
||||
case Flame::File::Type::Mod:
|
||||
{
|
||||
qDebug() << "Will download" << result.url << "to" << path;
|
||||
auto dl = Net::Download::makeFile(result.url, path);
|
||||
m_filesNetJob->addNetAction(dl);
|
||||
break;
|
||||
}
|
||||
case Flame::File::Type::Modpack:
|
||||
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
|
||||
break;
|
||||
case Flame::File::Type::Cmod2:
|
||||
case Flame::File::Type::Ctoc:
|
||||
case Flame::File::Type::Unknown:
|
||||
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_modIdResolver.reset();
|
||||
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
|
||||
{
|
||||
m_filesNetJob.reset();
|
||||
emitSucceeded();
|
||||
}
|
||||
);
|
||||
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
|
||||
{
|
||||
m_filesNetJob.reset();
|
||||
emitFailed(reason);
|
||||
});
|
||||
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
setStatus(tr("Downloading mods..."));
|
||||
m_filesNetJob->start();
|
||||
}
|
||||
);
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
|
||||
{
|
||||
m_modIdResolver.reset();
|
||||
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
|
||||
});
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
|
||||
{
|
||||
setProgress(current, total);
|
||||
});
|
||||
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
|
||||
{
|
||||
setStatus(status);
|
||||
});
|
||||
m_modIdResolver->start();
|
||||
}
|
||||
|
||||
void InstanceImportTask::processMultiMC()
|
||||
{
|
||||
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
|
||||
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
QString actualDir = instanceCfgFile.absolutePath();
|
||||
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
|
||||
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
|
||||
|
||||
// reset time played on import... because packs.
|
||||
instance.resetTimePlayed();
|
||||
@@ -147,18 +407,5 @@ void InstanceImportTask::extractFinished()
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "InstanceTask.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class QuaZip;
|
||||
class BaseInstanceProvider;
|
||||
namespace Flame
|
||||
{
|
||||
class FileResolvingTask;
|
||||
}
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
explicit InstanceImportTask(const QUrl sourceUrl);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
virtual void executeTask() override;
|
||||
|
||||
private:
|
||||
void extractAndTweak();
|
||||
void processZipPack();
|
||||
void processMultiMC();
|
||||
void processFlame();
|
||||
|
||||
private slots:
|
||||
void downloadSucceeded();
|
||||
@@ -32,16 +39,17 @@ private slots:
|
||||
void extractAborted();
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
NetJobPtr m_filesNetJob;
|
||||
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
|
||||
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;
|
||||
std::unique_ptr<QuaZip> m_packZip;
|
||||
QFuture<QStringList> m_extractFuture;
|
||||
QFutureWatcher<QStringList> m_extractFutureWatcher;
|
||||
enum class ModpackType{
|
||||
Unknown,
|
||||
MultiMC,
|
||||
Flame
|
||||
} m_modpackType = ModpackType::Unknown;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,10 +26,9 @@
|
||||
|
||||
#include "FolderInstanceProvider.h"
|
||||
|
||||
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
|
||||
: QAbstractListModel(parent), m_instDir(instDir)
|
||||
InstanceList::InstanceList(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
m_globalSettings = globalSettings;
|
||||
resumeWatch();
|
||||
}
|
||||
|
||||
@@ -241,6 +240,14 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
return NoError;
|
||||
}
|
||||
|
||||
void InstanceList::saveNow()
|
||||
{
|
||||
for(auto & item: m_instances)
|
||||
{
|
||||
item->saveNow();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::add(const QList<InstancePtr> &t)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
|
||||
@@ -338,5 +345,3 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
|
||||
emit dataChanged(index(i), index(i));
|
||||
}
|
||||
}
|
||||
|
||||
#include "InstanceList.moc"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,16 +27,14 @@
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class QFileSystemWatcher;
|
||||
class BaseInstance;
|
||||
class QDir;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
|
||||
explicit InstanceList(QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
|
||||
public:
|
||||
@@ -73,6 +71,7 @@ public:
|
||||
}
|
||||
|
||||
InstListError loadList(bool complete = false);
|
||||
void saveNow();
|
||||
|
||||
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
|
||||
void addInstanceProvider(BaseInstanceProvider * provider);
|
||||
@@ -100,9 +99,7 @@ private:
|
||||
protected:
|
||||
int m_watchLevel = 0;
|
||||
QSet<BaseInstanceProvider *> m_updatedProviders;
|
||||
QString m_instDir;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
|
||||
};
|
||||
|
||||
9
api/logic/InstanceTask.cpp
Normal file
9
api/logic/InstanceTask.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "InstanceTask.h"
|
||||
|
||||
InstanceTask::InstanceTask()
|
||||
{
|
||||
}
|
||||
|
||||
InstanceTask::~InstanceTask()
|
||||
{
|
||||
}
|
||||
55
api/logic/InstanceTask.h
Normal file
55
api/logic/InstanceTask.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
class BaseInstanceProvider;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceTask();
|
||||
virtual ~InstanceTask();
|
||||
|
||||
void setParentSettings(SettingsObjectPtr settings)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
}
|
||||
|
||||
void setStagingPath(const QString &stagingPath)
|
||||
{
|
||||
m_stagingPath = stagingPath;
|
||||
}
|
||||
|
||||
void setName(const QString &name)
|
||||
{
|
||||
m_instName = name;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_instName;
|
||||
}
|
||||
|
||||
void setIcon(const QString &icon)
|
||||
{
|
||||
m_instIcon = icon;
|
||||
}
|
||||
|
||||
void setGroup(const QString &group)
|
||||
{
|
||||
m_instGroup = group;
|
||||
}
|
||||
QString group() const
|
||||
{
|
||||
return m_instGroup;
|
||||
}
|
||||
|
||||
protected: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
QString m_stagingPath;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,172 +1,29 @@
|
||||
/*
|
||||
Copyright (C) 2010 Roberto Pompermaier
|
||||
Copyright (C) 2005-2014 Sergey A. Tachenov
|
||||
|
||||
Parts of this file were part of QuaZIP.
|
||||
|
||||
QuaZIP is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
QuaZIP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
See COPYING file for the full LGPL text.
|
||||
|
||||
Original ZIP package is copyrighted by Gilles Vollant and contributors,
|
||||
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
|
||||
*/
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <quazip.h>
|
||||
#include <JlCompress.h>
|
||||
#include <quazipdir.h>
|
||||
#include <quazipfile.h>
|
||||
#include <JlCompress.h>
|
||||
#include "MMCZip.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
bool copyData(QIODevice &inFile, QIODevice &outFile)
|
||||
{
|
||||
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)
|
||||
// ours
|
||||
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter)
|
||||
{
|
||||
QuaZip modZip(from.filePath());
|
||||
modZip.open(QuaZip::mdUnzip);
|
||||
@@ -176,7 +33,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
|
||||
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
|
||||
{
|
||||
QString filename = modZip.getCurrentFileName();
|
||||
if (!filter(filename))
|
||||
if (filter && !filter(filename))
|
||||
{
|
||||
qDebug() << "Skipping file " << filename << " from "
|
||||
<< from.fileName() << " - filtered";
|
||||
@@ -204,7 +61,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
|
||||
fileInsideMod.close();
|
||||
return false;
|
||||
}
|
||||
if (!copyData(fileInsideMod, zipOutFile))
|
||||
if (!JlCompress::copyData(fileInsideMod, zipOutFile))
|
||||
{
|
||||
zipOutFile.close();
|
||||
fileInsideMod.close();
|
||||
@@ -217,6 +74,7 @@ bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &containe
|
||||
return true;
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
|
||||
{
|
||||
QuaZip zipOut(targetJarPath);
|
||||
@@ -241,7 +99,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
continue;
|
||||
if (mod.type() == Mod::MOD_ZIPFILE)
|
||||
{
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter))
|
||||
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
@@ -251,9 +109,9 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_SINGLEFILE)
|
||||
{
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod.filename();
|
||||
if (!compressFile(&zipOut, filename.absoluteFilePath(),
|
||||
filename.fileName()))
|
||||
if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
@@ -264,12 +122,13 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
}
|
||||
else if (mod.type() == Mod::MOD_FOLDER)
|
||||
{
|
||||
// FIXME: buggy - does not work with addedFiles
|
||||
auto filename = mod.filename();
|
||||
QString what_to_zip = filename.absoluteFilePath();
|
||||
QDir dir(what_to_zip);
|
||||
dir.cdUp();
|
||||
QString parent_dir = dir.absolutePath();
|
||||
if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
|
||||
if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
@@ -279,9 +138,17 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
qDebug() << "Adding folder " << filename.fileName() << " from "
|
||||
<< filename.absoluteFilePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure we do not continue launching when something is missing or undefined...
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter))
|
||||
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");}))
|
||||
{
|
||||
zipOut.close();
|
||||
QFile::remove(targetJarPath);
|
||||
@@ -300,46 +167,8 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
|
||||
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)
|
||||
// ours
|
||||
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
for(auto fileName: rootDir.entryList(QDir::Files))
|
||||
@@ -349,7 +178,7 @@ QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString
|
||||
}
|
||||
for(auto fileName: rootDir.entryList(QDir::Dirs))
|
||||
{
|
||||
QString result = findFileInZip(zip, what, root + fileName);
|
||||
QString result = findFolderOfFileInZip(zip, what, root + fileName);
|
||||
if(!result.isEmpty())
|
||||
{
|
||||
return result;
|
||||
@@ -358,6 +187,7 @@ QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString
|
||||
return QString();
|
||||
}
|
||||
|
||||
// ours
|
||||
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
|
||||
{
|
||||
QuaZipDir rootDir(zip, root);
|
||||
@@ -376,95 +206,16 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
|
||||
return !result.isEmpty();
|
||||
}
|
||||
|
||||
bool removeFile(QStringList listFile)
|
||||
{
|
||||
bool ret = true;
|
||||
for (int i = 0; i < listFile.count(); i++)
|
||||
{
|
||||
ret &= QFile::remove(listFile.at(i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
|
||||
{
|
||||
if(!zip)
|
||||
return false;
|
||||
|
||||
if (zip->getMode() != QuaZip::mdUnzip)
|
||||
return false;
|
||||
|
||||
if (!fileName.isEmpty())
|
||||
zip->setCurrentFile(fileName);
|
||||
|
||||
QuaZipFile inFile(zip);
|
||||
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
|
||||
return false;
|
||||
|
||||
// Controllo esistenza cartella file risultato
|
||||
QDir curDir;
|
||||
if (fileDest.endsWith('/'))
|
||||
{
|
||||
if (!curDir.mkpath(fileDest))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QuaZipFileInfo64 info;
|
||||
if (!zip->getCurrentFileInfo(&info))
|
||||
return false;
|
||||
|
||||
QFile::Permissions srcPerm = info.getPermissions();
|
||||
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
|
||||
{
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
QFile(fileDest).setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QFile outFile;
|
||||
outFile.setFileName(fileDest);
|
||||
if (!outFile.open(QIODevice::WriteOnly))
|
||||
return false;
|
||||
|
||||
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
outFile.close();
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
outFile.close();
|
||||
|
||||
inFile.close();
|
||||
if (inFile.getZipError() != UNZ_OK)
|
||||
{
|
||||
removeFile(QStringList(fileDest));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (srcPerm != 0)
|
||||
{
|
||||
outFile.setPermissions(srcPerm);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
|
||||
{
|
||||
QDir directory(target);
|
||||
QStringList extracted;
|
||||
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
|
||||
if (!zip->goToFirstFile())
|
||||
{
|
||||
qWarning() << "Failed to seek to first file in zip";
|
||||
return QStringList();
|
||||
}
|
||||
do
|
||||
@@ -480,12 +231,25 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
|
||||
{
|
||||
absFilePath += "/";
|
||||
}
|
||||
if (!extractFile(zip, "", absFilePath))
|
||||
if (!JlCompress::extractFile(zip, "", absFilePath))
|
||||
{
|
||||
removeFile(extracted);
|
||||
qWarning() << "Failed to extract file" << name << "to" << absFilePath;
|
||||
JlCompress::removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
qDebug() << "Extracted file" << name;
|
||||
} while (zip->goToNextFile());
|
||||
return extracted;
|
||||
}
|
||||
|
||||
// ours
|
||||
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
|
||||
{
|
||||
QuaZip zip(fileCompressed);
|
||||
if (!zip.open(QuaZip::mdUnzip))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return MMCZip::extractSubDir(&zip, "", dir);
|
||||
}
|
||||
|
||||
@@ -1,70 +1,50 @@
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileInfo>
|
||||
#include <QSet>
|
||||
#include "minecraft/Mod.h"
|
||||
#include "SeparatorPrefixTree.h"
|
||||
#include <functional>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class QuaZip;
|
||||
#include <JlCompress.h>
|
||||
|
||||
namespace MMCZip
|
||||
{
|
||||
/**
|
||||
* Compress a subdirectory.
|
||||
* \param parentZip Opened zip containing the parent directory.
|
||||
* \param dir The full path to the directory to pack.
|
||||
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
|
||||
* \param recursive Whether to pack sub-directories as well or only files.
|
||||
* \return true if success, false otherwise.
|
||||
*/
|
||||
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);
|
||||
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
|
||||
const JlCompress::FilterFunction filter = nullptr);
|
||||
|
||||
/**
|
||||
* take a source jar, add mods to it, resulting in target jar
|
||||
*/
|
||||
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
|
||||
|
||||
/**
|
||||
* 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());
|
||||
QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
|
||||
|
||||
/**
|
||||
* Find a multiple files of the same name in archive by file name
|
||||
@@ -74,15 +54,18 @@ namespace MMCZip
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,30 +10,17 @@ public:
|
||||
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 void saveNow() override
|
||||
{
|
||||
}
|
||||
virtual QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
virtual bool shouldUpdate() const override
|
||||
{
|
||||
return false;
|
||||
};
|
||||
virtual QSet< QString > traits() override
|
||||
virtual QSet< QString > traits() const override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
@@ -45,21 +32,10 @@ public:
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual shared_qobject_ptr< Task > createUpdateTask() override
|
||||
virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) 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();
|
||||
@@ -84,6 +60,14 @@ public:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canEdit() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool canLaunch() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
QStringList verboseDescription(AuthSessionPtr session) override
|
||||
{
|
||||
QStringList out;
|
||||
|
||||
49
api/logic/ProblemProvider.h
Normal file
49
api/logic/ProblemProvider.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
enum class ProblemSeverity
|
||||
{
|
||||
None,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
struct PatchProblem
|
||||
{
|
||||
ProblemSeverity m_severity;
|
||||
QString m_description;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ProblemProvider
|
||||
{
|
||||
public:
|
||||
virtual ~ProblemProvider() {};
|
||||
virtual const QList<PatchProblem> getProblems() const = 0;
|
||||
virtual ProblemSeverity getProblemSeverity() const = 0;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
|
||||
{
|
||||
public:
|
||||
const QList<PatchProblem> getProblems() const override
|
||||
{
|
||||
return m_problems;
|
||||
}
|
||||
ProblemSeverity getProblemSeverity() const override
|
||||
{
|
||||
return m_problemSeverity;
|
||||
}
|
||||
virtual void addProblem(ProblemSeverity severity, const QString &description)
|
||||
{
|
||||
if(severity > m_problemSeverity)
|
||||
{
|
||||
m_problemSeverity = severity;
|
||||
}
|
||||
m_problems.append({severity, description});
|
||||
}
|
||||
|
||||
private:
|
||||
QList<PatchProblem> m_problems;
|
||||
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include <QWriteLocker>
|
||||
#include <QReadLocker>
|
||||
template <typename K, typename V>
|
||||
class RWStorage
|
||||
{
|
||||
@@ -42,7 +44,7 @@ public:
|
||||
}
|
||||
void setStale(K key)
|
||||
{
|
||||
QReadLocker l(&lock);
|
||||
QWriteLocker l(&lock);
|
||||
if(cache.contains(key))
|
||||
{
|
||||
stale_entries.insert(key);
|
||||
@@ -52,9 +54,10 @@ public:
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache.clear();
|
||||
stale_entries.clear();
|
||||
}
|
||||
private:
|
||||
QReadWriteLock lock;
|
||||
QMap<K, V> cache;
|
||||
QSet<K> stale_entries;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -75,6 +75,7 @@ void Version::parse()
|
||||
{
|
||||
m_sections.clear();
|
||||
|
||||
// FIXME: this is bad. versions can contain a lot more separators...
|
||||
QStringList parts = m_string.split('.');
|
||||
|
||||
for (const auto part : parts)
|
||||
@@ -82,59 +83,3 @@ void Version::parse()
|
||||
m_sections.append(Section(part));
|
||||
}
|
||||
}
|
||||
|
||||
bool versionIsInInterval(const QString &version, const QString &interval)
|
||||
{
|
||||
return versionIsInInterval(Version(version), interval);
|
||||
}
|
||||
bool versionIsInInterval(const Version &version, const QString &interval)
|
||||
{
|
||||
if (interval.isEmpty() || version.toString() == interval)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Interval notation is used
|
||||
QRegularExpression exp(
|
||||
"(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
|
||||
QRegularExpressionMatch match = exp.match(interval);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
const QChar start = match.captured("start").at(0);
|
||||
const QChar end = match.captured("end").at(0);
|
||||
const QString bottom = match.captured("bottom");
|
||||
const QString top = match.captured("top");
|
||||
|
||||
// check if in range (bottom)
|
||||
if (!bottom.isEmpty())
|
||||
{
|
||||
const auto bottomVersion = Version(bottom);
|
||||
if ((start == '[') && !(version >= bottomVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((start == '(') && !(version > bottomVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if in range (top)
|
||||
if (!top.isEmpty())
|
||||
{
|
||||
const auto topVersion = Version(top);
|
||||
if ((end == ']') && !(version <= topVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ((end == ')') && !(version < topVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -104,7 +104,3 @@ private:
|
||||
|
||||
void parse();
|
||||
};
|
||||
|
||||
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
|
||||
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,44 +60,6 @@ private slots:
|
||||
|
||||
}
|
||||
|
||||
void test_versionIsInInterval_data()
|
||||
{
|
||||
QTest::addColumn<QString>("version");
|
||||
QTest::addColumn<QString>("interval");
|
||||
QTest::addColumn<bool>("result");
|
||||
|
||||
QTest::newRow("empty, true") << "1.2.3" << "" << true;
|
||||
QTest::newRow("one version, true") << "1.2.3" << "1.2.3" << true;
|
||||
QTest::newRow("one version, false") << "1.2.3" << "1.2.2" << false;
|
||||
|
||||
QTest::newRow("one version inclusive <-> infinity, true") << "1.2.3" << "[1.2.3,)" << true;
|
||||
QTest::newRow("one version exclusive <-> infinity, true") << "1.2.3" << "(1.2.2,)" << true;
|
||||
QTest::newRow("one version inclusive <-> infitity, false") << "1.2.3" << "[1.2.4,)" << false;
|
||||
QTest::newRow("one version exclusive <-> infinity, false") << "1.2.3" << "(1.2.3,)" << false;
|
||||
|
||||
QTest::newRow("infinity <-> one version inclusive, true") << "1.2.3" << "(,1.2.3]" << true;
|
||||
QTest::newRow("infinity <-> one version exclusive, true") << "1.2.3" << "(,1.2.4)" << true;
|
||||
QTest::newRow("infinity <-> one version inclusive, false") << "1.2.3" << "(,1.2.2]" << false;
|
||||
QTest::newRow("infinity <-> one version exclusive, false") << "1.2.3" << "(,1.2.3)" << false;
|
||||
|
||||
QTest::newRow("inclusive <-> inclusive, true") << "1.2.3" << "[1.2.2,1.2.3]" << true;
|
||||
QTest::newRow("inclusive <-> exclusive, true") << "1.2.3" << "[1.2.3,1.2.4)" << true;
|
||||
QTest::newRow("exclusive <-> inclusive, true") << "1.2.3" << "(1.2.2,1.2.3]" << true;
|
||||
QTest::newRow("exclusive <-> exclusive, true") << "1.2.3" << "(1.2.2,1.2.4)" << true;
|
||||
QTest::newRow("inclusive <-> inclusive, false") << "1.2.3" << "[1.0.0,1.2.2]" << false;
|
||||
QTest::newRow("inclusive <-> exclusive, false") << "1.2.3" << "[1.0.0,1.2.3)" << false;
|
||||
QTest::newRow("exclusive <-> inclusive, false") << "1.2.3" << "(1.2.3,2.0.0]" << false;
|
||||
QTest::newRow("exclusive <-> exclusive, false") << "1.2.3" << "(1.0.0,1.2.3)" << false;
|
||||
}
|
||||
void test_versionIsInInterval()
|
||||
{
|
||||
QFETCH(QString, version);
|
||||
QFETCH(QString, interval);
|
||||
QFETCH(bool, result);
|
||||
|
||||
QCOMPARE(versionIsInInterval(version, interval), result);
|
||||
}
|
||||
|
||||
void test_versionCompare_data()
|
||||
{
|
||||
setupVersions();
|
||||
|
||||
@@ -22,4 +22,5 @@ public:
|
||||
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
|
||||
virtual bool iconFileExists(const QString &key) const = 0;
|
||||
virtual void installIcons(const QStringList &iconFiles) = 0;
|
||||
virtual void installIcon(const QString &file, const QString &name) = 0;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "JavaChecker.h"
|
||||
#include "JavaUtils.h"
|
||||
#include <FileSystem.h>
|
||||
#include <Commandline.h>
|
||||
#include <QFile>
|
||||
@@ -7,13 +8,15 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
#include "Env.h"
|
||||
|
||||
JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void JavaChecker::performCheck()
|
||||
{
|
||||
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
|
||||
QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar");
|
||||
|
||||
QStringList args;
|
||||
|
||||
@@ -40,6 +43,7 @@ void JavaChecker::performCheck()
|
||||
process->setArguments(args);
|
||||
process->setProgram(m_path);
|
||||
process->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
process->setProcessEnvironment(CleanEnviroment());
|
||||
qDebug() << "Running java checker: " + m_path + args.join(" ");;
|
||||
|
||||
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 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,20 +22,19 @@ void JavaCheckerJob::partFinished(JavaCheckResult result)
|
||||
num_finished++;
|
||||
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
|
||||
<< javacheckers.size();
|
||||
emit progress(num_finished, javacheckers.size());
|
||||
setProgress(num_finished, javacheckers.size());
|
||||
|
||||
javaresults.replace(result.id, result);
|
||||
|
||||
if (num_finished == javacheckers.size())
|
||||
{
|
||||
emit finished(javaresults);
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
void JavaCheckerJob::executeTask()
|
||||
{
|
||||
qDebug() << m_job_name.toLocal8Bit() << " started.";
|
||||
m_running = true;
|
||||
for (auto iter : javacheckers)
|
||||
{
|
||||
javaresults.append(JavaCheckResult());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@
|
||||
class JavaCheckerJob;
|
||||
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
|
||||
// FIXME: this just seems horribly redundant
|
||||
class JavaCheckerJob : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -31,41 +32,19 @@ public:
|
||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||
{
|
||||
javacheckers.append(base);
|
||||
total_progress++;
|
||||
// if this is already running, the action needs to be started right away!
|
||||
if (isRunning())
|
||||
{
|
||||
setProgress(current_progress, total_progress);
|
||||
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
|
||||
|
||||
setProgress(num_finished, javacheckers.size());
|
||||
connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
|
||||
base->performCheck();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
JavaCheckerPtr operator[](int index)
|
||||
QList<JavaCheckResult> getResults()
|
||||
{
|
||||
return javacheckers[index];
|
||||
return javaresults;
|
||||
}
|
||||
;
|
||||
JavaCheckerPtr first()
|
||||
{
|
||||
if (javacheckers.size())
|
||||
return javacheckers[0];
|
||||
return JavaCheckerPtr();
|
||||
}
|
||||
int size() const
|
||||
{
|
||||
return javacheckers.size();
|
||||
}
|
||||
virtual bool isRunning() const override
|
||||
{
|
||||
return m_running;
|
||||
}
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void finished(QList<JavaCheckResult>);
|
||||
|
||||
private slots:
|
||||
void partFinished(JavaCheckResult result);
|
||||
@@ -77,8 +56,5 @@ private:
|
||||
QString m_job_name;
|
||||
QList<JavaCheckerPtr> javacheckers;
|
||||
QList<JavaCheckResult> javaresults;
|
||||
qint64 current_progress = 0;
|
||||
qint64 total_progress = 0;
|
||||
int num_finished = 0;
|
||||
bool m_running = false;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,9 +29,29 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Task *JavaInstallList::getLoadTask()
|
||||
shared_qobject_ptr<Task> JavaInstallList::getLoadTask()
|
||||
{
|
||||
return new JavaListLoadTask(this);
|
||||
load();
|
||||
return getCurrentTask();
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Task> JavaInstallList::getCurrentTask()
|
||||
{
|
||||
if(m_status == Status::InProgress)
|
||||
{
|
||||
return m_loadTask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JavaInstallList::load()
|
||||
{
|
||||
if(m_status != Status::InProgress)
|
||||
{
|
||||
m_status = Status::InProgress;
|
||||
m_loadTask = new JavaListLoadTask(this);
|
||||
m_loadTask->start();
|
||||
}
|
||||
}
|
||||
|
||||
const BaseVersionPtr JavaInstallList::at(int i) const
|
||||
@@ -41,7 +61,7 @@ const BaseVersionPtr JavaInstallList::at(int i) const
|
||||
|
||||
bool JavaInstallList::isLoaded()
|
||||
{
|
||||
return m_loaded;
|
||||
return m_status == JavaInstallList::Status::Done;
|
||||
}
|
||||
|
||||
int JavaInstallList::count() const
|
||||
@@ -87,7 +107,6 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_vlist = versions;
|
||||
m_loaded = true;
|
||||
sortVersions();
|
||||
if(m_vlist.size())
|
||||
{
|
||||
@@ -95,6 +114,8 @@ void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
|
||||
best->recommended = true;
|
||||
}
|
||||
endResetModel();
|
||||
m_status = Status::Done;
|
||||
m_loadTask.reset();
|
||||
}
|
||||
|
||||
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
|
||||
@@ -129,7 +150,7 @@ void JavaListLoadTask::executeTask()
|
||||
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::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||
|
||||
qDebug() << "Probing the following Java paths: ";
|
||||
@@ -149,9 +170,10 @@ void JavaListLoadTask::executeTask()
|
||||
m_job->start();
|
||||
}
|
||||
|
||||
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
|
||||
void JavaListLoadTask::javaCheckerFinished()
|
||||
{
|
||||
QList<JavaInstallPtr> candidates;
|
||||
auto results = m_job->getResults();
|
||||
|
||||
qDebug() << "Found the following valid Java installations:";
|
||||
for(JavaCheckResult result : results)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,25 +31,35 @@ class JavaListLoadTask;
|
||||
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
|
||||
{
|
||||
Q_OBJECT
|
||||
enum class Status
|
||||
{
|
||||
NotDone,
|
||||
InProgress,
|
||||
Done
|
||||
};
|
||||
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;
|
||||
shared_qobject_ptr<Task> getLoadTask() override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersionPtr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
virtual RoleList providesRoles() const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
RoleList providesRoles() const override;
|
||||
|
||||
public slots:
|
||||
virtual void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
void updateListData(QList<BaseVersionPtr> versions) override;
|
||||
|
||||
protected:
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
void load();
|
||||
shared_qobject_ptr<Task> getCurrentTask();
|
||||
|
||||
bool m_loaded = false;
|
||||
protected:
|
||||
Status m_status = Status::NotDone;
|
||||
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
|
||||
QList<BaseVersionPtr> m_vlist;
|
||||
};
|
||||
|
||||
class JavaListLoadTask : public Task
|
||||
@@ -60,9 +70,9 @@ public:
|
||||
explicit JavaListLoadTask(JavaInstallList *vlist);
|
||||
~JavaListLoadTask();
|
||||
|
||||
virtual void executeTask();
|
||||
void executeTask() override;
|
||||
public slots:
|
||||
void javaCheckerFinished(QList<JavaCheckResult> results);
|
||||
void javaCheckerFinished();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 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,14 +22,105 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include "java/JavaUtils.h"
|
||||
#include "java/JavaCheckerJob.h"
|
||||
#include "java/JavaInstallList.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
JavaUtils::JavaUtils()
|
||||
{
|
||||
}
|
||||
|
||||
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
|
||||
{
|
||||
QDir mmcBin(QCoreApplication::applicationDirPath());
|
||||
auto items = LD_LIBRARY_PATH.split(':');
|
||||
QStringList final;
|
||||
for(auto & item: items)
|
||||
{
|
||||
QDir test(item);
|
||||
if(test == mmcBin)
|
||||
{
|
||||
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
|
||||
continue;
|
||||
}
|
||||
final.append(item);
|
||||
}
|
||||
return final.join(':');
|
||||
}
|
||||
|
||||
QProcessEnvironment CleanEnviroment()
|
||||
{
|
||||
// prepare the process environment
|
||||
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
|
||||
QProcessEnvironment env;
|
||||
|
||||
QStringList ignored =
|
||||
{
|
||||
"JAVA_ARGS",
|
||||
"CLASSPATH",
|
||||
"CONFIGPATH",
|
||||
"JAVA_HOME",
|
||||
"JRE_HOME",
|
||||
"_JAVA_OPTIONS",
|
||||
"JAVA_OPTIONS",
|
||||
"JAVA_TOOL_OPTIONS"
|
||||
};
|
||||
for(auto key: rawenv.keys())
|
||||
{
|
||||
auto value = rawenv.value(key);
|
||||
// filter out dangerous java crap
|
||||
if(ignored.contains(key))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
// filter MultiMC-related things
|
||||
if(key.startsWith("QT_"))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
#ifdef Q_OS_LINUX
|
||||
// Do not pass LD_* variables to java. They were intended for MultiMC
|
||||
if(key.startsWith("LD_"))
|
||||
{
|
||||
qDebug() << "Env: ignoring" << key << value;
|
||||
continue;
|
||||
}
|
||||
// Strip IBus
|
||||
// IBus is a Linux IME framework. For some reason, it breaks MC?
|
||||
if (key == "XMODIFIERS" && value.contains(IBUS))
|
||||
{
|
||||
QString save = value;
|
||||
value.replace(IBUS, "");
|
||||
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
|
||||
}
|
||||
if(key == "GAME_PRELOAD")
|
||||
{
|
||||
env.insert("LD_PRELOAD", value);
|
||||
continue;
|
||||
}
|
||||
if(key == "GAME_LIBRARY_PATH")
|
||||
{
|
||||
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
// qDebug() << "Env: " << key << value;
|
||||
env.insert(key, value);
|
||||
}
|
||||
#ifdef Q_OS_LINUX
|
||||
// HACK: Workaround for QTBUG42500
|
||||
if(!env.contains("LD_LIBRARY_PATH"))
|
||||
{
|
||||
env.insert("LD_LIBRARY_PATH", "");
|
||||
}
|
||||
#endif
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
|
||||
{
|
||||
JavaInstallPtr javaVersion(new JavaInstall());
|
||||
@@ -94,7 +185,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
|
||||
// Iterate until RegEnumKeyEx fails
|
||||
if (numSubKeys > 0)
|
||||
{
|
||||
for (int i = 0; i < numSubKeys; i++)
|
||||
for (DWORD i = 0; i < numSubKeys; i++)
|
||||
{
|
||||
subKeyNameSize = 255;
|
||||
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaChecker.h"
|
||||
#include "JavaInstallList.h"
|
||||
|
||||
@@ -27,6 +26,8 @@
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
QProcessEnvironment CleanEnviroment();
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -60,9 +60,18 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
|
||||
{
|
||||
if(m_parseable && rhs.m_parseable)
|
||||
{
|
||||
if(m_major < rhs.m_major)
|
||||
auto major = m_major;
|
||||
auto rmajor = rhs.m_major;
|
||||
|
||||
// HACK: discourage using java 9
|
||||
if(major > 8)
|
||||
major = -major;
|
||||
if(rmajor > 8)
|
||||
rmajor = -rmajor;
|
||||
|
||||
if(major < rmajor)
|
||||
return true;
|
||||
if(m_major > rhs.m_major)
|
||||
if(major > rmajor)
|
||||
return false;
|
||||
if(m_minor < rhs.m_minor)
|
||||
return true;
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
#include "multimc_logic_export.h"
|
||||
#include <QString>
|
||||
|
||||
// NOTE: apparently the GNU C library pollutes the global namespace with these... undef them.
|
||||
#ifdef major
|
||||
#undef major
|
||||
#endif
|
||||
#ifdef minor
|
||||
#undef minor
|
||||
#endif
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT JavaVersion
|
||||
{
|
||||
friend class JavaVersionTest;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -83,7 +83,7 @@ void LaunchTask::onStepFinished()
|
||||
}
|
||||
|
||||
auto step = m_steps[currentStep];
|
||||
if(step->successful())
|
||||
if(step->wasSuccessful())
|
||||
{
|
||||
// end?
|
||||
if(currentStep == m_steps.size() - 1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
|
||||
@@ -69,6 +69,11 @@ void LogModel::suspend(bool suspend)
|
||||
m_suspended = suspend;
|
||||
}
|
||||
|
||||
bool LogModel::suspended()
|
||||
{
|
||||
return m_suspended;
|
||||
}
|
||||
|
||||
void LogModel::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
@@ -147,3 +152,16 @@ void LogModel::setOverflowMessage(const QString& overflowMessage)
|
||||
{
|
||||
m_overflowMessage = overflowMessage;
|
||||
}
|
||||
|
||||
void LogModel::setLineWrap(bool state)
|
||||
{
|
||||
if(m_lineWrap != state)
|
||||
{
|
||||
m_lineWrap = state;
|
||||
}
|
||||
}
|
||||
|
||||
bool LogModel::wrapLines() const
|
||||
{
|
||||
return m_lineWrap;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ public:
|
||||
|
||||
void append(MessageLevel::Enum, QString line);
|
||||
void clear();
|
||||
|
||||
void suspend(bool suspend);
|
||||
bool suspended();
|
||||
|
||||
QString toPlainText();
|
||||
|
||||
@@ -26,6 +28,9 @@ public:
|
||||
void setStopOnOverflow(bool stop);
|
||||
void setOverflowMessage(const QString & overflowMessage);
|
||||
|
||||
void setLineWrap(bool state);
|
||||
bool wrapLines() const;
|
||||
|
||||
enum Roles
|
||||
{
|
||||
LevelRole = Qt::UserRole
|
||||
@@ -48,6 +53,7 @@ private: /* data */
|
||||
bool m_stopOnOverflow = false;
|
||||
QString m_overflowMessage = "OVERFLOW";
|
||||
bool m_suspended = false;
|
||||
bool m_lineWrap = true;
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(LogModel)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,7 @@ void Update::executeTask()
|
||||
emitFailed(tr("Task aborted."));
|
||||
return;
|
||||
}
|
||||
m_updateTask.reset(m_parent->instance()->createUpdateTask());
|
||||
m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
|
||||
if(m_updateTask)
|
||||
{
|
||||
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
|
||||
@@ -42,7 +42,7 @@ void Update::proceed()
|
||||
|
||||
void Update::updateFinished()
|
||||
{
|
||||
if(m_updateTask->successful())
|
||||
if(m_updateTask->wasSuccessful())
|
||||
{
|
||||
m_updateTask.reset();
|
||||
emitSucceeded();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 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,13 +19,14 @@
|
||||
#include <QObjectPtr.h>
|
||||
#include <LoggedProcess.h>
|
||||
#include <java/JavaChecker.h>
|
||||
#include <net/Mode.h>
|
||||
|
||||
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
|
||||
class Update: public LaunchStep
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
|
||||
explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {};
|
||||
virtual ~Update() {};
|
||||
|
||||
void executeTask() override;
|
||||
@@ -40,4 +41,5 @@ private slots:
|
||||
private:
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
bool m_aborted = false;
|
||||
Net::Mode m_mode = Net::Mode::Offline;
|
||||
};
|
||||
|
||||
162
api/logic/meta/BaseEntity.cpp
Normal file
162
api/logic/meta/BaseEntity.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
/* Copyright 2015-2018 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 "BaseEntity.h"
|
||||
|
||||
#include "Json.h"
|
||||
|
||||
#include "net/Download.h"
|
||||
#include "net/HttpMetaCache.h"
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "Env.h"
|
||||
#include "Json.h"
|
||||
|
||||
class ParsingValidator : public Net::Validator
|
||||
{
|
||||
public: /* con/des */
|
||||
ParsingValidator(Meta::BaseEntity *entity) : m_entity(entity)
|
||||
{
|
||||
};
|
||||
virtual ~ParsingValidator()
|
||||
{
|
||||
};
|
||||
|
||||
public: /* methods */
|
||||
bool init(QNetworkRequest &) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool write(QByteArray & data) override
|
||||
{
|
||||
this->data.append(data);
|
||||
return true;
|
||||
}
|
||||
bool abort() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool validate(QNetworkReply &) override
|
||||
{
|
||||
auto fname = m_entity->localFilename();
|
||||
try
|
||||
{
|
||||
m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname));
|
||||
return true;
|
||||
}
|
||||
catch (Exception &e)
|
||||
{
|
||||
qWarning() << "Unable to parse response:" << e.cause();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private: /* data */
|
||||
QByteArray data;
|
||||
Meta::BaseEntity *m_entity;
|
||||
};
|
||||
|
||||
Meta::BaseEntity::~BaseEntity()
|
||||
{
|
||||
}
|
||||
|
||||
QUrl Meta::BaseEntity::url() const
|
||||
{
|
||||
return QUrl("https://v1.meta.multimc.org").resolved(localFilename());
|
||||
}
|
||||
|
||||
bool Meta::BaseEntity::loadLocalFile()
|
||||
{
|
||||
const QString fname = QDir("meta").absoluteFilePath(localFilename());
|
||||
if (!QFile::exists(fname))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// TODO: check if the file has the expected checksum
|
||||
try
|
||||
{
|
||||
parse(Json::requireObject(Json::requireDocument(fname, fname), fname));
|
||||
return true;
|
||||
}
|
||||
catch (Exception &e)
|
||||
{
|
||||
qDebug() << QString("Unable to parse file %1: %2").arg(fname, e.cause());
|
||||
// just make sure it's gone and we never consider it again.
|
||||
QFile::remove(fname);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Meta::BaseEntity::load(Net::Mode loadType)
|
||||
{
|
||||
// load local file if nothing is loaded yet
|
||||
if(!isLoaded())
|
||||
{
|
||||
if(loadLocalFile())
|
||||
{
|
||||
m_loadStatus = LoadStatus::Local;
|
||||
}
|
||||
}
|
||||
// if we need remote update, run the update task
|
||||
if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate())
|
||||
{
|
||||
return;
|
||||
}
|
||||
NetJob *job = new NetJob(QObject::tr("Download of meta file %1").arg(localFilename()));
|
||||
auto url = this->url();
|
||||
auto entry = ENV.metacache()->resolveEntry("meta", localFilename());
|
||||
entry->setStale(true);
|
||||
auto dl = Net::Download::makeCached(url, entry);
|
||||
/*
|
||||
* The validator parses the file and loads it into the object.
|
||||
* If that fails, the file is not written to storage.
|
||||
*/
|
||||
dl->addValidator(new ParsingValidator(this));
|
||||
job->addNetAction(dl);
|
||||
m_updateStatus = UpdateStatus::InProgress;
|
||||
m_updateTask.reset(job);
|
||||
QObject::connect(job, &NetJob::succeeded, [&]()
|
||||
{
|
||||
m_loadStatus = LoadStatus::Remote;
|
||||
m_updateStatus = UpdateStatus::Succeeded;
|
||||
m_updateTask.reset();
|
||||
});
|
||||
QObject::connect(job, &NetJob::failed, [&]()
|
||||
{
|
||||
m_updateStatus = UpdateStatus::Failed;
|
||||
m_updateTask.reset();
|
||||
});
|
||||
m_updateTask->start();
|
||||
}
|
||||
|
||||
bool Meta::BaseEntity::isLoaded() const
|
||||
{
|
||||
return m_loadStatus > LoadStatus::NotLoaded;
|
||||
}
|
||||
|
||||
bool Meta::BaseEntity::shouldStartRemoteUpdate() const
|
||||
{
|
||||
// TODO: version-locks and offline mode?
|
||||
return m_updateStatus != UpdateStatus::InProgress;
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask()
|
||||
{
|
||||
if(m_updateStatus == UpdateStatus::InProgress)
|
||||
{
|
||||
return m_updateTask;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
68
api/logic/meta/BaseEntity.h
Normal file
68
api/logic/meta/BaseEntity.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* Copyright 2015-2018 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 <QJsonObject>
|
||||
#include <QObject>
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
class Task;
|
||||
namespace Meta
|
||||
{
|
||||
class MULTIMC_LOGIC_EXPORT BaseEntity
|
||||
{
|
||||
public: /* types */
|
||||
using Ptr = std::shared_ptr<BaseEntity>;
|
||||
enum class LoadStatus
|
||||
{
|
||||
NotLoaded,
|
||||
Local,
|
||||
Remote
|
||||
};
|
||||
enum class UpdateStatus
|
||||
{
|
||||
NotDone,
|
||||
InProgress,
|
||||
Failed,
|
||||
Succeeded
|
||||
};
|
||||
|
||||
public:
|
||||
virtual ~BaseEntity();
|
||||
|
||||
virtual void parse(const QJsonObject &obj) = 0;
|
||||
|
||||
virtual QString localFilename() const = 0;
|
||||
virtual QUrl url() const;
|
||||
|
||||
bool isLoaded() const;
|
||||
bool shouldStartRemoteUpdate() const;
|
||||
|
||||
void load(Net::Mode loadType);
|
||||
shared_qobject_ptr<Task> getCurrentTask();
|
||||
|
||||
protected: /* methods */
|
||||
bool loadLocalFile();
|
||||
|
||||
private:
|
||||
LoadStatus m_loadStatus = LoadStatus::NotLoaded;
|
||||
UpdateStatus m_updateStatus = UpdateStatus::NotDone;
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2018 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,18 +13,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WonkoIndex.h"
|
||||
#include "Index.h"
|
||||
|
||||
#include "WonkoVersionList.h"
|
||||
#include "tasks/BaseWonkoEntityLocalLoadTask.h"
|
||||
#include "tasks/BaseWonkoEntityRemoteLoadTask.h"
|
||||
#include "format/WonkoFormat.h"
|
||||
#include "VersionList.h"
|
||||
#include "JsonFormat.h"
|
||||
|
||||
WonkoIndex::WonkoIndex(QObject *parent)
|
||||
namespace Meta
|
||||
{
|
||||
Index::Index(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
WonkoIndex::WonkoIndex(const QVector<WonkoVersionListPtr> &lists, QObject *parent)
|
||||
Index::Index(const QVector<VersionListPtr> &lists, QObject *parent)
|
||||
: QAbstractListModel(parent), m_lists(lists)
|
||||
{
|
||||
for (int i = 0; i < m_lists.size(); ++i)
|
||||
@@ -34,14 +34,14 @@ WonkoIndex::WonkoIndex(const QVector<WonkoVersionListPtr> &lists, QObject *paren
|
||||
}
|
||||
}
|
||||
|
||||
QVariant WonkoIndex::data(const QModelIndex &index, int role) const
|
||||
QVariant Index::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (index.parent().isValid() || index.row() < 0 || index.row() >= m_lists.size())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
WonkoVersionListPtr list = m_lists.at(index.row());
|
||||
VersionListPtr list = m_lists.at(index.row());
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
@@ -56,15 +56,15 @@ QVariant WonkoIndex::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
int WonkoIndex::rowCount(const QModelIndex &parent) const
|
||||
int Index::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_lists.size();
|
||||
}
|
||||
int WonkoIndex::columnCount(const QModelIndex &parent) const
|
||||
int Index::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
QVariant WonkoIndex::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0)
|
||||
{
|
||||
@@ -76,36 +76,36 @@ QVariant WonkoIndex::headerData(int section, Qt::Orientation orientation, int ro
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Task> WonkoIndex::remoteUpdateTask()
|
||||
{
|
||||
return std::unique_ptr<WonkoIndexRemoteLoadTask>(new WonkoIndexRemoteLoadTask(this, this));
|
||||
}
|
||||
std::unique_ptr<Task> WonkoIndex::localUpdateTask()
|
||||
{
|
||||
return std::unique_ptr<WonkoIndexLocalLoadTask>(new WonkoIndexLocalLoadTask(this, this));
|
||||
}
|
||||
|
||||
QJsonObject WonkoIndex::serialized() const
|
||||
{
|
||||
return WonkoFormat::serializeIndex(this);
|
||||
}
|
||||
|
||||
bool WonkoIndex::hasUid(const QString &uid) const
|
||||
bool Index::hasUid(const QString &uid) const
|
||||
{
|
||||
return m_uids.contains(uid);
|
||||
}
|
||||
WonkoVersionListPtr WonkoIndex::getList(const QString &uid) const
|
||||
|
||||
VersionListPtr Index::get(const QString &uid)
|
||||
{
|
||||
return m_uids.value(uid, nullptr);
|
||||
}
|
||||
WonkoVersionListPtr WonkoIndex::getListGuaranteed(const QString &uid) const
|
||||
{
|
||||
return m_uids.value(uid, std::make_shared<WonkoVersionList>(uid));
|
||||
VersionListPtr out = m_uids.value(uid, nullptr);
|
||||
if(!out)
|
||||
{
|
||||
out = std::make_shared<VersionList>(uid);
|
||||
m_uids[uid] = out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void WonkoIndex::merge(const Ptr &other)
|
||||
VersionPtr Index::get(const QString &uid, const QString &version)
|
||||
{
|
||||
const QVector<WonkoVersionListPtr> lists = std::dynamic_pointer_cast<WonkoIndex>(other)->m_lists;
|
||||
auto list = get(uid);
|
||||
return list->getVersion(version);
|
||||
}
|
||||
|
||||
void Index::parse(const QJsonObject& obj)
|
||||
{
|
||||
parseIndex(obj, this);
|
||||
}
|
||||
|
||||
void Index::merge(const std::shared_ptr<Index> &other)
|
||||
{
|
||||
const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
|
||||
// initial load, no need to merge
|
||||
if (m_lists.isEmpty())
|
||||
{
|
||||
@@ -120,11 +120,11 @@ void WonkoIndex::merge(const Ptr &other)
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const WonkoVersionListPtr &list : lists)
|
||||
for (const VersionListPtr &list : lists)
|
||||
{
|
||||
if (m_uids.contains(list->uid()))
|
||||
{
|
||||
m_uids[list->uid()]->merge(list);
|
||||
m_uids[list->uid()]->mergeFromIndex(list);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -138,10 +138,11 @@ void WonkoIndex::merge(const Ptr &other)
|
||||
}
|
||||
}
|
||||
|
||||
void WonkoIndex::connectVersionList(const int row, const WonkoVersionListPtr &list)
|
||||
void Index::connectVersionList(const int row, const VersionListPtr &list)
|
||||
{
|
||||
connect(list.get(), &WonkoVersionList::nameChanged, this, [this, row]()
|
||||
connect(list.get(), &VersionList::nameChanged, this, [this, row]()
|
||||
{
|
||||
emit dataChanged(index(row), index(row), QVector<int>() << Qt::DisplayRole);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2018 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,19 +18,23 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <memory>
|
||||
|
||||
#include "BaseWonkoEntity.h"
|
||||
#include "BaseEntity.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class Task;
|
||||
using WonkoVersionListPtr = std::shared_ptr<class WonkoVersionList>;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT WonkoIndex : public QAbstractListModel, public BaseWonkoEntity
|
||||
namespace Meta
|
||||
{
|
||||
using VersionListPtr = std::shared_ptr<class VersionList>;
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Index : public QAbstractListModel, public BaseEntity
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WonkoIndex(QObject *parent = nullptr);
|
||||
explicit WonkoIndex(const QVector<WonkoVersionListPtr> &lists, QObject *parent = nullptr);
|
||||
explicit Index(QObject *parent = nullptr);
|
||||
explicit Index(const QVector<VersionListPtr> &lists, QObject *parent = nullptr);
|
||||
|
||||
enum
|
||||
{
|
||||
@@ -44,25 +48,24 @@ public:
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
|
||||
std::unique_ptr<Task> remoteUpdateTask() override;
|
||||
std::unique_ptr<Task> localUpdateTask() override;
|
||||
|
||||
QString localFilename() const override { return "index.json"; }
|
||||
QJsonObject serialized() const override;
|
||||
|
||||
// queries
|
||||
VersionListPtr get(const QString &uid);
|
||||
VersionPtr get(const QString &uid, const QString &version);
|
||||
bool hasUid(const QString &uid) const;
|
||||
WonkoVersionListPtr getList(const QString &uid) const;
|
||||
WonkoVersionListPtr getListGuaranteed(const QString &uid) const;
|
||||
|
||||
QVector<WonkoVersionListPtr> lists() const { return m_lists; }
|
||||
QVector<VersionListPtr> lists() const { return m_lists; }
|
||||
|
||||
public: // for usage by parsers only
|
||||
void merge(const BaseWonkoEntity::Ptr &other);
|
||||
void merge(const std::shared_ptr<Index> &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
private:
|
||||
QVector<WonkoVersionListPtr> m_lists;
|
||||
QHash<QString, WonkoVersionListPtr> m_uids;
|
||||
QVector<VersionListPtr> m_lists;
|
||||
QHash<QString, VersionListPtr> m_uids;
|
||||
|
||||
void connectVersionList(const int row, const WonkoVersionListPtr &list);
|
||||
void connectVersionList(const int row, const VersionListPtr &list);
|
||||
};
|
||||
}
|
||||
|
||||
44
api/logic/meta/Index_test.cpp
Normal file
44
api/logic/meta/Index_test.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <QTest>
|
||||
#include "TestUtil.h"
|
||||
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
#include "Env.h"
|
||||
|
||||
class IndexTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private
|
||||
slots:
|
||||
void test_isProvidedByEnv()
|
||||
{
|
||||
QVERIFY(ENV.metadataIndex());
|
||||
QCOMPARE(ENV.metadataIndex(), ENV.metadataIndex());
|
||||
}
|
||||
|
||||
void test_hasUid_and_getList()
|
||||
{
|
||||
Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
|
||||
QVERIFY(windex.hasUid("list1"));
|
||||
QVERIFY(!windex.hasUid("asdf"));
|
||||
QVERIFY(windex.get("list2") != nullptr);
|
||||
QCOMPARE(windex.get("list2")->uid(), QString("list2"));
|
||||
QVERIFY(windex.get("adsf") != nullptr);
|
||||
}
|
||||
|
||||
void test_merge()
|
||||
{
|
||||
Meta::Index windex({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")});
|
||||
QCOMPARE(windex.lists().size(), 3);
|
||||
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list1"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list3")})));
|
||||
QCOMPARE(windex.lists().size(), 3);
|
||||
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list4"), std::make_shared<Meta::VersionList>("list2"), std::make_shared<Meta::VersionList>("list5")})));
|
||||
QCOMPARE(windex.lists().size(), 5);
|
||||
windex.merge(std::shared_ptr<Meta::Index>(new Meta::Index({std::make_shared<Meta::VersionList>("list6")})));
|
||||
QCOMPARE(windex.lists().size(), 6);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(IndexTest)
|
||||
|
||||
#include "Index_test.moc"
|
||||
218
api/logic/meta/JsonFormat.cpp
Normal file
218
api/logic/meta/JsonFormat.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/* Copyright 2015-2018 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 "JsonFormat.h"
|
||||
|
||||
// FIXME: remove this from here... somehow
|
||||
#include "minecraft/OneSixVersionFormat.h"
|
||||
#include "Json.h"
|
||||
|
||||
#include "Index.h"
|
||||
#include "Version.h"
|
||||
#include "VersionList.h"
|
||||
|
||||
using namespace Json;
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
|
||||
MetadataVersion currentFormatVersion()
|
||||
{
|
||||
return MetadataVersion::InitialRelease;
|
||||
}
|
||||
|
||||
// Index
|
||||
static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
|
||||
{
|
||||
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
|
||||
QVector<VersionListPtr> lists;
|
||||
lists.reserve(objects.size());
|
||||
std::transform(objects.begin(), objects.end(), std::back_inserter(lists), [](const QJsonObject &obj)
|
||||
{
|
||||
VersionListPtr list = std::make_shared<VersionList>(requireString(obj, "uid"));
|
||||
list->setName(ensureString(obj, "name", QString()));
|
||||
return list;
|
||||
});
|
||||
return std::make_shared<Index>(lists);
|
||||
}
|
||||
|
||||
// Version
|
||||
static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
|
||||
{
|
||||
VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
|
||||
version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
|
||||
version->setType(ensureString(obj, "type", QString()));
|
||||
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
|
||||
version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
|
||||
RequireSet requires, conflicts;
|
||||
parseRequires(obj, &requires, "requires");
|
||||
parseRequires(obj, &conflicts, "conflicts");
|
||||
version->setRequires(requires, conflicts);
|
||||
return version;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
|
||||
{
|
||||
VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
|
||||
|
||||
version->setData(OneSixVersionFormat::versionFileFromJson(QJsonDocument(obj),
|
||||
QString("%1/%2.json").arg(version->uid(), version->version()),
|
||||
obj.contains("order")));
|
||||
return version;
|
||||
}
|
||||
|
||||
// Version list / package
|
||||
static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj)
|
||||
{
|
||||
const QString uid = requireString(obj, "uid");
|
||||
|
||||
const QVector<QJsonObject> versionsRaw = requireIsArrayOf<QJsonObject>(obj, "versions");
|
||||
QVector<VersionPtr> versions;
|
||||
versions.reserve(versionsRaw.size());
|
||||
std::transform(versionsRaw.begin(), versionsRaw.end(), std::back_inserter(versions), [uid](const QJsonObject &vObj)
|
||||
{
|
||||
auto version = parseCommonVersion(uid, vObj);
|
||||
version->setProvidesRecommendations();
|
||||
return version;
|
||||
});
|
||||
|
||||
VersionListPtr list = std::make_shared<VersionList>(uid);
|
||||
list->setName(ensureString(obj, "name", QString()));
|
||||
list->setVersions(versions);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required)
|
||||
{
|
||||
if (!obj.contains("formatVersion"))
|
||||
{
|
||||
if(required)
|
||||
{
|
||||
return MetadataVersion::Invalid;
|
||||
}
|
||||
return MetadataVersion::InitialRelease;
|
||||
}
|
||||
if (!obj.value("formatVersion").isDouble())
|
||||
{
|
||||
return MetadataVersion::Invalid;
|
||||
}
|
||||
switch(obj.value("formatVersion").toInt())
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
return MetadataVersion::InitialRelease;
|
||||
default:
|
||||
return MetadataVersion::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version)
|
||||
{
|
||||
if(version == MetadataVersion::Invalid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
obj.insert("formatVersion", int(version));
|
||||
}
|
||||
|
||||
void parseIndex(const QJsonObject &obj, Index *ptr)
|
||||
{
|
||||
const MetadataVersion version = parseFormatVersion(obj);
|
||||
switch (version)
|
||||
{
|
||||
case MetadataVersion::InitialRelease:
|
||||
ptr->merge(parseIndexInternal(obj));
|
||||
break;
|
||||
case MetadataVersion::Invalid:
|
||||
throw ParseException(QObject::tr("Unknown format version!"));
|
||||
}
|
||||
}
|
||||
|
||||
void parseVersionList(const QJsonObject &obj, VersionList *ptr)
|
||||
{
|
||||
const MetadataVersion version = parseFormatVersion(obj);
|
||||
switch (version)
|
||||
{
|
||||
case MetadataVersion::InitialRelease:
|
||||
ptr->merge(parseVersionListInternal(obj));
|
||||
break;
|
||||
case MetadataVersion::Invalid:
|
||||
throw ParseException(QObject::tr("Unknown format version!"));
|
||||
}
|
||||
}
|
||||
|
||||
void parseVersion(const QJsonObject &obj, Version *ptr)
|
||||
{
|
||||
const MetadataVersion version = parseFormatVersion(obj);
|
||||
switch (version)
|
||||
{
|
||||
case MetadataVersion::InitialRelease:
|
||||
ptr->merge(parseVersionInternal(obj));
|
||||
break;
|
||||
case MetadataVersion::Invalid:
|
||||
throw ParseException(QObject::tr("Unknown format version!"));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
[
|
||||
{"uid":"foo", "equals":"version"}
|
||||
]
|
||||
*/
|
||||
void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName)
|
||||
{
|
||||
if(obj.contains(keyName))
|
||||
{
|
||||
QSet<QString> requires;
|
||||
auto reqArray = requireArray(obj, keyName);
|
||||
auto iter = reqArray.begin();
|
||||
while(iter != reqArray.end())
|
||||
{
|
||||
auto reqObject = requireObject(*iter);
|
||||
auto uid = requireString(reqObject, "uid");
|
||||
auto equals = ensureString(reqObject, "equals", QString());
|
||||
auto suggests = ensureString(reqObject, "suggests", QString());
|
||||
ptr->insert({uid, equals, suggests});
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName)
|
||||
{
|
||||
if(!ptr || ptr->empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QJsonArray arrOut;
|
||||
for(auto &iter: *ptr)
|
||||
{
|
||||
QJsonObject reqOut;
|
||||
reqOut.insert("uid", iter.uid);
|
||||
if(!iter.equalsVersion.isEmpty())
|
||||
{
|
||||
reqOut.insert("equals", iter.equalsVersion);
|
||||
}
|
||||
if(!iter.suggests.isEmpty())
|
||||
{
|
||||
reqOut.insert("suggests", iter.suggests);
|
||||
}
|
||||
arrOut.append(reqOut);
|
||||
}
|
||||
obj.insert(keyName, arrOut);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
83
api/logic/meta/JsonFormat.h
Normal file
83
api/logic/meta/JsonFormat.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/* Copyright 2015-2018 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 <QJsonObject>
|
||||
#include <memory>
|
||||
|
||||
#include "Exception.h"
|
||||
#include "meta/BaseEntity.h"
|
||||
#include <set>
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
class Index;
|
||||
class Version;
|
||||
class VersionList;
|
||||
|
||||
enum class MetadataVersion
|
||||
{
|
||||
Invalid = -1,
|
||||
InitialRelease = 1
|
||||
};
|
||||
|
||||
class ParseException : public Exception
|
||||
{
|
||||
public:
|
||||
using Exception::Exception;
|
||||
};
|
||||
struct Require
|
||||
{
|
||||
bool operator==(const Require & rhs) const
|
||||
{
|
||||
return uid == rhs.uid;
|
||||
}
|
||||
bool operator<(const Require & rhs) const
|
||||
{
|
||||
return uid < rhs.uid;
|
||||
}
|
||||
bool deepEquals(const Require & rhs) const
|
||||
{
|
||||
return uid == rhs.uid
|
||||
&& equalsVersion == rhs.equalsVersion
|
||||
&& suggests == rhs.suggests;
|
||||
}
|
||||
QString uid;
|
||||
QString equalsVersion;
|
||||
QString suggests;
|
||||
};
|
||||
|
||||
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
|
||||
{
|
||||
return qHash(key.uid, seed);
|
||||
}
|
||||
|
||||
using RequireSet = std::set<Require>;
|
||||
|
||||
void parseIndex(const QJsonObject &obj, Index *ptr);
|
||||
void parseVersion(const QJsonObject &obj, Version *ptr);
|
||||
void parseVersionList(const QJsonObject &obj, VersionList *ptr);
|
||||
|
||||
MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required = true);
|
||||
void serializeFormatVersion(QJsonObject &obj, MetadataVersion version);
|
||||
|
||||
// FIXME: this has a different shape than the others...FIX IT!?
|
||||
void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires");
|
||||
void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires");
|
||||
MetadataVersion currentFormatVersion();
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::set<Meta::Require>);
|
||||
140
api/logic/meta/Version.cpp
Normal file
140
api/logic/meta/Version.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
/* Copyright 2015-2018 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 "Version.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "JsonFormat.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
|
||||
Meta::Version::Version(const QString &uid, const QString &version)
|
||||
: BaseVersion(), m_uid(uid), m_version(version)
|
||||
{
|
||||
}
|
||||
|
||||
Meta::Version::~Version()
|
||||
{
|
||||
}
|
||||
|
||||
QString Meta::Version::descriptor()
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
QString Meta::Version::name()
|
||||
{
|
||||
if(m_data)
|
||||
return m_data->name;
|
||||
return m_uid;
|
||||
}
|
||||
QString Meta::Version::typeString() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
QDateTime Meta::Version::time() const
|
||||
{
|
||||
return QDateTime::fromMSecsSinceEpoch(m_time * 1000, Qt::UTC);
|
||||
}
|
||||
|
||||
void Meta::Version::parse(const QJsonObject& obj)
|
||||
{
|
||||
parseVersion(obj, this);
|
||||
}
|
||||
|
||||
void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
|
||||
{
|
||||
if(other->m_providesRecommendations)
|
||||
{
|
||||
if(m_recommended != other->m_recommended)
|
||||
{
|
||||
setRecommended(other->m_recommended);
|
||||
}
|
||||
}
|
||||
if (m_type != other->m_type)
|
||||
{
|
||||
setType(other->m_type);
|
||||
}
|
||||
if (m_time != other->m_time)
|
||||
{
|
||||
setTime(other->m_time);
|
||||
}
|
||||
if (m_requires != other->m_requires)
|
||||
{
|
||||
m_requires = other->m_requires;
|
||||
}
|
||||
if (m_conflicts != other->m_conflicts)
|
||||
{
|
||||
m_conflicts = other->m_conflicts;
|
||||
}
|
||||
if(m_volatile != other->m_volatile)
|
||||
{
|
||||
setVolatile(other->m_volatile);
|
||||
}
|
||||
}
|
||||
|
||||
void Meta::Version::merge(const VersionPtr &other)
|
||||
{
|
||||
mergeFromList(other);
|
||||
if(other->m_data)
|
||||
{
|
||||
setData(other->m_data);
|
||||
}
|
||||
}
|
||||
|
||||
QString Meta::Version::localFilename() const
|
||||
{
|
||||
return m_uid + '/' + m_version + ".json";
|
||||
}
|
||||
|
||||
void Meta::Version::setType(const QString &type)
|
||||
{
|
||||
m_type = type;
|
||||
emit typeChanged();
|
||||
}
|
||||
|
||||
void Meta::Version::setTime(const qint64 time)
|
||||
{
|
||||
m_time = time;
|
||||
emit timeChanged();
|
||||
}
|
||||
|
||||
void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
|
||||
{
|
||||
m_requires = requires;
|
||||
m_conflicts = conflicts;
|
||||
emit requiresChanged();
|
||||
}
|
||||
|
||||
void Meta::Version::setVolatile(bool volatile_)
|
||||
{
|
||||
m_volatile = volatile_;
|
||||
}
|
||||
|
||||
|
||||
void Meta::Version::setData(const VersionFilePtr &data)
|
||||
{
|
||||
m_data = data;
|
||||
}
|
||||
|
||||
void Meta::Version::setProvidesRecommendations()
|
||||
{
|
||||
m_providesRecommendations = true;
|
||||
}
|
||||
|
||||
void Meta::Version::setRecommended(bool recommended)
|
||||
{
|
||||
m_recommended = recommended;
|
||||
}
|
||||
118
api/logic/meta/Version.h
Normal file
118
api/logic/meta/Version.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/* Copyright 2015-2018 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 "BaseVersion.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
#include <memory>
|
||||
|
||||
#include "minecraft/VersionFile.h"
|
||||
|
||||
#include "BaseEntity.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "JsonFormat.h"
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Version : public QObject, public BaseVersion, public BaseEntity
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public: /* con/des */
|
||||
explicit Version(const QString &uid, const QString &version);
|
||||
virtual ~Version();
|
||||
|
||||
QString descriptor() override;
|
||||
QString name() override;
|
||||
QString typeString() const override;
|
||||
|
||||
QString uid() const
|
||||
{
|
||||
return m_uid;
|
||||
}
|
||||
QString version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
QString type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
QDateTime time() const;
|
||||
qint64 rawTime() const
|
||||
{
|
||||
return m_time;
|
||||
}
|
||||
const Meta::RequireSet &requires() const
|
||||
{
|
||||
return m_requires;
|
||||
}
|
||||
VersionFilePtr data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
bool isRecommended() const
|
||||
{
|
||||
return m_recommended;
|
||||
}
|
||||
bool isLoaded() const
|
||||
{
|
||||
return m_data != nullptr;
|
||||
}
|
||||
|
||||
void merge(const VersionPtr &other);
|
||||
void mergeFromList(const VersionPtr &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
QString localFilename() const override;
|
||||
|
||||
public: // for usage by format parsers only
|
||||
void setType(const QString &type);
|
||||
void setTime(const qint64 time);
|
||||
void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
|
||||
void setVolatile(bool volatile_);
|
||||
void setRecommended(bool recommended);
|
||||
void setProvidesRecommendations();
|
||||
void setData(const VersionFilePtr &data);
|
||||
|
||||
signals:
|
||||
void typeChanged();
|
||||
void timeChanged();
|
||||
void requiresChanged();
|
||||
|
||||
private:
|
||||
bool m_providesRecommendations = false;
|
||||
bool m_recommended = false;
|
||||
QString m_name;
|
||||
QString m_uid;
|
||||
QString m_version;
|
||||
QString m_type;
|
||||
qint64 m_time = 0;
|
||||
Meta::RequireSet m_requires;
|
||||
Meta::RequireSet m_conflicts;
|
||||
bool m_volatile = false;
|
||||
VersionFilePtr m_data;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(Meta::VersionPtr)
|
||||
245
api/logic/meta/VersionList.cpp
Normal file
245
api/logic/meta/VersionList.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
/* Copyright 2015-2018 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 "VersionList.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "Version.h"
|
||||
#include "JsonFormat.h"
|
||||
#include "Version.h"
|
||||
|
||||
namespace Meta
|
||||
{
|
||||
VersionList::VersionList(const QString &uid, QObject *parent)
|
||||
: BaseVersionList(parent), m_uid(uid)
|
||||
{
|
||||
setObjectName("Version list: " + uid);
|
||||
}
|
||||
|
||||
shared_qobject_ptr<Task> VersionList::getLoadTask()
|
||||
{
|
||||
load(Net::Mode::Online);
|
||||
return getCurrentTask();
|
||||
}
|
||||
|
||||
bool VersionList::isLoaded()
|
||||
{
|
||||
return BaseEntity::isLoaded();
|
||||
}
|
||||
|
||||
const BaseVersionPtr VersionList::at(int i) const
|
||||
{
|
||||
return m_versions.at(i);
|
||||
}
|
||||
int VersionList::count() const
|
||||
{
|
||||
return m_versions.size();
|
||||
}
|
||||
|
||||
void VersionList::sortVersions()
|
||||
{
|
||||
beginResetModel();
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
|
||||
{
|
||||
return *a.get() < *b.get();
|
||||
});
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant VersionList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_versions.size() || index.parent().isValid())
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
VersionPtr version = m_versions.at(index.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case VersionPointerRole: return QVariant::fromValue(std::dynamic_pointer_cast<BaseVersion>(version));
|
||||
case VersionRole:
|
||||
case VersionIdRole:
|
||||
return version->version();
|
||||
case ParentVersionRole:
|
||||
{
|
||||
// FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'.
|
||||
auto & reqs = version->requires();
|
||||
auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
|
||||
{
|
||||
return req.uid == "net.minecraft";
|
||||
});
|
||||
if (iter != reqs.end())
|
||||
{
|
||||
return (*iter).equalsVersion;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
case TypeRole: return version->type();
|
||||
|
||||
case UidRole: return version->uid();
|
||||
case TimeRole: return version->time();
|
||||
case RequiresRole: return QVariant::fromValue(version->requires());
|
||||
case SortRole: return version->rawTime();
|
||||
case VersionPtrRole: return QVariant::fromValue(version);
|
||||
case RecommendedRole: return version->isRecommended();
|
||||
// FIXME: this should be determined in whatever view/proxy is used...
|
||||
// case LatestRole: return version == getLatestStable();
|
||||
default: return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
BaseVersionList::RoleList VersionList::providesRoles() const
|
||||
{
|
||||
return {VersionPointerRole, VersionRole, VersionIdRole, ParentVersionRole,
|
||||
TypeRole, UidRole, TimeRole, RequiresRole, SortRole,
|
||||
RecommendedRole, LatestRole, VersionPtrRole};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> VersionList::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = BaseVersionList::roleNames();
|
||||
roles.insert(UidRole, "uid");
|
||||
roles.insert(TimeRole, "time");
|
||||
roles.insert(SortRole, "sort");
|
||||
roles.insert(RequiresRole, "requires");
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString VersionList::localFilename() const
|
||||
{
|
||||
return m_uid + "/index.json";
|
||||
}
|
||||
|
||||
QString VersionList::humanReadable() const
|
||||
{
|
||||
return m_name.isEmpty() ? m_uid : m_name;
|
||||
}
|
||||
|
||||
VersionPtr VersionList::getVersion(const QString &version)
|
||||
{
|
||||
VersionPtr out = m_lookup.value(version, nullptr);
|
||||
if(!out)
|
||||
{
|
||||
out = std::make_shared<Version>(m_uid, version);
|
||||
m_lookup[version] = out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void VersionList::setName(const QString &name)
|
||||
{
|
||||
m_name = name;
|
||||
emit nameChanged(name);
|
||||
}
|
||||
|
||||
void VersionList::setVersions(const QVector<VersionPtr> &versions)
|
||||
{
|
||||
beginResetModel();
|
||||
m_versions = versions;
|
||||
std::sort(m_versions.begin(), m_versions.end(), [](const VersionPtr &a, const VersionPtr &b)
|
||||
{
|
||||
return a->rawTime() > b->rawTime();
|
||||
});
|
||||
for (int i = 0; i < m_versions.size(); ++i)
|
||||
{
|
||||
m_lookup.insert(m_versions.at(i)->version(), m_versions.at(i));
|
||||
setupAddedVersion(i, m_versions.at(i));
|
||||
}
|
||||
|
||||
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
|
||||
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
|
||||
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void VersionList::parse(const QJsonObject& obj)
|
||||
{
|
||||
parseVersionList(obj, this);
|
||||
}
|
||||
|
||||
// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
|
||||
static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
|
||||
{
|
||||
if(!a)
|
||||
return b;
|
||||
if(!b)
|
||||
return a;
|
||||
if(a->type() == b->type())
|
||||
{
|
||||
// newer of same type wins
|
||||
return (a->rawTime() > b->rawTime() ? a : b);
|
||||
}
|
||||
// 'release' type wins
|
||||
return (a->type() == "release" ? a : b);
|
||||
}
|
||||
|
||||
void VersionList::mergeFromIndex(const VersionListPtr &other)
|
||||
{
|
||||
if (m_name != other->m_name)
|
||||
{
|
||||
setName(other->m_name);
|
||||
}
|
||||
}
|
||||
|
||||
void VersionList::merge(const VersionListPtr &other)
|
||||
{
|
||||
if (m_name != other->m_name)
|
||||
{
|
||||
setName(other->m_name);
|
||||
}
|
||||
|
||||
// TODO: do not reset the whole model. maybe?
|
||||
beginResetModel();
|
||||
m_versions.clear();
|
||||
if(other->m_versions.isEmpty())
|
||||
{
|
||||
qWarning() << "Empty list loaded ...";
|
||||
}
|
||||
for (const VersionPtr &version : other->m_versions)
|
||||
{
|
||||
// we already have the version. merge the contents
|
||||
if (m_lookup.contains(version->version()))
|
||||
{
|
||||
m_lookup.value(version->version())->mergeFromList(version);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lookup.insert(version->uid(), version);
|
||||
}
|
||||
// connect it.
|
||||
setupAddedVersion(m_versions.size(), version);
|
||||
m_versions.append(version);
|
||||
m_recommended = getBetterVersion(m_recommended, version);
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void VersionList::setupAddedVersion(const int row, const VersionPtr &version)
|
||||
{
|
||||
// FIXME: do not disconnect from everythin, disconnect only the lambdas here
|
||||
version->disconnect();
|
||||
connect(version.get(), &Version::requiresChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << RequiresRole); });
|
||||
connect(version.get(), &Version::timeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TimeRole << SortRole); });
|
||||
connect(version.get(), &Version::typeChanged, this, [this, row]() { emit dataChanged(index(row), index(row), QVector<int>() << TypeRole); });
|
||||
}
|
||||
|
||||
BaseVersionPtr VersionList::getRecommended() const
|
||||
{
|
||||
return m_recommended;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2018 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,78 +15,87 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseEntity.h"
|
||||
#include "BaseVersionList.h"
|
||||
#include "BaseWonkoEntity.h"
|
||||
#include <QJsonObject>
|
||||
#include <memory>
|
||||
|
||||
using WonkoVersionPtr = std::shared_ptr<class WonkoVersion>;
|
||||
using WonkoVersionListPtr = std::shared_ptr<class WonkoVersionList>;
|
||||
namespace Meta
|
||||
{
|
||||
using VersionPtr = std::shared_ptr<class Version>;
|
||||
using VersionListPtr = std::shared_ptr<class VersionList>;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT WonkoVersionList : public BaseVersionList, public BaseWonkoEntity
|
||||
class MULTIMC_LOGIC_EXPORT VersionList : public BaseVersionList, public BaseEntity
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString uid READ uid CONSTANT)
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
public:
|
||||
explicit WonkoVersionList(const QString &uid, QObject *parent = nullptr);
|
||||
explicit VersionList(const QString &uid, QObject *parent = nullptr);
|
||||
|
||||
enum Roles
|
||||
{
|
||||
UidRole = Qt::UserRole + 100,
|
||||
TimeRole,
|
||||
RequiresRole,
|
||||
WonkoVersionPtrRole
|
||||
VersionPtrRole
|
||||
};
|
||||
|
||||
Task *getLoadTask() override;
|
||||
shared_qobject_ptr<Task> getLoadTask() override;
|
||||
bool isLoaded() override;
|
||||
const BaseVersionPtr at(int i) const override;
|
||||
int count() const override;
|
||||
void sortVersions() override;
|
||||
|
||||
BaseVersionPtr getLatestStable() const override;
|
||||
BaseVersionPtr getRecommended() const override;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
RoleList providesRoles() const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
std::unique_ptr<Task> remoteUpdateTask() override;
|
||||
std::unique_ptr<Task> localUpdateTask() override;
|
||||
|
||||
QString localFilename() const override;
|
||||
QJsonObject serialized() const override;
|
||||
|
||||
QString uid() const { return m_uid; }
|
||||
QString name() const { return m_name; }
|
||||
QString uid() const
|
||||
{
|
||||
return m_uid;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
QString humanReadable() const;
|
||||
|
||||
bool hasVersion(const QString &version) const;
|
||||
WonkoVersionPtr getVersion(const QString &version) const;
|
||||
VersionPtr getVersion(const QString &version);
|
||||
|
||||
QVector<WonkoVersionPtr> versions() const { return m_versions; }
|
||||
QVector<VersionPtr> versions() const
|
||||
{
|
||||
return m_versions;
|
||||
}
|
||||
|
||||
public: // for usage only by parsers
|
||||
void setName(const QString &name);
|
||||
void setVersions(const QVector<WonkoVersionPtr> &versions);
|
||||
void merge(const BaseWonkoEntity::Ptr &other);
|
||||
void setVersions(const QVector<VersionPtr> &versions);
|
||||
void merge(const VersionListPtr &other);
|
||||
void mergeFromIndex(const VersionListPtr &other);
|
||||
void parse(const QJsonObject &obj) override;
|
||||
|
||||
signals:
|
||||
void nameChanged(const QString &name);
|
||||
|
||||
protected slots:
|
||||
void updateListData(QList<BaseVersionPtr> versions) override {}
|
||||
void updateListData(QList<BaseVersionPtr>) override
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<WonkoVersionPtr> m_versions;
|
||||
QHash<QString, WonkoVersionPtr> m_lookup;
|
||||
QVector<VersionPtr> m_versions;
|
||||
QHash<QString, VersionPtr> m_lookup;
|
||||
QString m_uid;
|
||||
QString m_name;
|
||||
|
||||
WonkoVersionPtr m_recommended;
|
||||
WonkoVersionPtr m_latest;
|
||||
VersionPtr m_recommended;
|
||||
|
||||
void setupAddedVersion(const int row, const WonkoVersionPtr &version);
|
||||
void setupAddedVersion(const int row, const VersionPtr &version);
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(WonkoVersionListPtr)
|
||||
}
|
||||
Q_DECLARE_METATYPE(Meta::VersionListPtr)
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2018 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
439
api/logic/minecraft/Component.cpp
Normal file
439
api/logic/minecraft/Component.cpp
Normal file
@@ -0,0 +1,439 @@
|
||||
#include <meta/VersionList.h>
|
||||
#include <meta/Index.h>
|
||||
#include <Env.h>
|
||||
#include "Component.h"
|
||||
|
||||
#include "meta/Version.h"
|
||||
#include "VersionFile.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include <FileSystem.h>
|
||||
#include <QSaveFile>
|
||||
#include "OneSixVersionFormat.h"
|
||||
#include <assert.h>
|
||||
|
||||
Component::Component(ComponentList * parent, const QString& uid)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_uid = uid;
|
||||
}
|
||||
|
||||
Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_metaVersion = version;
|
||||
m_uid = version->uid();
|
||||
m_version = m_cachedVersion = version->version();
|
||||
m_cachedName = version->name();
|
||||
m_loaded = version->isLoaded();
|
||||
}
|
||||
|
||||
Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
|
||||
{
|
||||
assert(parent);
|
||||
m_parent = parent;
|
||||
|
||||
m_file = file;
|
||||
m_uid = uid;
|
||||
m_cachedVersion = m_file->version;
|
||||
m_cachedName = m_file->name;
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Meta::Version> Component::getMeta()
|
||||
{
|
||||
return m_metaVersion;
|
||||
}
|
||||
|
||||
void Component::applyTo(LaunchProfile* profile)
|
||||
{
|
||||
// do not apply disabled components
|
||||
if(!isEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
vfile->applyTo(profile);
|
||||
}
|
||||
else
|
||||
{
|
||||
profile->applyProblemSeverity(getProblemSeverity());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class VersionFile> Component::getVersionFile() const
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(!m_metaVersion->isLoaded())
|
||||
{
|
||||
m_metaVersion->load(Net::Mode::Online);
|
||||
}
|
||||
return m_metaVersion->data();
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_file;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
|
||||
{
|
||||
// FIXME: what if the metadata index isn't loaded yet?
|
||||
if(ENV.metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
return ENV.metadataIndex()->get(m_uid);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int Component::getOrder()
|
||||
{
|
||||
if(m_orderOverride)
|
||||
return m_order;
|
||||
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
return vfile->order;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void Component::setOrder(int order)
|
||||
{
|
||||
m_orderOverride = true;
|
||||
m_order = order;
|
||||
}
|
||||
QString Component::getID()
|
||||
{
|
||||
return m_uid;
|
||||
}
|
||||
QString Component::getName()
|
||||
{
|
||||
if (!m_cachedName.isEmpty())
|
||||
return m_cachedName;
|
||||
return m_uid;
|
||||
}
|
||||
QString Component::getVersion()
|
||||
{
|
||||
return m_cachedVersion;
|
||||
}
|
||||
QString Component::getFilename()
|
||||
{
|
||||
return m_parent->patchFilePathForUid(m_uid);
|
||||
}
|
||||
QDateTime Component::getReleaseDateTime()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
return m_metaVersion->time();
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(vfile)
|
||||
{
|
||||
return vfile->releaseTime;
|
||||
}
|
||||
// FIXME: fake
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
bool Component::isEnabled()
|
||||
{
|
||||
return !canBeDisabled() || !m_disabled;
|
||||
};
|
||||
|
||||
bool Component::canBeDisabled()
|
||||
{
|
||||
return isRemovable() && !m_dependencyOnly;
|
||||
}
|
||||
|
||||
bool Component::setEnabled(bool state)
|
||||
{
|
||||
bool intendedDisabled = !state;
|
||||
if (!canBeDisabled())
|
||||
{
|
||||
intendedDisabled = false;
|
||||
}
|
||||
if(intendedDisabled != m_disabled)
|
||||
{
|
||||
m_disabled = intendedDisabled;
|
||||
emit dataChanged();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Component::isCustom()
|
||||
{
|
||||
return m_file != nullptr;
|
||||
};
|
||||
|
||||
bool Component::isCustomizable()
|
||||
{
|
||||
if(m_metaVersion)
|
||||
{
|
||||
if(getVersionFile())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Component::isRemovable()
|
||||
{
|
||||
return !m_important;
|
||||
}
|
||||
bool Component::isRevertible()
|
||||
{
|
||||
if (isCustom())
|
||||
{
|
||||
if(ENV.metadataIndex()->hasUid(m_uid))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool Component::isMoveable()
|
||||
{
|
||||
// HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
|
||||
return true;
|
||||
}
|
||||
bool Component::isVersionChangeable()
|
||||
{
|
||||
auto list = getVersionList();
|
||||
if(list)
|
||||
{
|
||||
if(!list->isLoaded())
|
||||
{
|
||||
list->load(Net::Mode::Online);
|
||||
}
|
||||
return list->count() != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Component::setImportant(bool state)
|
||||
{
|
||||
if(m_important != state)
|
||||
{
|
||||
m_important = state;
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
ProblemSeverity Component::getProblemSeverity() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblemSeverity();
|
||||
}
|
||||
return ProblemSeverity::Error;
|
||||
}
|
||||
|
||||
const QList<PatchProblem> Component::getProblems() const
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
return file->getProblems();
|
||||
}
|
||||
return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
|
||||
}
|
||||
|
||||
void Component::setVersion(const QString& version)
|
||||
{
|
||||
if(version == m_version)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_version = version;
|
||||
if(m_loaded)
|
||||
{
|
||||
// we are loaded and potentially have state to invalidate
|
||||
if(m_file)
|
||||
{
|
||||
// we have a file... explicit version has been changed and there is nothing else to do.
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't have a file, therefore we are loaded with metadata
|
||||
m_cachedVersion = version;
|
||||
// see if the meta version is loaded
|
||||
auto metaVersion = ENV.metadataIndex()->get(m_uid, version);
|
||||
if(metaVersion->isLoaded())
|
||||
{
|
||||
// if yes, we can continue with that.
|
||||
m_metaVersion = metaVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not, we need loading
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
updateCachedData();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// not loaded... assume it will be sorted out later by the update task
|
||||
}
|
||||
emit dataChanged();
|
||||
}
|
||||
|
||||
bool Component::customize()
|
||||
{
|
||||
if(isCustom())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto filename = getFilename();
|
||||
if(!FS::ensureFilePathExists(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// FIXME: get rid of this try-catch.
|
||||
try
|
||||
{
|
||||
QSaveFile jsonFile(filename);
|
||||
if(!jsonFile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto vfile = getVersionFile();
|
||||
if(!vfile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto document = OneSixVersionFormat::versionFileToJson(vfile);
|
||||
jsonFile.write(document.toJson());
|
||||
if(!jsonFile.commit())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_file = vfile;
|
||||
m_metaVersion.reset();
|
||||
emit dataChanged();
|
||||
}
|
||||
catch (Exception &error)
|
||||
{
|
||||
qWarning() << "Version could not be loaded:" << error.cause();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Component::revert()
|
||||
{
|
||||
if(!isCustom())
|
||||
{
|
||||
// already not custom
|
||||
return true;
|
||||
}
|
||||
auto filename = getFilename();
|
||||
bool result = true;
|
||||
// just kill the file and reload
|
||||
if(QFile::exists(filename))
|
||||
{
|
||||
result = QFile::remove(filename);
|
||||
}
|
||||
if(result)
|
||||
{
|
||||
// file gone...
|
||||
m_file.reset();
|
||||
|
||||
// check local cache for metadata...
|
||||
auto version = ENV.metadataIndex()->get(m_uid, m_version);
|
||||
if(version->isLoaded())
|
||||
{
|
||||
m_metaVersion = version;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_metaVersion.reset();
|
||||
m_loaded = false;
|
||||
}
|
||||
emit dataChanged();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* deep inspecting compare for requirement sets
|
||||
* By default, only uids are compared for set operations.
|
||||
* This compares all fields of the Require structs in the sets.
|
||||
*/
|
||||
static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
|
||||
{
|
||||
// NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
|
||||
if(a.size() != b.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(const auto & reqA :a)
|
||||
{
|
||||
const auto &iter2 = b.find(reqA);
|
||||
if(iter2 == b.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto & reqB = *iter2;
|
||||
if(!reqA.deepEquals(reqB))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Component::updateCachedData()
|
||||
{
|
||||
auto file = getVersionFile();
|
||||
if(file)
|
||||
{
|
||||
bool changed = false;
|
||||
if(m_cachedName != file->name)
|
||||
{
|
||||
m_cachedName = file->name;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVersion != file->version)
|
||||
{
|
||||
m_cachedVersion = file->version;
|
||||
changed = true;
|
||||
}
|
||||
if(m_cachedVolatile != file->m_volatile)
|
||||
{
|
||||
m_cachedVolatile = file->m_volatile;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedRequires, file->requires))
|
||||
{
|
||||
m_cachedRequires = file->requires;
|
||||
changed = true;
|
||||
}
|
||||
if(!deepCompare(m_cachedConflicts, file->conflicts))
|
||||
{
|
||||
m_cachedConflicts = file->conflicts;
|
||||
changed = true;
|
||||
}
|
||||
if(changed)
|
||||
{
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// in case we removed all the metadata
|
||||
m_cachedRequires.clear();
|
||||
m_cachedConflicts.clear();
|
||||
emit dataChanged();
|
||||
}
|
||||
}
|
||||
111
api/logic/minecraft/Component.h
Normal file
111
api/logic/minecraft/Component.h
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QList>
|
||||
#include <QJsonDocument>
|
||||
#include <QDateTime>
|
||||
#include "meta/JsonFormat.h"
|
||||
#include "ProblemProvider.h"
|
||||
#include "QObjectPtr.h"
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ComponentList;
|
||||
class LaunchProfile;
|
||||
namespace Meta
|
||||
{
|
||||
class Version;
|
||||
class VersionList;
|
||||
}
|
||||
class VersionFile;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Component(ComponentList * parent, const QString &uid);
|
||||
|
||||
// DEPRECATED: remove these constructors?
|
||||
Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
|
||||
Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
|
||||
|
||||
virtual ~Component(){};
|
||||
void applyTo(LaunchProfile *profile);
|
||||
|
||||
bool isEnabled();
|
||||
bool setEnabled (bool state);
|
||||
bool canBeDisabled();
|
||||
|
||||
bool isMoveable();
|
||||
bool isCustomizable();
|
||||
bool isRevertible();
|
||||
bool isRemovable();
|
||||
bool isCustom();
|
||||
bool isVersionChangeable();
|
||||
|
||||
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
|
||||
void setOrder(int order);
|
||||
int getOrder();
|
||||
|
||||
QString getID();
|
||||
QString getName();
|
||||
QString getVersion();
|
||||
std::shared_ptr<Meta::Version> getMeta();
|
||||
QDateTime getReleaseDateTime();
|
||||
|
||||
QString getFilename();
|
||||
|
||||
std::shared_ptr<class VersionFile> getVersionFile() const;
|
||||
std::shared_ptr<class Meta::VersionList> getVersionList() const;
|
||||
|
||||
void setImportant (bool state);
|
||||
|
||||
|
||||
const QList<PatchProblem> getProblems() const override;
|
||||
ProblemSeverity getProblemSeverity() const override;
|
||||
|
||||
void setVersion(const QString & version);
|
||||
bool customize();
|
||||
bool revert();
|
||||
|
||||
void updateCachedData();
|
||||
|
||||
signals:
|
||||
void dataChanged();
|
||||
|
||||
public: /* data */
|
||||
ComponentList * m_parent;
|
||||
|
||||
// BEGIN: persistent component list properties
|
||||
/// ID of the component
|
||||
QString m_uid;
|
||||
/// version of the component - when there's a custom json override, this is also the version the component reverts to
|
||||
QString m_version;
|
||||
/// if true, this has been added automatically to satisfy dependencies and may be automatically removed
|
||||
bool m_dependencyOnly = false;
|
||||
/// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
|
||||
bool m_important = false;
|
||||
/// if true, the component is disabled
|
||||
bool m_disabled = false;
|
||||
|
||||
/// cached name for display purposes, taken from the version file (meta or local override)
|
||||
QString m_cachedName;
|
||||
/// cached version for display AND other purposes, taken from the version file (meta or local override)
|
||||
QString m_cachedVersion;
|
||||
/// cached set of requirements, taken from the version file (meta or local override)
|
||||
Meta::RequireSet m_cachedRequires;
|
||||
Meta::RequireSet m_cachedConflicts;
|
||||
/// if true, the component is volatile and may be automatically removed when no longer needed
|
||||
bool m_cachedVolatile = false;
|
||||
// END: persistent component list properties
|
||||
|
||||
// DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
|
||||
bool m_orderOverride = false;
|
||||
int m_order = 0;
|
||||
|
||||
// load state
|
||||
std::shared_ptr<Meta::Version> m_metaVersion;
|
||||
std::shared_ptr<VersionFile> m_file;
|
||||
bool m_loaded = false;
|
||||
};
|
||||
|
||||
typedef shared_qobject_ptr<Component> ComponentPtr;
|
||||
1204
api/logic/minecraft/ComponentList.cpp
Normal file
1204
api/logic/minecraft/ComponentList.cpp
Normal file
File diff suppressed because it is too large
Load Diff
146
api/logic/minecraft/ComponentList.h
Normal file
146
api/logic/minecraft/ComponentList.h
Normal file
@@ -0,0 +1,146 @@
|
||||
/* Copyright 2013-2018 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 <QAbstractListModel>
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <memory>
|
||||
|
||||
#include "Library.h"
|
||||
#include "LaunchProfile.h"
|
||||
#include "Component.h"
|
||||
#include "ProfileUtils.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "MojangDownloadInfo.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
class MinecraftInstance;
|
||||
struct ComponentListData;
|
||||
class ComponentUpdateTask;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend ComponentUpdateTask;
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn = 0,
|
||||
VersionColumn,
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
explicit ComponentList(MinecraftInstance * instance);
|
||||
virtual ~ComponentList();
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
virtual int columnCount(const QModelIndex &parent) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
/// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
|
||||
void buildingFromScratch();
|
||||
|
||||
/// install more jar mods
|
||||
void installJarMods(QStringList selectedFiles);
|
||||
|
||||
/// install a jar/zip as a replacement for the main jar
|
||||
void installCustomJar(QString selectedFile);
|
||||
|
||||
enum MoveDirection { MoveUp, MoveDown };
|
||||
/// move component file # up or down the list
|
||||
void move(const int index, const MoveDirection direction);
|
||||
|
||||
/// remove component file # - including files/records
|
||||
bool remove(const int index);
|
||||
|
||||
/// remove component file by id - including files/records
|
||||
bool remove(const QString id);
|
||||
|
||||
bool customize(int index);
|
||||
|
||||
bool revertToBase(int index);
|
||||
|
||||
/// reload the list, reload all components, resolve dependencies
|
||||
void reload(Net::Mode netmode);
|
||||
|
||||
// reload all components, resolve dependencies
|
||||
void resolve(Net::Mode netmode);
|
||||
|
||||
/// get current running task...
|
||||
shared_qobject_ptr<Task> getCurrentTask();
|
||||
|
||||
std::shared_ptr<LaunchProfile> getProfile() const;
|
||||
|
||||
// NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
|
||||
void setOldConfigVersion(const QString &uid, const QString &version);
|
||||
|
||||
QString getComponentVersion(const QString &uid) const;
|
||||
|
||||
bool setComponentVersion(const QString &uid, const QString &version, bool important = false);
|
||||
|
||||
bool installEmpty(const QString &uid, const QString &name);
|
||||
|
||||
QString patchFilePathForUid(const QString &uid) const;
|
||||
|
||||
/// if there is a save scheduled, do it now.
|
||||
void saveNow();
|
||||
|
||||
public:
|
||||
/// get the profile component by id
|
||||
Component * getComponent(const QString &id);
|
||||
|
||||
/// get the profile component by index
|
||||
Component * getComponent(int index);
|
||||
|
||||
private:
|
||||
void scheduleSave();
|
||||
bool saveIsScheduled() const;
|
||||
|
||||
/// apply the component patches. Catches all the errors and returns true/false for success/failure
|
||||
void invalidateLaunchProfile();
|
||||
|
||||
/// Add the component to the internal list of patches
|
||||
void appendComponent(ComponentPtr component);
|
||||
/// insert component so that its index is ideally the specified one (returns real index)
|
||||
void insertComponent(size_t index, ComponentPtr component);
|
||||
|
||||
QString componentsFilePath() const;
|
||||
QString patchesPattern() const;
|
||||
|
||||
private slots:
|
||||
void save_internal();
|
||||
void updateSucceeded();
|
||||
void updateFailed(const QString & error);
|
||||
void componentDataChanged();
|
||||
|
||||
private:
|
||||
bool load();
|
||||
bool installJarMods_internal(QStringList filepaths);
|
||||
bool installCustomJar_internal(QString filepath);
|
||||
bool removeComponent_internal(ComponentPtr patch);
|
||||
|
||||
bool migratePreComponentConfig();
|
||||
|
||||
private: /* data */
|
||||
|
||||
std::unique_ptr<ComponentListData> d;
|
||||
};
|
||||
42
api/logic/minecraft/ComponentList_p.h
Normal file
42
api/logic/minecraft/ComponentList_p.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "Component.h"
|
||||
#include <map>
|
||||
#include <QTimer>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
class MinecraftInstance;
|
||||
using ComponentContainer = QList<ComponentPtr>;
|
||||
using ComponentIndex = QMap<QString, ComponentPtr>;
|
||||
using ConnectionList = QList<QMetaObject::Connection>;
|
||||
|
||||
struct ComponentListData
|
||||
{
|
||||
// the instance this belongs to
|
||||
MinecraftInstance *m_instance;
|
||||
|
||||
// the launch profile (volatile, temporary thing created on demand)
|
||||
std::shared_ptr<LaunchProfile> m_profile;
|
||||
|
||||
// version information migrated from instance.cfg file. Single use on migration!
|
||||
std::map<QString, QString> m_oldConfigVersions;
|
||||
QString getOldConfigVersion(const QString& uid) const
|
||||
{
|
||||
const auto iter = m_oldConfigVersions.find(uid);
|
||||
if(iter != m_oldConfigVersions.cend())
|
||||
{
|
||||
return (*iter).second;
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// persistent list of components and related machinery
|
||||
ComponentContainer components;
|
||||
ComponentIndex componentIndex;
|
||||
bool dirty = false;
|
||||
QTimer m_saveTimer;
|
||||
shared_qobject_ptr<Task> m_updateTask;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
691
api/logic/minecraft/ComponentUpdateTask.cpp
Normal file
691
api/logic/minecraft/ComponentUpdateTask.cpp
Normal file
@@ -0,0 +1,691 @@
|
||||
#include "ComponentUpdateTask.h"
|
||||
|
||||
#include "ComponentList_p.h"
|
||||
#include "ComponentList.h"
|
||||
#include "Component.h"
|
||||
#include <Env.h>
|
||||
#include <meta/Index.h>
|
||||
#include <meta/VersionList.h>
|
||||
#include <meta/Version.h>
|
||||
#include "ComponentUpdateTask_p.h"
|
||||
#include <cassert>
|
||||
#include <Version.h>
|
||||
#include "net/Mode.h"
|
||||
#include "OneSixVersionFormat.h"
|
||||
|
||||
/*
|
||||
* This is responsible for loading the components of a component list AND resolving dependency issues between them
|
||||
*/
|
||||
|
||||
/*
|
||||
* FIXME: the 'one shot async task' nature of this does not fit the intended usage
|
||||
* Really, it should be a reactor/state machine that receives input from the application
|
||||
* and dynamically adapts to changing requirements...
|
||||
*
|
||||
* The reactor should be the only entry into manipulating the ComponentList.
|
||||
* See: https://en.wikipedia.org/wiki/Reactor_pattern
|
||||
*/
|
||||
|
||||
/*
|
||||
* Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change?
|
||||
* If the component list changes, start over.
|
||||
*/
|
||||
|
||||
ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent)
|
||||
: Task(parent)
|
||||
{
|
||||
d.reset(new ComponentUpdateTaskData);
|
||||
d->m_list = list;
|
||||
d->mode = mode;
|
||||
d->netmode = netmode;
|
||||
}
|
||||
|
||||
ComponentUpdateTask::~ComponentUpdateTask()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::executeTask()
|
||||
{
|
||||
qDebug() << "Loading components";
|
||||
loadComponents();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class LoadResult
|
||||
{
|
||||
LoadedLocal,
|
||||
RequiresRemote,
|
||||
Failed
|
||||
};
|
||||
|
||||
LoadResult composeLoadResult(LoadResult a, LoadResult b)
|
||||
{
|
||||
if (a < b)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
|
||||
{
|
||||
if(component->m_loaded)
|
||||
{
|
||||
qDebug() << component->getName() << "is already loaded";
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
|
||||
LoadResult result = LoadResult::Failed;
|
||||
auto customPatchFilename = component->getFilename();
|
||||
if(QFile::exists(customPatchFilename))
|
||||
{
|
||||
// if local file exists...
|
||||
|
||||
// check for uid problems inside...
|
||||
bool fileChanged = false;
|
||||
auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false);
|
||||
if(file->uid != component->m_uid)
|
||||
{
|
||||
file->uid = component->m_uid;
|
||||
fileChanged = true;
|
||||
}
|
||||
if(fileChanged)
|
||||
{
|
||||
// FIXME: @QUALITY do not ignore return value
|
||||
ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename);
|
||||
}
|
||||
|
||||
component->m_file = file;
|
||||
component->m_loaded = true;
|
||||
result = LoadResult::LoadedLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version);
|
||||
component->m_metaVersion = metaVersion;
|
||||
if(metaVersion->isLoaded())
|
||||
{
|
||||
component->m_loaded = true;
|
||||
result = LoadResult::LoadedLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
metaVersion->load(netmode);
|
||||
loadTask = metaVersion->getCurrentTask();
|
||||
if(loadTask)
|
||||
result = LoadResult::RequiresRemote;
|
||||
else if (metaVersion->isLoaded())
|
||||
result = LoadResult::LoadedLocal;
|
||||
else
|
||||
result = LoadResult::Failed;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: dead code. determine if this can still be useful?
|
||||
/*
|
||||
static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
|
||||
{
|
||||
if(component->m_loaded)
|
||||
{
|
||||
qDebug() << component->getName() << "is already loaded";
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
|
||||
LoadResult result = LoadResult::Failed;
|
||||
auto metaList = ENV.metadataIndex()->get(component->m_uid);
|
||||
if(metaList->isLoaded())
|
||||
{
|
||||
component->m_loaded = true;
|
||||
result = LoadResult::LoadedLocal;
|
||||
}
|
||||
else
|
||||
{
|
||||
metaList->load(netmode);
|
||||
loadTask = metaList->getCurrentTask();
|
||||
result = LoadResult::RequiresRemote;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
|
||||
static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
|
||||
{
|
||||
// FIXME: DECIDE. do we want to run the update task anyway?
|
||||
if(ENV.metadataIndex()->isLoaded())
|
||||
{
|
||||
qDebug() << "Index is already loaded";
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
ENV.metadataIndex()->load(netmode);
|
||||
loadTask = ENV.metadataIndex()->getCurrentTask();
|
||||
if(loadTask)
|
||||
{
|
||||
return LoadResult::RequiresRemote;
|
||||
}
|
||||
// FIXME: this is assuming the load succeeded... did it really?
|
||||
return LoadResult::LoadedLocal;
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::loadComponents()
|
||||
{
|
||||
LoadResult result = LoadResult::LoadedLocal;
|
||||
size_t taskIndex = 0;
|
||||
size_t componentIndex = 0;
|
||||
d->remoteLoadSuccessful = true;
|
||||
// load the main index (it is needed to determine if components can revert)
|
||||
{
|
||||
// FIXME: tear out as a method? or lambda?
|
||||
shared_qobject_ptr<Task> indexLoadTask;
|
||||
auto singleResult = loadIndex(indexLoadTask, d->netmode);
|
||||
result = composeLoadResult(result, singleResult);
|
||||
if(indexLoadTask)
|
||||
{
|
||||
qDebug() << "Remote loading is being run for metadata index";
|
||||
RemoteLoadStatus status;
|
||||
status.type = RemoteLoadStatus::Type::Index;
|
||||
d->remoteLoadStatusList.append(status);
|
||||
connect(indexLoadTask.get(), &Task::succeeded, [=]()
|
||||
{
|
||||
remoteLoadSucceeded(taskIndex);
|
||||
});
|
||||
connect(indexLoadTask.get(), &Task::failed, [=](const QString & error)
|
||||
{
|
||||
remoteLoadFailed(taskIndex, error);
|
||||
});
|
||||
taskIndex++;
|
||||
}
|
||||
}
|
||||
// load all the components OR their lists...
|
||||
for (auto component: d->m_list->d->components)
|
||||
{
|
||||
shared_qobject_ptr<Task> loadTask;
|
||||
LoadResult singleResult;
|
||||
RemoteLoadStatus::Type loadType;
|
||||
// FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that...
|
||||
#if 0
|
||||
switch(d->mode)
|
||||
{
|
||||
case Mode::Launch:
|
||||
{
|
||||
singleResult = loadComponent(component, loadTask, d->netmode);
|
||||
loadType = RemoteLoadStatus::Type::Version;
|
||||
break;
|
||||
}
|
||||
case Mode::Resolution:
|
||||
{
|
||||
singleResult = loadComponentList(component, loadTask, d->netmode);
|
||||
loadType = RemoteLoadStatus::Type::List;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
singleResult = loadComponent(component, loadTask, d->netmode);
|
||||
loadType = RemoteLoadStatus::Type::Version;
|
||||
#endif
|
||||
if(singleResult == LoadResult::LoadedLocal)
|
||||
{
|
||||
component->updateCachedData();
|
||||
}
|
||||
result = composeLoadResult(result, singleResult);
|
||||
if (loadTask)
|
||||
{
|
||||
qDebug() << "Remote loading is being run for" << component->getName();
|
||||
connect(loadTask.get(), &Task::succeeded, [=]()
|
||||
{
|
||||
remoteLoadSucceeded(taskIndex);
|
||||
});
|
||||
connect(loadTask.get(), &Task::failed, [=](const QString & error)
|
||||
{
|
||||
remoteLoadFailed(taskIndex, error);
|
||||
});
|
||||
RemoteLoadStatus status;
|
||||
status.type = loadType;
|
||||
status.componentListIndex = componentIndex;
|
||||
d->remoteLoadStatusList.append(status);
|
||||
taskIndex++;
|
||||
}
|
||||
componentIndex++;
|
||||
}
|
||||
d->remoteTasksInProgress = taskIndex;
|
||||
switch(result)
|
||||
{
|
||||
case LoadResult::LoadedLocal:
|
||||
{
|
||||
// Everything got loaded. Advance to dependency resolution.
|
||||
resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
|
||||
break;
|
||||
}
|
||||
case LoadResult::RequiresRemote:
|
||||
{
|
||||
// we wait for signals.
|
||||
break;
|
||||
}
|
||||
case LoadResult::Failed:
|
||||
{
|
||||
emitFailed(tr("Some component metadata load tasks failed."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct RequireEx : public Meta::Require
|
||||
{
|
||||
size_t indexOfFirstDependee = 0;
|
||||
};
|
||||
struct RequireCompositionResult
|
||||
{
|
||||
bool ok;
|
||||
RequireEx outcome;
|
||||
};
|
||||
using RequireExSet = std::set<RequireEx>;
|
||||
}
|
||||
|
||||
static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b)
|
||||
{
|
||||
assert(a.uid == b.uid);
|
||||
RequireEx out;
|
||||
out.uid = a.uid;
|
||||
out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee);
|
||||
if(a.equalsVersion.isEmpty())
|
||||
{
|
||||
out.equalsVersion = b.equalsVersion;
|
||||
}
|
||||
else if (b.equalsVersion.isEmpty())
|
||||
{
|
||||
out.equalsVersion = a.equalsVersion;
|
||||
}
|
||||
else if (a.equalsVersion == b.equalsVersion)
|
||||
{
|
||||
out.equalsVersion = a.equalsVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: mark error as explicit version conflict
|
||||
return {false, out};
|
||||
}
|
||||
|
||||
if(a.suggests.isEmpty())
|
||||
{
|
||||
out.suggests = b.suggests;
|
||||
}
|
||||
else if (b.suggests.isEmpty())
|
||||
{
|
||||
out.suggests = a.suggests;
|
||||
}
|
||||
else
|
||||
{
|
||||
Version aVer(a.suggests);
|
||||
Version bVer(b.suggests);
|
||||
out.suggests = (aVer < bVer ? b.suggests : a.suggests);
|
||||
}
|
||||
return {true, out};
|
||||
}
|
||||
|
||||
// gather the requirements from all components, finding any obvious conflicts
|
||||
static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output)
|
||||
{
|
||||
bool succeeded = true;
|
||||
size_t componentNum = 0;
|
||||
for(auto component: input)
|
||||
{
|
||||
auto &componentRequires = component->m_cachedRequires;
|
||||
for(const auto & componentRequire: componentRequires)
|
||||
{
|
||||
auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){
|
||||
return req.uid == componentRequire.uid;
|
||||
});
|
||||
|
||||
RequireEx componenRequireEx;
|
||||
componenRequireEx.uid = componentRequire.uid;
|
||||
componenRequireEx.suggests = componentRequire.suggests;
|
||||
componenRequireEx.equalsVersion = componentRequire.equalsVersion;
|
||||
componenRequireEx.indexOfFirstDependee = componentNum;
|
||||
|
||||
if(found != output.cend())
|
||||
{
|
||||
// found... process it further
|
||||
auto result = composeRequirement(componenRequireEx, *found);
|
||||
if(result.ok)
|
||||
{
|
||||
output.erase(componenRequireEx);
|
||||
output.insert(result.outcome);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical()
|
||||
<< "Conflicting requirements:"
|
||||
<< componentRequire.uid
|
||||
<< "versions:"
|
||||
<< componentRequire.equalsVersion
|
||||
<< ";"
|
||||
<< (*found).equalsVersion;
|
||||
}
|
||||
succeeded &= result.ok;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not found, accumulate
|
||||
output.insert(componenRequireEx);
|
||||
}
|
||||
}
|
||||
componentNum++;
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps)
|
||||
static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove)
|
||||
{
|
||||
for(const auto & component: components)
|
||||
{
|
||||
if(!component->m_dependencyOnly)
|
||||
continue;
|
||||
if(!component->m_cachedVolatile)
|
||||
continue;
|
||||
RequireEx reqNeedle;
|
||||
reqNeedle.uid = component->m_uid;
|
||||
const auto iter = reqs.find(reqNeedle);
|
||||
if(iter == reqs.cend())
|
||||
{
|
||||
toRemove.append(component->m_uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles:
|
||||
* - trivial addition (there is an unmet requirement and it can be trivially met by adding something)
|
||||
* - trivial version conflict of dependencies == explicit version required and installed is different
|
||||
*
|
||||
* toAdd - set of requirements than mean adding a new component
|
||||
* toChange - set of requirements that mean changing version of an existing component
|
||||
*/
|
||||
static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange)
|
||||
{
|
||||
enum class Decision
|
||||
{
|
||||
Undetermined,
|
||||
Met,
|
||||
Missing,
|
||||
VersionNotSame,
|
||||
LockedVersionNotSame
|
||||
} decision = Decision::Undetermined;
|
||||
|
||||
QString reqStr;
|
||||
bool succeeded = true;
|
||||
// list the composed requirements and say if they are met or unmet
|
||||
for(auto & req: input)
|
||||
{
|
||||
do
|
||||
{
|
||||
if(req.equalsVersion.isEmpty())
|
||||
{
|
||||
reqStr = QString("Req: %1").arg(req.uid);
|
||||
if(index.contains(req.uid))
|
||||
{
|
||||
decision = Decision::Met;
|
||||
}
|
||||
else
|
||||
{
|
||||
toAdd.insert(req);
|
||||
decision = Decision::Missing;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion);
|
||||
const auto & compIter = index.find(req.uid);
|
||||
if(compIter == index.cend())
|
||||
{
|
||||
toAdd.insert(req);
|
||||
decision = Decision::Missing;
|
||||
break;
|
||||
}
|
||||
auto & comp = (*compIter);
|
||||
if(comp->getVersion() != req.equalsVersion)
|
||||
{
|
||||
if(comp->m_dependencyOnly)
|
||||
{
|
||||
decision = Decision::VersionNotSame;
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = Decision::LockedVersionNotSame;
|
||||
}
|
||||
break;
|
||||
}
|
||||
decision = Decision::Met;
|
||||
}
|
||||
} while(false);
|
||||
switch(decision)
|
||||
{
|
||||
case Decision::Undetermined:
|
||||
qCritical() << "No decision for" << reqStr;
|
||||
succeeded = false;
|
||||
break;
|
||||
case Decision::Met:
|
||||
qDebug() << reqStr << "Is met.";
|
||||
break;
|
||||
case Decision::Missing:
|
||||
qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
|
||||
toAdd.insert(req);
|
||||
break;
|
||||
case Decision::VersionNotSame:
|
||||
qDebug() << reqStr << "already has different version that can be changed.";
|
||||
toChange.insert(req);
|
||||
break;
|
||||
case Decision::LockedVersionNotSame:
|
||||
qDebug() << reqStr << "already has different version that cannot be changed.";
|
||||
succeeded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
|
||||
// FIXME, TODO: decouple dependency resolution from loading
|
||||
// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses.
|
||||
// FIXME: throw all this away and use a graph
|
||||
void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
{
|
||||
qDebug() << "Resolving dependencies";
|
||||
/*
|
||||
* this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways:
|
||||
* 1. There are conflicting dependencies on the same uid with different exact version numbers
|
||||
* -> hard error
|
||||
* 2. A dependency has non-matching exact version number
|
||||
* -> hard error
|
||||
* 3. A dependency is entirely missing and needs to be injected before the dependee(s)
|
||||
* -> requirements are injected
|
||||
*
|
||||
* NOTE: this is a placeholder and should eventually be replaced with something 'serious'
|
||||
*/
|
||||
auto & components = d->m_list->d->components;
|
||||
auto & componentIndex = d->m_list->d->componentIndex;
|
||||
|
||||
RequireExSet allRequires;
|
||||
QStringList toRemove;
|
||||
do
|
||||
{
|
||||
allRequires.clear();
|
||||
toRemove.clear();
|
||||
if(!gatherRequirementsFromComponents(components, allRequires))
|
||||
{
|
||||
emitFailed(tr("Conflicting requirements detected during dependency checking!"));
|
||||
return;
|
||||
}
|
||||
getTrivialRemovals(components, allRequires, toRemove);
|
||||
if(!toRemove.isEmpty())
|
||||
{
|
||||
qDebug() << "Removing obsolete components...";
|
||||
for(auto & remove : toRemove)
|
||||
{
|
||||
qDebug() << "Removing" << remove;
|
||||
d->m_list->remove(remove);
|
||||
}
|
||||
}
|
||||
} while (!toRemove.isEmpty());
|
||||
RequireExSet toAdd;
|
||||
RequireExSet toChange;
|
||||
bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
|
||||
if(!succeeded)
|
||||
{
|
||||
emitFailed(tr("Instance has conflicting dependencies."));
|
||||
return;
|
||||
}
|
||||
if(checkOnly)
|
||||
{
|
||||
if(toAdd.size() || toChange.size())
|
||||
{
|
||||
emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
|
||||
}
|
||||
else
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool recursionNeeded = false;
|
||||
if(toAdd.size())
|
||||
{
|
||||
// add stuff...
|
||||
for(auto &add: toAdd)
|
||||
{
|
||||
ComponentPtr component = new Component(d->m_list, add.uid);
|
||||
if(!add.equalsVersion.isEmpty())
|
||||
{
|
||||
// exact version
|
||||
qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
|
||||
component->m_version = add.equalsVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
// version needs to be decided
|
||||
qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
|
||||
// ############################################################################################################
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
if(!add.suggests.isEmpty())
|
||||
{
|
||||
component->m_version = add.suggests;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(add.uid == "org.lwjgl")
|
||||
{
|
||||
component->m_version = "2.9.1";
|
||||
}
|
||||
else if (add.uid == "org.lwjgl3")
|
||||
{
|
||||
component->m_version = "3.1.2";
|
||||
}
|
||||
}
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
// ############################################################################################################
|
||||
}
|
||||
component->m_dependencyOnly = true;
|
||||
// FIXME: this should not work directly with the component list
|
||||
d->m_list->insertComponent(add.indexOfFirstDependee, component);
|
||||
componentIndex[add.uid] = component;
|
||||
}
|
||||
recursionNeeded = true;
|
||||
}
|
||||
if(toChange.size())
|
||||
{
|
||||
// change a version of something that exists
|
||||
for(auto &change: toChange)
|
||||
{
|
||||
// FIXME: this should not work directly with the component list
|
||||
qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
|
||||
auto component = componentIndex[change.uid];
|
||||
component->setVersion(change.equalsVersion);
|
||||
}
|
||||
recursionNeeded = true;
|
||||
}
|
||||
|
||||
if(recursionNeeded)
|
||||
{
|
||||
loadComponents();
|
||||
}
|
||||
else
|
||||
{
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
|
||||
{
|
||||
auto &taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if(taskSlot.finished)
|
||||
{
|
||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||
return;
|
||||
}
|
||||
qDebug() << "Remote task" << taskIndex << "succeeded";
|
||||
taskSlot.succeeded = false;
|
||||
taskSlot.finished = true;
|
||||
d->remoteTasksInProgress --;
|
||||
// update the cached data of the component from the downloaded version file.
|
||||
if (taskSlot.type == RemoteLoadStatus::Type::Version)
|
||||
{
|
||||
auto component = d->m_list->getComponent(taskSlot.componentListIndex);
|
||||
component->m_loaded = true;
|
||||
component->updateCachedData();
|
||||
}
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
|
||||
void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
|
||||
{
|
||||
auto &taskSlot = d->remoteLoadStatusList[taskIndex];
|
||||
if(taskSlot.finished)
|
||||
{
|
||||
qWarning() << "Got multiple results from remote load task" << taskIndex;
|
||||
return;
|
||||
}
|
||||
qDebug() << "Remote task" << taskIndex << "failed: " << msg;
|
||||
d->remoteLoadSuccessful = false;
|
||||
taskSlot.succeeded = false;
|
||||
taskSlot.finished = true;
|
||||
taskSlot.error = msg;
|
||||
d->remoteTasksInProgress --;
|
||||
checkIfAllFinished();
|
||||
}
|
||||
|
||||
void ComponentUpdateTask::checkIfAllFinished()
|
||||
{
|
||||
if(d->remoteTasksInProgress)
|
||||
{
|
||||
// not yet...
|
||||
return;
|
||||
}
|
||||
if(d->remoteLoadSuccessful)
|
||||
{
|
||||
// nothing bad happened... clear the temp load status and proceed with looking at dependencies
|
||||
d->remoteLoadStatusList.clear();
|
||||
resolveDependencies(d->mode == Mode::Launch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// remote load failed... report error and bail
|
||||
QStringList allErrorsList;
|
||||
for(auto & item: d->remoteLoadStatusList)
|
||||
{
|
||||
if(!item.succeeded)
|
||||
{
|
||||
allErrorsList.append(item.error);
|
||||
}
|
||||
}
|
||||
auto allErrors = allErrorsList.join("\n");
|
||||
emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors));
|
||||
d->remoteLoadStatusList.clear();
|
||||
}
|
||||
}
|
||||
37
api/logic/minecraft/ComponentUpdateTask.h
Normal file
37
api/logic/minecraft/ComponentUpdateTask.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "net/Mode.h"
|
||||
|
||||
#include <memory>
|
||||
class ComponentList;
|
||||
struct ComponentUpdateTaskData;
|
||||
|
||||
class ComponentUpdateTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Launch,
|
||||
Resolution
|
||||
};
|
||||
|
||||
public:
|
||||
explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
|
||||
virtual ~ComponentUpdateTask();
|
||||
|
||||
protected:
|
||||
void executeTask();
|
||||
|
||||
private:
|
||||
void loadComponents();
|
||||
void resolveDependencies(bool checkOnly);
|
||||
|
||||
void remoteLoadSucceeded(size_t index);
|
||||
void remoteLoadFailed(size_t index, const QString &msg);
|
||||
void checkIfAllFinished();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ComponentUpdateTaskData> d;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user