mirror of
https://github.com/UltimMC/Launcher.git
synced 2025-12-17 17:37:13 +00:00
Compare commits
231 Commits
0.6.1
...
feature/bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19ab1251c2 | ||
|
|
62e1bf327d | ||
|
|
280e0e6e36 | ||
|
|
3a67990acd | ||
|
|
23eab74e6d | ||
|
|
b9d4293552 | ||
|
|
5110b58def | ||
|
|
791a8227b6 | ||
|
|
725ec35635 | ||
|
|
739a86f171 | ||
|
|
48b2f95129 | ||
|
|
497d9bec02 | ||
|
|
c01d020afc | ||
|
|
ee83d432f6 | ||
|
|
8ee11b1a8e | ||
|
|
0b86a7ebf3 | ||
|
|
63330bf111 | ||
|
|
f74e3db804 | ||
|
|
a55fa04353 | ||
|
|
fde43c993e | ||
|
|
917f148fc4 | ||
|
|
34611c00e3 | ||
|
|
44a7c5867b | ||
|
|
75ddbc8851 | ||
|
|
2f1d31cf43 | ||
|
|
e7c5b266c8 | ||
|
|
384979bf94 | ||
|
|
b5a16935b7 | ||
|
|
320637e8dc | ||
|
|
77f3f028fa | ||
|
|
2a96e16902 | ||
|
|
1ed84eddd5 | ||
|
|
7b52b8689b | ||
|
|
d21700ee91 | ||
|
|
f87c890912 | ||
|
|
306b98edac | ||
|
|
ce12f1a734 | ||
|
|
c1953739d9 | ||
|
|
e8bf9cef24 | ||
|
|
8aa4b9dac5 | ||
|
|
4836ba22cd | ||
|
|
6c30076b6c | ||
|
|
83c48a0f1a | ||
|
|
d251097545 | ||
|
|
c35dbd972e | ||
|
|
0c5d121452 | ||
|
|
53ad5cb436 | ||
|
|
d87bd4b588 | ||
|
|
86850ef5d0 | ||
|
|
30fba4d407 | ||
|
|
932160818e | ||
|
|
59e1ed3d87 | ||
|
|
3470a3df96 | ||
|
|
61913daaf3 | ||
|
|
cb71dfa605 | ||
|
|
2be98baaba | ||
|
|
0ce637bf0e | ||
|
|
70ed30f9e6 | ||
|
|
414946cad9 | ||
|
|
1fc199697c | ||
|
|
9292d846b6 | ||
|
|
22db95ce3b | ||
|
|
597fe50d37 | ||
|
|
bf93ba02e9 | ||
|
|
07c1685ff1 | ||
|
|
a5e531d4f1 | ||
|
|
ae956d8fd6 | ||
|
|
73e487a31e | ||
|
|
0b95d2b03f | ||
|
|
9c82adaee5 | ||
|
|
0a99d037c4 | ||
|
|
6e8e4c5dfa | ||
|
|
e4599ee2d1 | ||
|
|
62c9fcdc6c | ||
|
|
c1ea42d3d9 | ||
|
|
437dec91f9 | ||
|
|
7436c94976 | ||
|
|
c08053d8b8 | ||
|
|
e71786d7b9 | ||
|
|
819503d530 | ||
|
|
d6ace374d8 | ||
|
|
6a21c043ce | ||
|
|
4474d269cc | ||
|
|
ec2732ccd1 | ||
|
|
4b7971f60f | ||
|
|
4cbd1a7692 | ||
|
|
f481940aeb | ||
|
|
037fb6fdb7 | ||
|
|
3e60e770b5 | ||
|
|
70052b180c | ||
|
|
2e58429b6a | ||
|
|
56a9b65efb | ||
|
|
4a4ba954ed | ||
|
|
14bb666a20 | ||
|
|
075e173fbd | ||
|
|
3fe9165201 | ||
|
|
13b293dd65 | ||
|
|
de568b32b8 | ||
|
|
fb29e45bd0 | ||
|
|
3018310be3 | ||
|
|
f111d1bc0c | ||
|
|
0ee915200b | ||
|
|
9eb456336d | ||
|
|
3f6aecf5a2 | ||
|
|
e8c382bede | ||
|
|
54e857a7f5 | ||
|
|
74c598d756 | ||
|
|
c214c13fb3 | ||
|
|
c4a472981f | ||
|
|
33a7cc1890 | ||
|
|
a8e77f0ecc | ||
|
|
5603133822 | ||
|
|
47b1f9a860 | ||
|
|
0f0dc30729 | ||
|
|
1648b34aed | ||
|
|
4cc7329ce3 | ||
|
|
16df8d7b88 | ||
|
|
3d630c7856 | ||
|
|
51a6883737 | ||
|
|
d6367407b0 | ||
|
|
defa911705 | ||
|
|
17e09a292d | ||
|
|
8a7f1e405f | ||
|
|
58260da861 | ||
|
|
16cc20aefd | ||
|
|
0572a1e4e6 | ||
|
|
9b74e73ad3 | ||
|
|
24fd2918c7 | ||
|
|
9eb165bfee | ||
|
|
e4ce74e622 | ||
|
|
59e2f52db7 | ||
|
|
d5037d4f79 | ||
|
|
aef0ccb1a2 | ||
|
|
d9c1fc09e7 | ||
|
|
8b56d77f5e | ||
|
|
e3ab393cec | ||
|
|
2c03c67c36 | ||
|
|
a279df8bda | ||
|
|
5f2d3f014a | ||
|
|
6aada8adf7 | ||
|
|
6cee50eac6 | ||
|
|
9cc93ae81d | ||
|
|
0c73ddee73 | ||
|
|
9965decd81 | ||
|
|
9e7cdbfe11 | ||
|
|
76d6ec91a4 | ||
|
|
7b439c85c0 | ||
|
|
12f2716f31 | ||
|
|
4169f53b19 | ||
|
|
e4c33458f2 | ||
|
|
14f85813c8 | ||
|
|
bbb3b3e6f6 | ||
|
|
03280cc62e | ||
|
|
128bce6acb | ||
|
|
8108c61745 | ||
|
|
a222c94d34 | ||
|
|
a872085f9a | ||
|
|
8516a6646e | ||
|
|
44381c09d7 | ||
|
|
bb599abf59 | ||
|
|
07f7ec8eef | ||
|
|
7fe94ca7b4 | ||
|
|
b5f636b3d5 | ||
|
|
19bb50b872 | ||
|
|
4d68c1b509 | ||
|
|
f0ff2db4e1 | ||
|
|
72c0002b45 | ||
|
|
b9fd381eee | ||
|
|
584f1e89b9 | ||
|
|
72ff342d63 | ||
|
|
6284f070c1 | ||
|
|
be9063317e | ||
|
|
8ec36e2cb9 | ||
|
|
8cefc76108 | ||
|
|
172f83c7e2 | ||
|
|
b1e0cbf852 | ||
|
|
9b7f82ff26 | ||
|
|
67cef79d81 | ||
|
|
7e1c5d439a | ||
|
|
38ed0c2a1f | ||
|
|
9b7564e967 | ||
|
|
15926b2b4a | ||
|
|
6323aae56f | ||
|
|
c9c6037f50 | ||
|
|
97b74ef56a | ||
|
|
df6e66101c | ||
|
|
bbd523acb8 | ||
|
|
67d2f283da | ||
|
|
4530d9064b | ||
|
|
3406335cd8 | ||
|
|
c9832d0d86 | ||
|
|
fa8f19b046 | ||
|
|
370bbada87 | ||
|
|
1ef416cb56 | ||
|
|
b46a34d0ae | ||
|
|
6188c577e3 | ||
|
|
40a30b67f4 | ||
|
|
12b304ea73 | ||
|
|
8e44ab2338 | ||
|
|
4c7ea0f99a | ||
|
|
a1c713811c | ||
|
|
106155dd62 | ||
|
|
303842a19e | ||
|
|
ea151ca9d4 | ||
|
|
6e69370fbf | ||
|
|
82208be49e | ||
|
|
b497aee920 | ||
|
|
0812e3a87b | ||
|
|
b8ca36372b | ||
|
|
2d295d5afb | ||
|
|
ab3fe74c97 | ||
|
|
1a43f28297 | ||
|
|
093dd22826 | ||
|
|
06ccff0f47 | ||
|
|
65bca65489 | ||
|
|
a7957f24ba | ||
|
|
2ea22d407d | ||
|
|
22b32fce12 | ||
|
|
2c219df061 | ||
|
|
604295e6d5 | ||
|
|
f259e9f727 | ||
|
|
38e669dbf5 | ||
|
|
ca11765436 | ||
|
|
354ed2e7f0 | ||
|
|
a08afb8172 | ||
|
|
2dac9d02d8 | ||
|
|
b3fb437f8e | ||
|
|
f115bdf5b8 | ||
|
|
41aef8414a | ||
|
|
83649b5d52 | ||
|
|
9583d0d8ee |
@@ -1,4 +1,4 @@
|
||||
UseTab: true
|
||||
UseTab: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
||||
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -9,6 +9,7 @@ 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
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -15,6 +15,8 @@ CMakeLists.txt.user
|
||||
CMakeLists.txt.user.*
|
||||
/.project
|
||||
/.settings
|
||||
/.idea
|
||||
cmake-build-*/
|
||||
|
||||
# Build dirs
|
||||
build
|
||||
|
||||
@@ -32,7 +32,7 @@ set(CMAKE_C_STANDARD_REQUIRED true)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
include(GenerateExportHeader)
|
||||
set(CMAKE_CXX_FLAGS " -Wall -D_GLIBCXX_USE_CXX11_ABI=0 ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
|
||||
if(UNIX AND APPLE)
|
||||
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
|
||||
endif()
|
||||
@@ -41,12 +41,12 @@ 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(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
|
||||
|
||||
######## Set version numbers ########
|
||||
set(MultiMC_VERSION_MAJOR 0)
|
||||
set(MultiMC_VERSION_MINOR 6)
|
||||
set(MultiMC_VERSION_HOTFIX 1)
|
||||
set(MultiMC_VERSION_HOTFIX 6)
|
||||
|
||||
# Build number
|
||||
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
|
||||
@@ -61,10 +61,10 @@ set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
|
||||
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
|
||||
|
||||
# paste.ee API key
|
||||
set(MultiMC_PASTE_EE_API_KEY "" CACHE STRING "API key you can get from paste.ee when you register an account")
|
||||
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 "" CACHE STRING "ID you can get from Google analytics")
|
||||
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)
|
||||
@@ -141,7 +141,7 @@ if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
|
||||
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-2017 MultiMC Contributors")
|
||||
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors")
|
||||
|
||||
# directories to look for dependencies
|
||||
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
29
COPYING.md
29
COPYING.md
@@ -1,6 +1,6 @@
|
||||
# MultiMC
|
||||
|
||||
Copyright 2012-2017 MultiMC Contributors
|
||||
Copyright 2012-2019 MultiMC Contributors
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
@@ -225,3 +225,30 @@
|
||||
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 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="http://i.imgur.com/IOcTf8M.png" alt="MultiMC logo"/>
|
||||
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
|
||||
</p>
|
||||
|
||||
MultiMC 5
|
||||
@@ -27,9 +27,7 @@ We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the
|
||||
|
||||
|
||||
## Translations
|
||||
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5) or using our [translation server](http://translate.multimc.org). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
|
||||
|
||||
Currently, MultiMC is [](http://translate.multimc.org/engage/multimc/?utm_source=widget)
|
||||
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
|
||||
|
||||
## Forking/Redistributing
|
||||
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
|
||||
@@ -40,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
|
||||
|
||||
|
||||
## License
|
||||
Copyright © 2013-2017 MultiMC Contributors
|
||||
Copyright © 2013-2019 MultiMC Contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
|
||||
generate_export_header(MultiMC_gui)
|
||||
|
||||
# Link
|
||||
target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic)
|
||||
qt5_use_modules(MultiMC_gui Gui)
|
||||
target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
@@ -34,4 +34,4 @@ namespace DesktopServices
|
||||
* Open the URL, most likely in a browser. Maybe.
|
||||
*/
|
||||
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -187,8 +187,7 @@ Qt::DropActions IconList::supportedDropActions() const
|
||||
return Qt::CopyAction;
|
||||
}
|
||||
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@@ -261,7 +260,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" && suffix != "svg")
|
||||
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
|
||||
continue;
|
||||
|
||||
if (!QFile::copy(file, target))
|
||||
@@ -269,6 +268,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);
|
||||
@@ -401,4 +411,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-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
|
||||
QIcon getIcon(const QString &key) const;
|
||||
int getIconIndex(const QString &key) const;
|
||||
QString getDirectory() const;
|
||||
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
@@ -55,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;
|
||||
|
||||
@@ -78,7 +80,7 @@ protected slots:
|
||||
void fileChanged(const QString &path);
|
||||
void SettingChanged(const Setting & setting, QVariant value);
|
||||
private:
|
||||
std::shared_ptr<QFileSystemWatcher> m_watcher;
|
||||
shared_qobject_ptr<QFileSystemWatcher> m_watcher;
|
||||
bool is_watching;
|
||||
QMap<QString, int> name_index;
|
||||
QVector<MMCIcon> icons;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -102,3 +102,17 @@ void MMCIcon::replace(IconType new_type, const QString& key)
|
||||
m_images[new_type].filename = QString();
|
||||
m_images[new_type].key = key;
|
||||
}
|
||||
|
||||
QString MMCIcon::getFilePath() const
|
||||
{
|
||||
if(m_current_type == IconType::ToBeDeleted){
|
||||
return QString();
|
||||
}
|
||||
return m_images[m_current_type].filename;
|
||||
}
|
||||
|
||||
|
||||
bool MMCIcon::isBuiltIn() const
|
||||
{
|
||||
return m_current_type == IconType::Builtin;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -46,4 +46,6 @@ struct MULTIMC_GUI_EXPORT MMCIcon
|
||||
void remove(IconType rm_type);
|
||||
void replace(IconType new_type, QIcon icon, QString path = QString());
|
||||
void replace(IconType new_type, const QString &key);
|
||||
bool isBuiltIn() const;
|
||||
QString getFilePath() const;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -102,13 +102,6 @@ void BaseInstance::invalidate()
|
||||
qDebug() << "Instance" << id() << "has been invalidated.";
|
||||
}
|
||||
|
||||
void BaseInstance::nuke()
|
||||
{
|
||||
changeStatus(Status::Gone);
|
||||
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
|
||||
FS::deletePath(instanceRoot());
|
||||
}
|
||||
|
||||
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
|
||||
{
|
||||
Status status = currentStatus();
|
||||
@@ -182,11 +175,6 @@ QString BaseInstance::instanceRoot() const
|
||||
return m_rootDir;
|
||||
}
|
||||
|
||||
InstancePtr BaseInstance::getSharedPtr()
|
||||
{
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
SettingsObjectPtr BaseInstance::settings() const
|
||||
{
|
||||
return m_settings;
|
||||
@@ -214,31 +202,6 @@ void BaseInstance::setLastLaunch(qint64 val)
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupInitial(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_group = val;
|
||||
emit propertiesChanged(this);
|
||||
}
|
||||
|
||||
void BaseInstance::setGroupPost(QString val)
|
||||
{
|
||||
if(m_group == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
setGroupInitial(val);
|
||||
emit groupChanged();
|
||||
}
|
||||
|
||||
QString BaseInstance::group() const
|
||||
{
|
||||
return m_group;
|
||||
}
|
||||
|
||||
void BaseInstance::setNotes(QString val)
|
||||
{
|
||||
//FIXME: if no change, do not set. setting involves saving a file.
|
||||
@@ -276,7 +239,7 @@ QString BaseInstance::name() const
|
||||
|
||||
QString BaseInstance::windowTitle() const
|
||||
{
|
||||
return "MultiMC: " + name();
|
||||
return "MultiMC: " + name().replace(QRegExp("[ \n\r\t]+"), " ");
|
||||
}
|
||||
|
||||
// FIXME: why is this here? move it to MinecraftInstance!!!
|
||||
@@ -285,23 +248,7 @@ QStringList BaseInstance::extraArguments() const
|
||||
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
|
||||
{
|
||||
return m_launchProcess;
|
||||
}
|
||||
|
||||
void BaseInstance::setProvider(BaseInstanceProvider* provider)
|
||||
{
|
||||
// only once.
|
||||
assert(!m_provider);
|
||||
if(m_provider)
|
||||
{
|
||||
qWarning() << "Provider set more than once for instance" << id();
|
||||
}
|
||||
m_provider = provider;
|
||||
}
|
||||
|
||||
BaseInstanceProvider* BaseInstance::provider() const
|
||||
{
|
||||
return m_provider;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,7 +38,6 @@ class QDir;
|
||||
class Task;
|
||||
class LaunchTask;
|
||||
class BaseInstance;
|
||||
class BaseInstanceProvider;
|
||||
|
||||
// pointer for lazy people
|
||||
typedef std::shared_ptr<BaseInstance> InstancePtr;
|
||||
@@ -69,13 +68,8 @@ public:
|
||||
/// virtual destructor to make sure the destruction is COMPLETE
|
||||
virtual ~BaseInstance() {};
|
||||
|
||||
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
|
||||
void nuke();
|
||||
|
||||
/***
|
||||
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
|
||||
* but it has not necessarily been deleted.
|
||||
@@ -93,15 +87,18 @@ public:
|
||||
int64_t totalTimePlayed() const;
|
||||
void resetTimePlayed();
|
||||
|
||||
void setProvider(BaseInstanceProvider * provider);
|
||||
BaseInstanceProvider * provider() const;
|
||||
|
||||
/// get the type of this instance
|
||||
QString instanceType() const;
|
||||
|
||||
/// Path to the instance's root directory.
|
||||
QString instanceRoot() const;
|
||||
|
||||
/// Path to the instance's game root directory.
|
||||
virtual QString gameRoot() const
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
|
||||
QString name() const;
|
||||
void setName(QString val);
|
||||
|
||||
@@ -114,10 +111,6 @@ public:
|
||||
QString notes() const;
|
||||
void setNotes(QString val);
|
||||
|
||||
QString group() const;
|
||||
void setGroupInitial(QString val);
|
||||
void setGroupPost(QString val);
|
||||
|
||||
QString getPreLaunchCommand();
|
||||
QString getPostExitCommand();
|
||||
QString getWrapperCommand();
|
||||
@@ -141,8 +134,6 @@ public:
|
||||
/// Sets the last launched time to 'val' milliseconds since epoch
|
||||
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
|
||||
|
||||
InstancePtr getSharedPtr();
|
||||
|
||||
/*!
|
||||
* \brief Gets this instance's settings object.
|
||||
* This settings object stores instance-specific settings.
|
||||
@@ -154,10 +145,10 @@ public:
|
||||
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;
|
||||
virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
|
||||
|
||||
/// returns the current launch task (if any)
|
||||
std::shared_ptr<LaunchTask> getLaunchTask();
|
||||
shared_qobject_ptr<LaunchTask> getLaunchTask();
|
||||
|
||||
/*!
|
||||
* Create envrironment variables for running the instance
|
||||
@@ -247,12 +238,8 @@ signals:
|
||||
* \brief Signal emitted when properties relevant to the instance view change
|
||||
*/
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
/*!
|
||||
* \brief Signal emitted when groups are affected in any way
|
||||
*/
|
||||
void groupChanged();
|
||||
|
||||
void launchTaskChanged(std::shared_ptr<LaunchTask>);
|
||||
void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
|
||||
|
||||
void runningStatusChanged(bool running);
|
||||
|
||||
@@ -263,13 +250,11 @@ protected slots:
|
||||
|
||||
protected: /* data */
|
||||
QString m_rootDir;
|
||||
QString m_group;
|
||||
SettingsObjectPtr m_settings;
|
||||
// InstanceFlags m_flags;
|
||||
bool m_isRunning = false;
|
||||
std::shared_ptr<LaunchTask> m_launchProcess;
|
||||
shared_qobject_ptr<LaunchTask> m_launchProcess;
|
||||
QDateTime m_timeStarted;
|
||||
BaseInstanceProvider * m_provider = nullptr;
|
||||
|
||||
private: /* data */
|
||||
Status m_status = Status::Present;
|
||||
@@ -278,6 +263,6 @@ private: /* data */
|
||||
bool m_hasBrokenVersion = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
|
||||
Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
|
||||
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
|
||||
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include "BaseInstance.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
using InstanceId = QString;
|
||||
using InstanceLocator = std::pair<InstancePtr, int>;
|
||||
|
||||
enum class InstCreateError
|
||||
{
|
||||
NoCreateError = 0,
|
||||
NoSuchVersion,
|
||||
UnknownCreateError,
|
||||
InstExists,
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
|
||||
{
|
||||
// nil
|
||||
}
|
||||
public:
|
||||
virtual QList<InstanceId> discoverInstances() = 0;
|
||||
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
|
||||
virtual void loadGroupList() = 0;
|
||||
virtual void saveGroupList() = 0;
|
||||
|
||||
virtual QString getStagedInstancePath()
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool destroyStagingPath(const QString & path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
signals:
|
||||
// Emit this when the list of provided instances changed
|
||||
void instancesChanged();
|
||||
// Emit when the set of groups your provider supplies changes.
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
protected:
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -8,21 +8,14 @@ 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
|
||||
MessageLevel.h
|
||||
BaseInstanceProvider.h
|
||||
FolderInstanceProvider.h
|
||||
FolderInstanceProvider.cpp
|
||||
BaseVersion.h
|
||||
BaseInstance.h
|
||||
BaseInstance.cpp
|
||||
@@ -32,6 +25,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 +43,10 @@ set(CORE_SOURCES
|
||||
Env.h
|
||||
Env.cpp
|
||||
|
||||
# String filters
|
||||
Filter.h
|
||||
Filter.cpp
|
||||
|
||||
# JSON parsing helpers
|
||||
Json.h
|
||||
Json.cpp
|
||||
@@ -177,9 +182,11 @@ set(NEWS_SOURCES
|
||||
|
||||
# Icon interface
|
||||
set(ICONS_SOURCES
|
||||
# News System
|
||||
# Icons System and related code
|
||||
icons/IIconList.h
|
||||
icons/IIconList.cpp
|
||||
icons/IconUtils.h
|
||||
icons/IconUtils.cpp
|
||||
)
|
||||
|
||||
# Minecraft services status checker
|
||||
@@ -206,6 +213,8 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/auth/flows/RefreshTask.cpp
|
||||
minecraft/auth/flows/ValidateTask.h
|
||||
minecraft/auth/flows/ValidateTask.cpp
|
||||
minecraft/gameoptions/GameOptions.h
|
||||
minecraft/gameoptions/GameOptions.cpp
|
||||
minecraft/update/AssetUpdateTask.h
|
||||
minecraft/update/AssetUpdateTask.cpp
|
||||
minecraft/update/FMLLibrariesTask.cpp
|
||||
@@ -228,6 +237,8 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/launch/LauncherPartLaunch.h
|
||||
minecraft/launch/PrintInstanceInfo.cpp
|
||||
minecraft/launch/PrintInstanceInfo.h
|
||||
minecraft/launch/ReconstructAssets.cpp
|
||||
minecraft/launch/ReconstructAssets.h
|
||||
minecraft/legacy/LegacyModList.h
|
||||
minecraft/legacy/LegacyModList.cpp
|
||||
minecraft/legacy/LegacyInstance.h
|
||||
@@ -270,19 +281,13 @@ set(MINECRAFT_SOURCES
|
||||
minecraft/VersionFilterData.cpp
|
||||
minecraft/Mod.h
|
||||
minecraft/Mod.cpp
|
||||
minecraft/ModList.h
|
||||
minecraft/ModList.cpp
|
||||
minecraft/SimpleModList.h
|
||||
minecraft/SimpleModList.cpp
|
||||
minecraft/World.h
|
||||
minecraft/World.cpp
|
||||
minecraft/WorldList.h
|
||||
minecraft/WorldList.cpp
|
||||
|
||||
# Flame
|
||||
minecraft/flame/PackManifest.h
|
||||
minecraft/flame/PackManifest.cpp
|
||||
minecraft/flame/FileResolvingTask.h
|
||||
minecraft/flame/FileResolvingTask.cpp
|
||||
|
||||
# Assets
|
||||
minecraft/AssetsUtils.h
|
||||
minecraft/AssetsUtils.cpp
|
||||
@@ -313,8 +318,8 @@ add_unit_test(Library
|
||||
)
|
||||
|
||||
# FIXME: shares data with FileSystem test
|
||||
add_unit_test(ModList
|
||||
SOURCES minecraft/ModList_test.cpp
|
||||
add_unit_test(SimpleModList
|
||||
SOURCES minecraft/SimpleModList_test.cpp
|
||||
DATA testdata
|
||||
LIBS MultiMC_logic
|
||||
)
|
||||
@@ -388,6 +393,8 @@ add_unit_test(JavaVersion
|
||||
set(TRANSLATIONS_SOURCES
|
||||
translations/TranslationsModel.h
|
||||
translations/TranslationsModel.cpp
|
||||
translations/POTranslator.h
|
||||
translations/POTranslator.cpp
|
||||
)
|
||||
|
||||
set(TOOLS_SOURCES
|
||||
@@ -418,6 +425,28 @@ set(META_SOURCES
|
||||
meta/Index.h
|
||||
)
|
||||
|
||||
set(FTB_SOURCES
|
||||
modplatform/ftb/FtbPackFetchTask.h
|
||||
modplatform/ftb/FtbPackFetchTask.cpp
|
||||
modplatform/ftb/FtbPackInstallTask.h
|
||||
modplatform/ftb/FtbPackInstallTask.cpp
|
||||
|
||||
modplatform/ftb/FtbPrivatePackManager.h
|
||||
modplatform/ftb/FtbPrivatePackManager.cpp
|
||||
|
||||
modplatform/ftb/PackHelpers.h
|
||||
)
|
||||
|
||||
set(FLAME_SOURCES
|
||||
# Flame
|
||||
modplatform/flame/PackManifest.h
|
||||
modplatform/flame/PackManifest.cpp
|
||||
modplatform/flame/FileResolvingTask.h
|
||||
modplatform/flame/FileResolvingTask.cpp
|
||||
modplatform/flame/UrlResolvingTask.h
|
||||
modplatform/flame/UrlResolvingTask.cpp
|
||||
)
|
||||
|
||||
add_unit_test(Index
|
||||
SOURCES meta/Index_test.cpp
|
||||
LIBS MultiMC_logic
|
||||
@@ -446,8 +475,12 @@ set(LOGIC_SOURCES
|
||||
${TOOLS_SOURCES}
|
||||
${META_SOURCES}
|
||||
${ICONS_SOURCES}
|
||||
${FTB_SOURCES}
|
||||
${FLAME_SOURCES}
|
||||
)
|
||||
|
||||
message(STATUS "FOO! ${LOGIC_SOURCES}")
|
||||
|
||||
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
|
||||
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
|
||||
|
||||
@@ -455,7 +488,7 @@ generate_export_header(MultiMC_logic)
|
||||
|
||||
# Link
|
||||
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)
|
||||
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
|
||||
|
||||
# Mark and export headers
|
||||
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ struct Env::Private
|
||||
std::shared_ptr<IIconList> m_iconlist;
|
||||
shared_qobject_ptr<Meta::Index> m_metadataIndex;
|
||||
QString m_jarsPath;
|
||||
QSet<QString> m_features;
|
||||
};
|
||||
|
||||
static Env * instance;
|
||||
@@ -95,6 +96,7 @@ 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());
|
||||
@@ -178,3 +180,30 @@ void Env::setJarsPath(const QString& path)
|
||||
{
|
||||
d->m_jarsPath = path;
|
||||
}
|
||||
|
||||
void Env::enableFeature(const QString& featureName, bool state)
|
||||
{
|
||||
if(state)
|
||||
{
|
||||
d->m_features.insert(featureName);
|
||||
}
|
||||
else
|
||||
{
|
||||
d->m_features.remove(featureName);
|
||||
}
|
||||
}
|
||||
|
||||
bool Env::isFeatureEnabled(const QString& featureName) const
|
||||
{
|
||||
return d->m_features.contains(featureName);
|
||||
}
|
||||
|
||||
void Env::getEnabledFeatures(QSet<QString>& features) const
|
||||
{
|
||||
features = d->m_features;
|
||||
}
|
||||
|
||||
void Env::setEnabledFeatures(const QSet<QString>& features) const
|
||||
{
|
||||
d->m_features = features;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ public:
|
||||
|
||||
QString getJarsPath();
|
||||
void setJarsPath(const QString & path);
|
||||
|
||||
bool isFeatureEnabled(const QString & featureName) const;
|
||||
void enableFeature(const QString & featureName, bool state = true);
|
||||
void getEnabledFeatures(QSet<QString> & features) const;
|
||||
void setEnabledFeatures(const QSet<QString> & features) const;
|
||||
|
||||
protected:
|
||||
Private * d;
|
||||
};
|
||||
|
||||
43
api/logic/ExponentialSeries.h
Normal file
43
api/logic/ExponentialSeries.h
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
template <typename T>
|
||||
inline void clamp(T& current, T min, T max)
|
||||
{
|
||||
if (current < min)
|
||||
{
|
||||
current = min;
|
||||
}
|
||||
else if(current > max)
|
||||
{
|
||||
current = max;
|
||||
}
|
||||
}
|
||||
|
||||
// List of numbers from min to max. Next is exponent times bigger than previous.
|
||||
|
||||
class ExponentialSeries
|
||||
{
|
||||
public:
|
||||
ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
|
||||
{
|
||||
m_current = m_min = min;
|
||||
m_max = max;
|
||||
m_exponent = exponent;
|
||||
}
|
||||
void reset()
|
||||
{
|
||||
m_current = m_min;
|
||||
}
|
||||
unsigned operator()()
|
||||
{
|
||||
unsigned retval = m_current;
|
||||
m_current *= m_exponent;
|
||||
clamp(m_current, m_min, m_max);
|
||||
return retval;
|
||||
}
|
||||
unsigned m_current;
|
||||
unsigned m_min;
|
||||
unsigned m_max;
|
||||
unsigned m_exponent;
|
||||
};
|
||||
@@ -3,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;
|
||||
@@ -291,7 +294,7 @@ QString NormalizePath(QString path)
|
||||
}
|
||||
}
|
||||
|
||||
QString badFilenameChars = "\"\\/?<>:*|!";
|
||||
QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
|
||||
|
||||
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
|
||||
{
|
||||
@@ -337,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)
|
||||
@@ -365,7 +356,7 @@ HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
|
||||
|
||||
if (!SUCCEEDED(hres))
|
||||
{
|
||||
qWarning("Failed to initialize COM. Error 0x%08X", hres);
|
||||
qWarning("Failed to initialize COM. Error 0x%08lX", hres);
|
||||
return hres;
|
||||
}
|
||||
}
|
||||
|
||||
31
api/logic/Filter.cpp
Normal file
31
api/logic/Filter.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "Filter.h"
|
||||
|
||||
Filter::~Filter(){}
|
||||
|
||||
ContainsFilter::ContainsFilter(const QString& pattern) : pattern(pattern){}
|
||||
ContainsFilter::~ContainsFilter(){}
|
||||
bool ContainsFilter::accepts(const QString& value)
|
||||
{
|
||||
return value.contains(pattern);
|
||||
}
|
||||
|
||||
ExactFilter::ExactFilter(const QString& pattern) : pattern(pattern){}
|
||||
ExactFilter::~ExactFilter(){}
|
||||
bool ExactFilter::accepts(const QString& value)
|
||||
{
|
||||
return value == pattern;
|
||||
}
|
||||
|
||||
RegexpFilter::RegexpFilter(const QString& regexp, bool invert)
|
||||
:invert(invert)
|
||||
{
|
||||
pattern.setPattern(regexp);
|
||||
pattern.optimize();
|
||||
}
|
||||
RegexpFilter::~RegexpFilter(){}
|
||||
bool RegexpFilter::accepts(const QString& value)
|
||||
{
|
||||
auto match = pattern.match(value);
|
||||
bool matched = match.hasMatch();
|
||||
return invert ? (!matched) : (matched);
|
||||
}
|
||||
44
api/logic/Filter.h
Normal file
44
api/logic/Filter.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT Filter
|
||||
{
|
||||
public:
|
||||
virtual ~Filter();
|
||||
virtual bool accepts(const QString & value) = 0;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ContainsFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ContainsFilter(const QString &pattern);
|
||||
virtual ~ContainsFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT ExactFilter: public Filter
|
||||
{
|
||||
public:
|
||||
ExactFilter(const QString &pattern);
|
||||
virtual ~ExactFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QString pattern;
|
||||
};
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT RegexpFilter: public Filter
|
||||
{
|
||||
public:
|
||||
RegexpFilter(const QString ®exp, bool invert);
|
||||
virtual ~RegexpFilter();
|
||||
bool accepts(const QString & value) override;
|
||||
private:
|
||||
QRegularExpression pattern;
|
||||
bool invert = false;
|
||||
};
|
||||
@@ -1,487 +0,0 @@
|
||||
#include "FolderInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "NullInstance.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUuid>
|
||||
#include <QTimer>
|
||||
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
struct WatchLock
|
||||
{
|
||||
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
|
||||
: m_watcher(watcher), m_instDir(instDir)
|
||||
{
|
||||
m_watcher->removePath(m_instDir);
|
||||
}
|
||||
~WatchLock()
|
||||
{
|
||||
m_watcher->addPath(m_instDir);
|
||||
}
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QString m_instDir;
|
||||
};
|
||||
|
||||
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
|
||||
: BaseInstanceProvider(settings)
|
||||
{
|
||||
// Create aand normalize path
|
||||
if (!QDir::current().exists(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);
|
||||
}
|
||||
|
||||
QList< InstanceId > FolderInstanceProvider::discoverInstances()
|
||||
{
|
||||
QList<InstanceId> out;
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
QFileInfo dirInfo(subDir);
|
||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
// if it is a symlink, ignore it if it goes to the instance folder
|
||||
if(dirInfo.isSymLink())
|
||||
{
|
||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||
QFileInfo instDirInfo(m_instDir);
|
||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
||||
{
|
||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto id = dirInfo.fileName();
|
||||
out.append(id);
|
||||
qDebug() << "Found instance ID" << id;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
|
||||
{
|
||||
if(!m_groupsLoaded)
|
||||
{
|
||||
loadGroupList();
|
||||
}
|
||||
|
||||
auto instanceRoot = FS::PathCombine(m_instDir, id);
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
|
||||
InstancePtr inst;
|
||||
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
QString inst_type = instanceSettings->get("InstanceType").toString();
|
||||
|
||||
if (inst_type == "OneSix" || inst_type == "Nostalgia")
|
||||
{
|
||||
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else if (inst_type == "Legacy")
|
||||
{
|
||||
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
inst->init();
|
||||
inst->setProvider(this);
|
||||
auto iter = groupMap.find(id);
|
||||
if (iter != groupMap.end())
|
||||
{
|
||||
inst->setGroupInitial((*iter));
|
||||
}
|
||||
connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged);
|
||||
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
|
||||
return inst;
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::saveGroupList()
|
||||
{
|
||||
WatchLock foo(m_watcher, m_instDir);
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
|
||||
{
|
||||
QString id = iter.key();
|
||||
QString group = iter.value();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
|
||||
if (!reverseGroupMap.count(group))
|
||||
{
|
||||
QSet<QString> set;
|
||||
set.insert(id);
|
||||
reverseGroupMap[group] = set;
|
||||
}
|
||||
else
|
||||
{
|
||||
QSet<QString> &set = reverseGroupMap[group];
|
||||
set.insert(id);
|
||||
}
|
||||
}
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||
QJsonObject groupsArr;
|
||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
||||
{
|
||||
auto list = iter.value();
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
}
|
||||
groupObj.insert("instances", instanceArr);
|
||||
groupsArr.insert(name, groupObj);
|
||||
}
|
||||
toplevel.insert("groups", groupsArr);
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
{
|
||||
FS::write(groupFileName, doc.toJson());
|
||||
}
|
||||
catch(FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::loadGroupList()
|
||||
{
|
||||
QSet<QString> groupSet;
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if (!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QByteArray jsonData;
|
||||
try
|
||||
{
|
||||
jsonData = FS::read(groupFileName);
|
||||
}
|
||||
catch (FS::FileSystemException & e)
|
||||
{
|
||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
// if the json was bad, fail
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||
.arg(error.errorString(), QString::number(error.offset))
|
||||
.toUtf8();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the root of the json wasn't an object, fail
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches, otherwise fail.
|
||||
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
|
||||
return;
|
||||
|
||||
// Get the groups. if it's not an object, fail
|
||||
if (!rootObj.value("groups").isObject())
|
||||
{
|
||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
groupMap.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
||||
{
|
||||
QString groupName = iter.key();
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list should "
|
||||
"be an object.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
|
||||
iter2++)
|
||||
{
|
||||
groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
emit groupsChanged(groupSet);
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::groupChanged()
|
||||
{
|
||||
// save the groups. save all of them.
|
||||
auto instance = (BaseInstance *) QObject::sender();
|
||||
auto id = instance->id();
|
||||
groupMap[id] = instance->group();
|
||||
emit groupsChanged({instance->group()});
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
|
||||
void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
|
||||
{
|
||||
Q_UNUSED(path);
|
||||
emit instancesChanged();
|
||||
}
|
||||
|
||||
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||
if(newInstDir != m_instDir)
|
||||
{
|
||||
if(m_groupsLoaded)
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
m_instDir = newInstDir;
|
||||
m_groupsLoaded = false;
|
||||
emit instancesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
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 "InstanceImportTask.h"
|
||||
Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
auto task = new InstanceImportTask(m_globalSettings, sourceUrl, stagingPath, instName, instIcon, instGroup);
|
||||
return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
|
||||
}
|
||||
|
||||
#include "InstanceCreationTask.h"
|
||||
Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
auto task = new InstanceCreationTask(m_globalSettings, stagingPath, version, instName, instIcon, instGroup);
|
||||
return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
|
||||
}
|
||||
|
||||
#include "InstanceCopyTask.h"
|
||||
Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
auto task = new InstanceCopyTask(m_globalSettings, stagingPath, oldInstance, instName, instIcon, instGroup, copySaves);
|
||||
return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
|
||||
}
|
||||
|
||||
// FIXME: find a better place for this
|
||||
#include "minecraft/legacy/LegacyUpgradeTask.h"
|
||||
Task * FolderInstanceProvider::legacyUpgradeTask(const InstancePtr& oldInstance)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
QString newName = tr("%1 (Migrated)").arg(oldInstance->name());
|
||||
auto task = new LegacyUpgradeTask(m_globalSettings, stagingPath, oldInstance, newName);
|
||||
return new FolderInstanceStaging(this, task, stagingPath, newName, oldInstance->group());
|
||||
}
|
||||
|
||||
QString FolderInstanceProvider::getStagedInstancePath()
|
||||
{
|
||||
QString key = QUuid::createUuid().toString();
|
||||
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
|
||||
QDir rootPath(m_instDir);
|
||||
auto path = FS::PathCombine(m_instDir, relPath);
|
||||
if(!rootPath.mkpath(relPath))
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
|
||||
{
|
||||
QDir dir;
|
||||
QString instID = FS::DirNameFromString(instanceName, m_instDir);
|
||||
{
|
||||
WatchLock lock(m_watcher, m_instDir);
|
||||
QString destination = FS::PathCombine(m_instDir, instID);
|
||||
if(!dir.rename(path, destination))
|
||||
{
|
||||
qWarning() << "Failed to move" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
groupMap[instID] = groupName;
|
||||
emit groupsChanged({groupName});
|
||||
emit instancesChanged();
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
|
||||
{
|
||||
return FS::deletePath(keyPath);
|
||||
}
|
||||
|
||||
#include "FolderInstanceProvider.moc"
|
||||
@@ -1,66 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include <QMap>
|
||||
|
||||
class QFileSystemWatcher;
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
|
||||
|
||||
public:
|
||||
/// used by InstanceList to @return a list of plausible IDs to probe for
|
||||
QList<InstanceId> discoverInstances() override;
|
||||
|
||||
/// used by InstanceList to (re)load an instance with the given @id.
|
||||
InstancePtr loadInstance(const InstanceId& id) override;
|
||||
|
||||
|
||||
// create instance in this provider
|
||||
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
// copy instance to this provider
|
||||
Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves);
|
||||
|
||||
// import zipped instance into this provider
|
||||
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
|
||||
|
||||
// migrate an instance to the current format
|
||||
Task * legacyUpgradeTask(const InstancePtr& oldInstance);
|
||||
|
||||
/**
|
||||
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
QString getStagedInstancePath() override;
|
||||
/**
|
||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override;
|
||||
/**
|
||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool destroyStagingPath(const QString & keyPath) override;
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
private slots:
|
||||
void instanceDirContentsChanged(const QString &path);
|
||||
void groupChanged();
|
||||
|
||||
private: /* methods */
|
||||
void loadGroupList() override;
|
||||
void saveGroupList() override;
|
||||
|
||||
private: /* data */
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QMap<QString, QString> groupMap;
|
||||
bool m_groupsLoaded = false;
|
||||
};
|
||||
@@ -1,19 +1,13 @@
|
||||
#include "InstanceCopyTask.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
#include "NullInstance.h"
|
||||
#include "pathmatcher/RegexpMatcher.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
|
||||
InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_stagingPath = stagingPath;
|
||||
m_origInstance = origInstance;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
|
||||
if(!copySaves)
|
||||
{
|
||||
|
||||
@@ -9,16 +9,13 @@
|
||||
#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, const QString & stagingPath, 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,15 +24,8 @@ protected:
|
||||
void copyAborted();
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "InstanceCreationTask.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "FileSystem.h"
|
||||
|
||||
@@ -7,14 +6,8 @@
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
|
||||
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version,
|
||||
const QString& instName, const QString& instIcon, const QString& instGroup)
|
||||
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_stagingPath = stagingPath;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
m_version = version;
|
||||
}
|
||||
|
||||
@@ -32,7 +25,6 @@ void InstanceCreationTask::executeTask()
|
||||
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
|
||||
inst.setName(m_instName);
|
||||
inst.setIconKey(m_instIcon);
|
||||
inst.init();
|
||||
instanceSettings->resumeSave();
|
||||
}
|
||||
emitSucceeded();
|
||||
|
||||
@@ -6,24 +6,18 @@
|
||||
#include <QUrl>
|
||||
#include "settings/SettingsObject.h"
|
||||
#include "BaseVersion.h"
|
||||
#include "InstanceTask.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
|
||||
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, 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;
|
||||
QString m_stagingPath;
|
||||
BaseVersionPtr m_version;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
#include "InstanceImportTask.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
#include "FileSystem.h"
|
||||
#include "Env.h"
|
||||
#include "MMCZip.h"
|
||||
#include "NullInstance.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "icons/IIconList.h"
|
||||
#include "icons/IconUtils.h"
|
||||
#include <QtConcurrentRun>
|
||||
|
||||
// FIXME: this does not belong here, it's Minecraft/Flame specific
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "minecraft/ComponentList.h"
|
||||
#include "minecraft/flame/FileResolvingTask.h"
|
||||
#include "minecraft/flame/PackManifest.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "modplatform/flame/PackManifest.h"
|
||||
#include "Json.h"
|
||||
|
||||
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, const QString & stagingPath,
|
||||
const QString &instName, const QString &instIcon, const QString &instGroup)
|
||||
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
m_sourceUrl = sourceUrl;
|
||||
m_stagingPath = stagingPath;
|
||||
m_instName = instName;
|
||||
m_instIcon = instIcon;
|
||||
m_instGroup = instGroup;
|
||||
}
|
||||
|
||||
void InstanceImportTask::executeTask()
|
||||
@@ -194,7 +188,7 @@ void InstanceImportTask::processFlame()
|
||||
Flame::loadManifest(pack, configPath);
|
||||
QFile::remove(configPath);
|
||||
}
|
||||
catch (JSONValidationError & e)
|
||||
catch (const JSONValidationError &e)
|
||||
{
|
||||
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
|
||||
return;
|
||||
@@ -281,7 +275,6 @@ void InstanceImportTask::processFlame()
|
||||
instance.setIconKey("flame");
|
||||
}
|
||||
}
|
||||
instance.init();
|
||||
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
|
||||
QFileInfo jarmodsInfo(jarmodsPath);
|
||||
if(jarmodsInfo.isDir())
|
||||
@@ -401,8 +394,9 @@ void InstanceImportTask::processMultiMC()
|
||||
else
|
||||
{
|
||||
m_instIcon = instance.iconKey();
|
||||
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
|
||||
if (QFile::exists(importIconPath))
|
||||
|
||||
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
|
||||
if (!importIconPath.isNull() && QFile::exists(importIconPath))
|
||||
{
|
||||
// import icon
|
||||
auto iconList = ENV.icons();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "InstanceTask.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "net/NetJob.h"
|
||||
#include <QUrl>
|
||||
@@ -10,18 +10,16 @@
|
||||
#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, const QString & stagingPath, const QString &instName,
|
||||
const QString &instIcon, const QString &instGroup);
|
||||
explicit InstanceImportTask(const QUrl sourceUrl);
|
||||
|
||||
protected:
|
||||
//! Entry point for tasks.
|
||||
@@ -40,16 +38,11 @@ private slots:
|
||||
void extractAborted();
|
||||
|
||||
private: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
NetJobPtr m_filesNetJob;
|
||||
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
|
||||
QUrl m_sourceUrl;
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,22 +14,49 @@
|
||||
*/
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QSet>
|
||||
#include <QFile>
|
||||
#include <QThread>
|
||||
#include <QTextStream>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QUuid>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "InstanceList.h"
|
||||
#include "BaseInstance.h"
|
||||
#include "InstanceTask.h"
|
||||
#include "settings/INISettingsObject.h"
|
||||
#include "minecraft/legacy/LegacyInstance.h"
|
||||
#include "NullInstance.h"
|
||||
#include "minecraft/MinecraftInstance.h"
|
||||
#include "FileSystem.h"
|
||||
#include "ExponentialSeries.h"
|
||||
#include "WatchLock.h"
|
||||
|
||||
#include "FolderInstanceProvider.h"
|
||||
const static int GROUP_FILE_FORMAT_VERSION = 1;
|
||||
|
||||
InstanceList::InstanceList(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent)
|
||||
: QAbstractListModel(parent), m_globalSettings(settings)
|
||||
{
|
||||
resumeWatch();
|
||||
// Create aand normalize path
|
||||
if (!QDir::current().exists(instDir))
|
||||
{
|
||||
QDir::current().mkpath(instDir);
|
||||
}
|
||||
|
||||
connect(this, &InstanceList::instancesChanged, this, &InstanceList::providerUpdated);
|
||||
|
||||
// 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, &InstanceList::instanceDirContentsChanged);
|
||||
m_watcher->addPath(m_instDir);
|
||||
}
|
||||
|
||||
InstanceList::~InstanceList()
|
||||
@@ -68,6 +95,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
return pdata->id();
|
||||
}
|
||||
case Qt::EditRole:
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
return pdata->name();
|
||||
@@ -83,7 +111,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
// HACK: see GroupView.h in gui!
|
||||
case GroupRole:
|
||||
{
|
||||
return pdata->group();
|
||||
return getInstanceGroup(pdata->id());
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@@ -91,16 +119,85 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (!index.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(role != Qt::EditRole)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
|
||||
auto newName = value.toString();
|
||||
if(pdata->name() == newName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
pdata->setName(newName);
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags f;
|
||||
if (index.isValid())
|
||||
{
|
||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
||||
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
|
||||
{
|
||||
auto inst = getInstanceById(id);
|
||||
if(!inst)
|
||||
{
|
||||
return GroupId();
|
||||
}
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
{
|
||||
return *iter;
|
||||
}
|
||||
return GroupId();
|
||||
}
|
||||
|
||||
void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
|
||||
{
|
||||
auto inst = getInstanceById(id);
|
||||
if(!inst)
|
||||
{
|
||||
qDebug() << "Attempt to set a null instance's group";
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
auto iter = m_groupMap.find(inst->id());
|
||||
if(iter != m_groupMap.end())
|
||||
{
|
||||
if(*iter != name)
|
||||
{
|
||||
*iter = name;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
changed = true;
|
||||
m_groupMap[id] = name;
|
||||
}
|
||||
|
||||
if(changed)
|
||||
{
|
||||
m_groups.insert(name);
|
||||
auto idx = getInstIndex(inst.get());
|
||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
||||
saveGroupList();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList InstanceList::getGroups()
|
||||
{
|
||||
return m_groups.toList();
|
||||
@@ -108,15 +205,53 @@ QStringList InstanceList::getGroups()
|
||||
|
||||
void InstanceList::deleteGroup(const QString& name)
|
||||
{
|
||||
bool removed = false;
|
||||
qDebug() << "Delete group" << name;
|
||||
for(auto & instance: m_instances)
|
||||
{
|
||||
auto instGroupName = instance->group();
|
||||
const auto & instID = instance->id();
|
||||
auto instGroupName = getInstanceGroup(instID);
|
||||
if(instGroupName == name)
|
||||
{
|
||||
instance->setGroupPost(QString());
|
||||
m_groupMap.remove(instID);
|
||||
qDebug() << "Remove" << instID << "from group" << name;
|
||||
removed = true;
|
||||
auto idx = getInstIndex(instance.get());
|
||||
if(idx > 0)
|
||||
{
|
||||
emit dataChanged(index(idx), index(idx), {GroupRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
if(removed)
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::deleteInstance(const InstanceId& id)
|
||||
{
|
||||
auto inst = getInstanceById(id);
|
||||
if(!inst)
|
||||
{
|
||||
qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_groupMap.remove(id))
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
|
||||
qDebug() << "Will delete instance" << id;
|
||||
if(!FS::deletePath(inst->instanceRoot()))
|
||||
{
|
||||
qWarning() << "Deletion of instance" << id << "has not been completely successful ...";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Instance" << id << "has been deleted by MultiMC.";
|
||||
}
|
||||
|
||||
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
|
||||
{
|
||||
@@ -135,50 +270,60 @@ static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &
|
||||
return out;
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
QList< InstanceId > InstanceList::discoverInstances()
|
||||
{
|
||||
qDebug() << "Discovering instances in" << m_instDir;
|
||||
QList<InstanceId> out;
|
||||
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString subDir = iter.next();
|
||||
QFileInfo dirInfo(subDir);
|
||||
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
|
||||
continue;
|
||||
// if it is a symlink, ignore it if it goes to the instance folder
|
||||
if(dirInfo.isSymLink())
|
||||
{
|
||||
QFileInfo targetInfo(dirInfo.symLinkTarget());
|
||||
QFileInfo instDirInfo(m_instDir);
|
||||
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
|
||||
{
|
||||
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto id = dirInfo.fileName();
|
||||
out.append(id);
|
||||
qDebug() << "Found instance ID" << id;
|
||||
}
|
||||
instanceSet = out.toSet();
|
||||
m_instancesProbed = true;
|
||||
return out;
|
||||
}
|
||||
|
||||
InstanceList::InstListError InstanceList::loadList()
|
||||
{
|
||||
auto existingIds = getIdMapping(m_instances);
|
||||
|
||||
QList<InstancePtr> newList;
|
||||
|
||||
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
|
||||
{
|
||||
for(auto & id: ids)
|
||||
for(auto & id: discoverInstances())
|
||||
{
|
||||
if(existingIds.contains(id))
|
||||
{
|
||||
auto instPair = existingIds[id];
|
||||
/*
|
||||
auto & instPtr = instPair.first;
|
||||
auto & instIdx = instPair.second;
|
||||
*/
|
||||
existingIds.remove(id);
|
||||
qDebug() << "Should keep and soft-reload" << id;
|
||||
}
|
||||
else
|
||||
{
|
||||
InstancePtr instPtr = provider->loadInstance(id);
|
||||
InstancePtr instPtr = loadInstance(id);
|
||||
if(instPtr)
|
||||
{
|
||||
newList.append(instPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
if(complete)
|
||||
{
|
||||
for(auto & item: m_providers)
|
||||
{
|
||||
processIds(item.get(), item->discoverInstances());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto & item: m_updatedProviders)
|
||||
{
|
||||
processIds(item, item->discoverInstances());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
|
||||
if(!existingIds.isEmpty())
|
||||
@@ -205,10 +350,6 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
for(auto & removedItem: deadList)
|
||||
{
|
||||
auto instPtr = removedItem.first;
|
||||
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
instPtr->invalidate();
|
||||
currentItem = removedItem.second;
|
||||
if(back_bookmark == -1)
|
||||
@@ -236,7 +377,7 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
|
||||
{
|
||||
add(newList);
|
||||
}
|
||||
m_updatedProviders.clear();
|
||||
m_dirty = false;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
@@ -267,7 +408,7 @@ void InstanceList::resumeWatch()
|
||||
return;
|
||||
}
|
||||
m_watchLevel++;
|
||||
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
|
||||
if(m_watchLevel > 0 && m_dirty)
|
||||
{
|
||||
loadList();
|
||||
}
|
||||
@@ -280,31 +421,13 @@ void InstanceList::suspendWatch()
|
||||
|
||||
void InstanceList::providerUpdated()
|
||||
{
|
||||
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
|
||||
if(!provider)
|
||||
{
|
||||
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
|
||||
return;
|
||||
}
|
||||
m_updatedProviders.insert(provider);
|
||||
m_dirty = true;
|
||||
if(m_watchLevel == 1)
|
||||
{
|
||||
loadList();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::groupsPublished(QSet<QString> newGroups)
|
||||
{
|
||||
m_groups.unite(newGroups);
|
||||
}
|
||||
|
||||
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
|
||||
{
|
||||
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
|
||||
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
|
||||
m_providers.append(provider);
|
||||
}
|
||||
|
||||
InstancePtr InstanceList::getInstanceById(QString instId) const
|
||||
{
|
||||
if(instId.isEmpty())
|
||||
@@ -345,3 +468,366 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
|
||||
emit dataChanged(index(i), index(i));
|
||||
}
|
||||
}
|
||||
|
||||
InstancePtr InstanceList::loadInstance(const InstanceId& id)
|
||||
{
|
||||
if(!m_groupsLoaded)
|
||||
{
|
||||
loadGroupList();
|
||||
}
|
||||
|
||||
auto instanceRoot = FS::PathCombine(m_instDir, id);
|
||||
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
|
||||
InstancePtr inst;
|
||||
|
||||
instanceSettings->registerSetting("InstanceType", "Legacy");
|
||||
|
||||
QString inst_type = instanceSettings->get("InstanceType").toString();
|
||||
|
||||
if (inst_type == "OneSix" || inst_type == "Nostalgia")
|
||||
{
|
||||
inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else if (inst_type == "Legacy")
|
||||
{
|
||||
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
else
|
||||
{
|
||||
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
|
||||
}
|
||||
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
|
||||
return inst;
|
||||
}
|
||||
|
||||
void InstanceList::saveGroupList()
|
||||
{
|
||||
qDebug() << "Will save group list now.";
|
||||
if(!m_instancesProbed)
|
||||
{
|
||||
qDebug() << "Group saving prevented because we don't know the full list of instances yet.";
|
||||
return;
|
||||
}
|
||||
WatchLock foo(m_watcher, m_instDir);
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
QMap<QString, QSet<QString>> reverseGroupMap;
|
||||
for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++)
|
||||
{
|
||||
QString id = iter.key();
|
||||
QString group = iter.value();
|
||||
if (group.isEmpty())
|
||||
continue;
|
||||
if(!instanceSet.contains(id))
|
||||
{
|
||||
qDebug() << "Skipping saving missing instance" << id << "to groups list.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!reverseGroupMap.count(group))
|
||||
{
|
||||
QSet<QString> set;
|
||||
set.insert(id);
|
||||
reverseGroupMap[group] = set;
|
||||
}
|
||||
else
|
||||
{
|
||||
QSet<QString> &set = reverseGroupMap[group];
|
||||
set.insert(id);
|
||||
}
|
||||
}
|
||||
QJsonObject toplevel;
|
||||
toplevel.insert("formatVersion", QJsonValue(QString("1")));
|
||||
QJsonObject groupsArr;
|
||||
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
|
||||
{
|
||||
auto list = iter.value();
|
||||
auto name = iter.key();
|
||||
QJsonObject groupObj;
|
||||
QJsonArray instanceArr;
|
||||
groupObj.insert("hidden", QJsonValue(QString("false")));
|
||||
for (auto item : list)
|
||||
{
|
||||
instanceArr.append(QJsonValue(item));
|
||||
}
|
||||
groupObj.insert("instances", instanceArr);
|
||||
groupsArr.insert(name, groupObj);
|
||||
}
|
||||
toplevel.insert("groups", groupsArr);
|
||||
QJsonDocument doc(toplevel);
|
||||
try
|
||||
{
|
||||
FS::write(groupFileName, doc.toJson());
|
||||
qDebug() << "Group list saved.";
|
||||
}
|
||||
catch (const FS::FileSystemException &e)
|
||||
{
|
||||
qCritical() << "Failed to write instance group file :" << e.cause();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceList::loadGroupList()
|
||||
{
|
||||
qDebug() << "Will load group list now.";
|
||||
QSet<QString> groupSet;
|
||||
|
||||
QString groupFileName = m_instDir + "/instgroups.json";
|
||||
|
||||
// if there's no group file, fail
|
||||
if (!QFileInfo(groupFileName).exists())
|
||||
return;
|
||||
|
||||
QByteArray jsonData;
|
||||
try
|
||||
{
|
||||
jsonData = FS::read(groupFileName);
|
||||
}
|
||||
catch (const FS::FileSystemException &e)
|
||||
{
|
||||
qCritical() << "Failed to read instance group file :" << e.cause();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
|
||||
|
||||
// if the json was bad, fail
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
|
||||
.arg(error.errorString(), QString::number(error.offset))
|
||||
.toUtf8();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the root of the json wasn't an object, fail
|
||||
if (!jsonDoc.isObject())
|
||||
{
|
||||
qWarning() << "Invalid group file. Root entry should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject rootObj = jsonDoc.object();
|
||||
|
||||
// Make sure the format version matches, otherwise fail.
|
||||
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
|
||||
return;
|
||||
|
||||
// Get the groups. if it's not an object, fail
|
||||
if (!rootObj.value("groups").isObject())
|
||||
{
|
||||
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_groupMap.clear();
|
||||
|
||||
// Iterate through all the groups.
|
||||
QJsonObject groupMapping = rootObj.value("groups").toObject();
|
||||
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
|
||||
{
|
||||
QString groupName = iter.key();
|
||||
|
||||
// If not an object, complain and skip to the next one.
|
||||
if (!iter.value().isObject())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list should "
|
||||
"be an object.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject groupObj = iter.value().toObject();
|
||||
if (!groupObj.value("instances").isArray())
|
||||
{
|
||||
qWarning() << QString("Group '%1' in the group list is invalid. "
|
||||
"It should contain an array "
|
||||
"called 'instances'.")
|
||||
.arg(groupName)
|
||||
.toUtf8();
|
||||
continue;
|
||||
}
|
||||
|
||||
// keep a list/set of groups for choosing
|
||||
groupSet.insert(groupName);
|
||||
|
||||
// Iterate through the list of instances in the group.
|
||||
QJsonArray instancesArray = groupObj.value("instances").toArray();
|
||||
|
||||
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
|
||||
{
|
||||
m_groupMap[(*iter2).toString()] = groupName;
|
||||
}
|
||||
}
|
||||
m_groupsLoaded = true;
|
||||
m_groups.unite(groupSet);
|
||||
qDebug() << "Group list loaded.";
|
||||
}
|
||||
|
||||
void InstanceList::instanceDirContentsChanged(const QString& path)
|
||||
{
|
||||
Q_UNUSED(path);
|
||||
emit instancesChanged();
|
||||
}
|
||||
|
||||
void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
|
||||
{
|
||||
QString newInstDir = QDir(value.toString()).canonicalPath();
|
||||
if(newInstDir != m_instDir)
|
||||
{
|
||||
if(m_groupsLoaded)
|
||||
{
|
||||
saveGroupList();
|
||||
}
|
||||
m_instDir = newInstDir;
|
||||
m_groupsLoaded = false;
|
||||
emit instancesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
class InstanceStaging : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
const unsigned minBackoff = 1;
|
||||
const unsigned maxBackoff = 16;
|
||||
public:
|
||||
InstanceStaging (
|
||||
InstanceList * 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, &InstanceStaging::childSucceded);
|
||||
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
|
||||
connect(child, &Task::status, this, &InstanceStaging::setStatus);
|
||||
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
|
||||
m_instanceName = instanceName;
|
||||
m_groupName = groupName;
|
||||
m_stagingPath = stagingPath;
|
||||
m_backoffTimer.setSingleShot(true);
|
||||
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
|
||||
}
|
||||
|
||||
virtual ~InstanceStaging() {};
|
||||
|
||||
|
||||
// FIXME/TODO: add ability to abort during instance commit retries
|
||||
bool abort() override
|
||||
{
|
||||
if(m_child && m_child->canAbort())
|
||||
{
|
||||
return m_child->abort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool canAbort() const override
|
||||
{
|
||||
if(m_child && m_child->canAbort())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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:
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
ExponentialSeries backoff;
|
||||
QString m_stagingPath;
|
||||
InstanceList * m_parent;
|
||||
unique_qobject_ptr<Task> m_child;
|
||||
QString m_instanceName;
|
||||
QString m_groupName;
|
||||
QTimer m_backoffTimer;
|
||||
};
|
||||
|
||||
Task * InstanceList::wrapInstanceTask(InstanceTask * task)
|
||||
{
|
||||
auto stagingPath = getStagedInstancePath();
|
||||
task->setStagingPath(stagingPath);
|
||||
task->setParentSettings(m_globalSettings);
|
||||
return new InstanceStaging(this, task, stagingPath, task->name(), task->group());
|
||||
}
|
||||
|
||||
QString InstanceList::getStagedInstancePath()
|
||||
{
|
||||
QString key = QUuid::createUuid().toString();
|
||||
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
|
||||
QDir rootPath(m_instDir);
|
||||
auto path = FS::PathCombine(m_instDir, relPath);
|
||||
if(!rootPath.mkpath(relPath))
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
|
||||
{
|
||||
QDir dir;
|
||||
QString instID = FS::DirNameFromString(instanceName, m_instDir);
|
||||
{
|
||||
WatchLock lock(m_watcher, m_instDir);
|
||||
QString destination = FS::PathCombine(m_instDir, instID);
|
||||
if(!dir.rename(path, destination))
|
||||
{
|
||||
qWarning() << "Failed to move" << path << "to" << destination;
|
||||
return false;
|
||||
}
|
||||
m_groupMap[instID] = groupName;
|
||||
instanceSet.insert(instID);
|
||||
m_groups.insert(groupName);
|
||||
emit instancesChanged();
|
||||
emit instanceSelectRequest(instID);
|
||||
}
|
||||
saveGroupList();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InstanceList::destroyStagingPath(const QString& keyPath)
|
||||
{
|
||||
return FS::deletePath(keyPath);
|
||||
}
|
||||
|
||||
#include "InstanceList.moc"
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,27 +21,49 @@
|
||||
#include <QList>
|
||||
|
||||
#include "BaseInstance.h"
|
||||
#include "BaseInstanceProvider.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
class BaseInstance;
|
||||
class QFileSystemWatcher;
|
||||
class InstanceTask;
|
||||
using InstanceId = QString;
|
||||
using GroupId = QString;
|
||||
using InstanceLocator = std::pair<InstancePtr, int>;
|
||||
|
||||
enum class InstCreateError
|
||||
{
|
||||
NoCreateError = 0,
|
||||
NoSuchVersion,
|
||||
UnknownCreateError,
|
||||
InstExists,
|
||||
CantCreateDir
|
||||
};
|
||||
|
||||
enum class GroupsState
|
||||
{
|
||||
NotLoaded,
|
||||
Steady,
|
||||
Dirty
|
||||
};
|
||||
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InstanceList(QObject *parent = 0);
|
||||
explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
|
||||
virtual ~InstanceList();
|
||||
|
||||
public:
|
||||
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
|
||||
|
||||
enum AdditionalRoles
|
||||
{
|
||||
@@ -70,36 +92,75 @@ public:
|
||||
return m_instances.count();
|
||||
}
|
||||
|
||||
InstListError loadList(bool complete = false);
|
||||
InstListError loadList();
|
||||
void saveNow();
|
||||
|
||||
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
|
||||
void addInstanceProvider(BaseInstanceProvider * provider);
|
||||
|
||||
InstancePtr getInstanceById(QString id) const;
|
||||
QModelIndex getInstanceIndexById(const QString &id) const;
|
||||
QStringList getGroups();
|
||||
GroupId getInstanceGroup(const InstanceId & id) const;
|
||||
void setInstanceGroup(const InstanceId & id, const GroupId& name);
|
||||
|
||||
void deleteGroup(const QString & name);
|
||||
void deleteGroup(const GroupId & name);
|
||||
void deleteInstance(const InstanceId & id);
|
||||
|
||||
// Wrap an instance creation task in some more task machinery and make it ready to be used
|
||||
Task * wrapInstanceTask(InstanceTask * task);
|
||||
|
||||
/**
|
||||
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
QString getStagedInstancePath();
|
||||
|
||||
/**
|
||||
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
|
||||
|
||||
/**
|
||||
* Destroy a previously created staging area given by @keyPath - used when creation fails.
|
||||
* Used by instance manipulation tasks.
|
||||
*/
|
||||
bool destroyStagingPath(const QString & keyPath);
|
||||
|
||||
signals:
|
||||
void dataIsInvalid();
|
||||
void instancesChanged();
|
||||
void instanceSelectRequest(QString instanceId);
|
||||
void groupsChanged(QSet<QString> groups);
|
||||
|
||||
public slots:
|
||||
void on_InstFolderChanged(const Setting &setting, QVariant value);
|
||||
|
||||
private slots:
|
||||
void propertiesChanged(BaseInstance *inst);
|
||||
void groupsPublished(QSet<QString>);
|
||||
void providerUpdated();
|
||||
void instanceDirContentsChanged(const QString &path);
|
||||
|
||||
private:
|
||||
int getInstIndex(BaseInstance *inst) const;
|
||||
void suspendWatch();
|
||||
void resumeWatch();
|
||||
void add(const QList<InstancePtr> &list);
|
||||
void loadGroupList();
|
||||
void saveGroupList();
|
||||
QList<InstanceId> discoverInstances();
|
||||
InstancePtr loadInstance(const InstanceId& id);
|
||||
|
||||
protected:
|
||||
private:
|
||||
int m_watchLevel = 0;
|
||||
QSet<BaseInstanceProvider *> m_updatedProviders;
|
||||
bool m_dirty = false;
|
||||
QList<InstancePtr> m_instances;
|
||||
QSet<QString> m_groups;
|
||||
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
|
||||
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instDir;
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QMap<InstanceId, GroupId> m_groupMap;
|
||||
QSet<InstanceId> instanceSet;
|
||||
bool m_groupsLoaded = false;
|
||||
bool m_instancesProbed = false;
|
||||
};
|
||||
|
||||
9
api/logic/InstanceTask.cpp
Normal file
9
api/logic/InstanceTask.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "InstanceTask.h"
|
||||
|
||||
InstanceTask::InstanceTask()
|
||||
{
|
||||
}
|
||||
|
||||
InstanceTask::~InstanceTask()
|
||||
{
|
||||
}
|
||||
53
api/logic/InstanceTask.h
Normal file
53
api/logic/InstanceTask.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "tasks/Task.h"
|
||||
#include "multimc_logic_export.h"
|
||||
#include "settings/SettingsObject.h"
|
||||
|
||||
class MULTIMC_LOGIC_EXPORT InstanceTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InstanceTask();
|
||||
virtual ~InstanceTask();
|
||||
|
||||
void setParentSettings(SettingsObjectPtr settings)
|
||||
{
|
||||
m_globalSettings = settings;
|
||||
}
|
||||
|
||||
void setStagingPath(const QString &stagingPath)
|
||||
{
|
||||
m_stagingPath = stagingPath;
|
||||
}
|
||||
|
||||
void setName(const QString &name)
|
||||
{
|
||||
m_instName = name;
|
||||
}
|
||||
QString name() const
|
||||
{
|
||||
return m_instName;
|
||||
}
|
||||
|
||||
void setIcon(const QString &icon)
|
||||
{
|
||||
m_instIcon = icon;
|
||||
}
|
||||
|
||||
void setGroup(const QString &group)
|
||||
{
|
||||
m_instGroup = group;
|
||||
}
|
||||
QString group() const
|
||||
{
|
||||
return m_instGroup;
|
||||
}
|
||||
|
||||
protected: /* data */
|
||||
SettingsObjectPtr m_globalSettings;
|
||||
QString m_instName;
|
||||
QString m_instIcon;
|
||||
QString m_instGroup;
|
||||
QString m_stagingPath;
|
||||
};
|
||||
@@ -123,7 +123,7 @@ T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &w
|
||||
{
|
||||
return requireIsType<T>(value, what);
|
||||
}
|
||||
catch (JsonException &)
|
||||
catch (const JsonException &)
|
||||
{
|
||||
return default_;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -212,8 +212,10 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
|
||||
{
|
||||
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
|
||||
@@ -231,10 +233,12 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
|
||||
}
|
||||
if (!JlCompress::extractFile(zip, "", absFilePath))
|
||||
{
|
||||
qWarning() << "Failed to extract file" << name << "to" << absFilePath;
|
||||
JlCompress::removeFile(extracted);
|
||||
return QStringList();
|
||||
}
|
||||
extracted.append(absFilePath);
|
||||
qDebug() << "Extracted file" << name;
|
||||
} while (zip->goToNextFile());
|
||||
return extracted;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
#include "BaseInstance.h"
|
||||
#include "launch/LaunchTask.h"
|
||||
|
||||
class NullInstance: public BaseInstance
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
|
||||
:BaseInstance(globalSettings, settings, rootDir)
|
||||
@@ -10,49 +12,46 @@ public:
|
||||
setVersionBroken(true);
|
||||
}
|
||||
virtual ~NullInstance() {};
|
||||
virtual void init() override
|
||||
void saveNow() override
|
||||
{
|
||||
}
|
||||
virtual void saveNow() override
|
||||
{
|
||||
}
|
||||
virtual QString getStatusbarDescription() override
|
||||
QString getStatusbarDescription() override
|
||||
{
|
||||
return tr("Unknown instance type");
|
||||
};
|
||||
virtual QSet< QString > traits() const override
|
||||
QSet< QString > traits() const override
|
||||
{
|
||||
return {};
|
||||
};
|
||||
virtual QString instanceConfigFolder() const override
|
||||
QString instanceConfigFolder() const override
|
||||
{
|
||||
return instanceRoot();
|
||||
};
|
||||
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
|
||||
shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QProcessEnvironment createEnvironment() override
|
||||
QProcessEnvironment createEnvironment() override
|
||||
{
|
||||
return QProcessEnvironment();
|
||||
}
|
||||
virtual QMap<QString, QString> getVariables() const override
|
||||
QMap<QString, QString> getVariables() const override
|
||||
{
|
||||
return QMap<QString, QString>();
|
||||
}
|
||||
virtual IPathMatcher::Ptr getLogFileMatcher() override
|
||||
IPathMatcher::Ptr getLogFileMatcher() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
virtual QString getLogFileRoot() override
|
||||
QString getLogFileRoot() override
|
||||
{
|
||||
return instanceRoot();
|
||||
}
|
||||
virtual QString typeName() const override
|
||||
QString typeName() const override
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
|
||||
@@ -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,6 +54,7 @@ public:
|
||||
{
|
||||
QWriteLocker l(&lock);
|
||||
cache.clear();
|
||||
stale_entries.clear();
|
||||
}
|
||||
private:
|
||||
QReadWriteLock lock;
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
|
||||
class QUrl;
|
||||
|
||||
struct MULTIMC_LOGIC_EXPORT Version
|
||||
class MULTIMC_LOGIC_EXPORT Version
|
||||
{
|
||||
public:
|
||||
Version(const QString &str);
|
||||
Version() {}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
20
api/logic/WatchLock.h
Normal file
20
api/logic/WatchLock.h
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
struct WatchLock
|
||||
{
|
||||
WatchLock(QFileSystemWatcher * watcher, const QString& directory)
|
||||
: m_watcher(watcher), m_directory(directory)
|
||||
{
|
||||
m_watcher->removePath(m_directory);
|
||||
}
|
||||
~WatchLock()
|
||||
{
|
||||
m_watcher->addPath(m_directory);
|
||||
}
|
||||
QFileSystemWatcher * m_watcher;
|
||||
QString m_directory;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
62
api/logic/icons/IconUtils.cpp
Normal file
62
api/logic/icons/IconUtils.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "IconUtils.h"
|
||||
|
||||
#include "FileSystem.h"
|
||||
#include <QDirIterator>
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
std::array<const char *, 6> validIconExtensions = {{
|
||||
"svg",
|
||||
"png",
|
||||
"ico",
|
||||
"gif",
|
||||
"jpg",
|
||||
"jpeg"
|
||||
}};
|
||||
}
|
||||
|
||||
namespace IconUtils{
|
||||
|
||||
QString findBestIconIn(const QString &folder, const QString & iconKey) {
|
||||
int best_found = validIconExtensions.size();
|
||||
QString best_filename;
|
||||
|
||||
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
auto fileInfo = it.fileInfo();
|
||||
|
||||
if(fileInfo.completeBaseName() != iconKey)
|
||||
continue;
|
||||
|
||||
auto extension = fileInfo.suffix();
|
||||
|
||||
for(int i = 0; i < best_found; i++) {
|
||||
if(extension == validIconExtensions[i]) {
|
||||
best_found = i;
|
||||
qDebug() << i << " : " << fileInfo.fileName();
|
||||
best_filename = fileInfo.fileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
return FS::PathCombine(folder, best_filename);
|
||||
}
|
||||
|
||||
QString getIconFilter() {
|
||||
QString out;
|
||||
QTextStream stream(&out);
|
||||
stream << '(';
|
||||
for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
|
||||
if(i > 0) {
|
||||
stream << " ";
|
||||
}
|
||||
stream << "*." << validIconExtensions[i];
|
||||
}
|
||||
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
|
||||
stream << ')';
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
14
api/logic/icons/IconUtils.h
Normal file
14
api/logic/icons/IconUtils.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
namespace IconUtils {
|
||||
|
||||
// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
|
||||
MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey);
|
||||
|
||||
// Get icon file type filter for file browser dialogs
|
||||
MULTIMC_LOGIC_EXPORT QString getIconFilter();
|
||||
|
||||
}
|
||||
@@ -75,8 +75,8 @@ void JavaChecker::stderrReady()
|
||||
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
|
||||
{
|
||||
killTimer.stop();
|
||||
QProcessPtr _process;
|
||||
_process.swap(process);
|
||||
QProcessPtr _process = process;
|
||||
process.reset();
|
||||
|
||||
JavaCheckResult result;
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
#include "JavaVersion.h"
|
||||
@@ -27,8 +29,8 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
|
||||
} validity = Validity::Errored;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<QProcess> QProcessPtr;
|
||||
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
|
||||
typedef shared_qobject_ptr<QProcess> QProcessPtr;
|
||||
typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
|
||||
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "tasks/Task.h"
|
||||
|
||||
class JavaCheckerJob;
|
||||
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
|
||||
|
||||
// FIXME: this just seems horribly redundant
|
||||
class JavaCheckerJob : public Task
|
||||
@@ -28,6 +28,7 @@ class JavaCheckerJob : public Task
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
|
||||
virtual ~JavaCheckerJob() {};
|
||||
|
||||
bool addJavaCheckerAction(JavaCheckerPtr base)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -149,7 +149,7 @@ void JavaListLoadTask::executeTask()
|
||||
JavaUtils ju;
|
||||
QList<QString> candidate_paths = ju.FindJavaPaths();
|
||||
|
||||
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
|
||||
m_job = new JavaCheckerJob("Java detection");
|
||||
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
|
||||
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "JavaCheckerJob.h"
|
||||
#include "JavaInstall.h"
|
||||
|
||||
#include "QObjectPtr.h"
|
||||
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class JavaListLoadTask;
|
||||
@@ -68,14 +70,14 @@ class JavaListLoadTask : public Task
|
||||
|
||||
public:
|
||||
explicit JavaListLoadTask(JavaInstallList *vlist);
|
||||
~JavaListLoadTask();
|
||||
virtual ~JavaListLoadTask();
|
||||
|
||||
void executeTask() override;
|
||||
public slots:
|
||||
void javaCheckerFinished();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<JavaCheckerJob> m_job;
|
||||
shared_qobject_ptr<JavaCheckerJob> m_job;
|
||||
JavaInstallList *m_list;
|
||||
JavaInstall *m_currentRecommended;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ JavaUtils::JavaUtils()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
|
||||
{
|
||||
QDir mmcBin(QCoreApplication::applicationDirPath());
|
||||
@@ -48,6 +49,7 @@ static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
|
||||
}
|
||||
return final.join(':');
|
||||
}
|
||||
#endif
|
||||
|
||||
QProcessEnvironment CleanEnviroment()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -60,7 +60,7 @@ void CheckJava::executeTask()
|
||||
// if timestamps are not the same, or something is missing, check!
|
||||
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
|
||||
{
|
||||
m_JavaChecker = std::make_shared<JavaChecker>();
|
||||
m_JavaChecker = new JavaChecker();
|
||||
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
|
||||
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
|
||||
m_JavaChecker->m_path = realJavaPath;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,8 +31,8 @@ public: /* methods */
|
||||
};
|
||||
virtual ~LaunchStep() {};
|
||||
|
||||
protected: /* methods */
|
||||
virtual void bind(LaunchTask *parent);
|
||||
private: /* methods */
|
||||
void bind(LaunchTask *parent);
|
||||
|
||||
signals:
|
||||
void logLines(QStringList lines, MessageLevel::Enum level);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -33,9 +33,9 @@ void LaunchTask::init()
|
||||
m_instance->setRunning(true);
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
|
||||
{
|
||||
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
|
||||
proc->init();
|
||||
return proc;
|
||||
}
|
||||
@@ -44,12 +44,12 @@ LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
|
||||
{
|
||||
}
|
||||
|
||||
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.append(step);
|
||||
}
|
||||
|
||||
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
|
||||
void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
|
||||
{
|
||||
m_steps.prepend(step);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
|
||||
*
|
||||
@@ -45,11 +45,11 @@ public:
|
||||
};
|
||||
|
||||
public: /* methods */
|
||||
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
|
||||
static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
|
||||
virtual ~LaunchTask() {};
|
||||
|
||||
void appendStep(std::shared_ptr<LaunchStep> step);
|
||||
void prependStep(std::shared_ptr<LaunchStep> step);
|
||||
void appendStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void prependStep(shared_qobject_ptr<LaunchStep> step);
|
||||
void setCensorFilter(QMap<QString, QString> filter);
|
||||
|
||||
InstancePtr instance()
|
||||
@@ -117,7 +117,7 @@ private: /*methods */
|
||||
protected: /* data */
|
||||
InstancePtr m_instance;
|
||||
shared_qobject_ptr<LogModel> m_logModel;
|
||||
QList <std::shared_ptr<LaunchStep>> m_steps;
|
||||
QList <shared_qobject_ptr<LaunchStep>> m_steps;
|
||||
QMap<QString, QString> m_censorFilter;
|
||||
int currentStep = -1;
|
||||
State state = NotStarted;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,8 @@ class PostLaunchCommand: public LaunchStep
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PostLaunchCommand(LaunchTask *parent);
|
||||
virtual ~PostLaunchCommand() {};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool abort();
|
||||
virtual bool canAbort() const
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,8 @@ class PreLaunchCommand: public LaunchStep
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PreLaunchCommand(LaunchTask *parent);
|
||||
virtual ~PreLaunchCommand() {};
|
||||
|
||||
virtual void executeTask();
|
||||
virtual bool abort();
|
||||
virtual bool canAbort() const
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,7 +49,7 @@ void Update::updateFinished()
|
||||
}
|
||||
else
|
||||
{
|
||||
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
|
||||
QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason());
|
||||
m_updateTask.reset();
|
||||
emit logLine(reason, MessageLevel::Fatal);
|
||||
emitFailed(reason);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -56,7 +56,7 @@ public: /* methods */
|
||||
m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname));
|
||||
return true;
|
||||
}
|
||||
catch (Exception &e)
|
||||
catch (const Exception &e)
|
||||
{
|
||||
qWarning() << "Unable to parse response:" << e.cause();
|
||||
return false;
|
||||
@@ -74,7 +74,7 @@ Meta::BaseEntity::~BaseEntity()
|
||||
|
||||
QUrl Meta::BaseEntity::url() const
|
||||
{
|
||||
return QUrl("https://v1.meta.multimc.org").resolved(localFilename());
|
||||
return QUrl("https://meta.multimc.org/v1/").resolved(localFilename());
|
||||
}
|
||||
|
||||
bool Meta::BaseEntity::loadLocalFile()
|
||||
@@ -90,7 +90,7 @@ bool Meta::BaseEntity::loadLocalFile()
|
||||
parse(Json::requireObject(Json::requireDocument(fname, fname), fname));
|
||||
return true;
|
||||
}
|
||||
catch (Exception &e)
|
||||
catch (const 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -80,4 +80,4 @@ void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyNa
|
||||
MetadataVersion currentFormatVersion();
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::set<Meta::Require>);
|
||||
Q_DECLARE_METATYPE(std::set<Meta::Require>)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015-2017 MultiMC Contributors
|
||||
/* Copyright 2015-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,34 @@
|
||||
#include "FileSystem.h"
|
||||
#include "net/Download.h"
|
||||
#include "net/ChecksumValidator.h"
|
||||
#include "net/URLConstants.h"
|
||||
|
||||
namespace {
|
||||
QSet<QString> collectPathsFromDir(QString dirPath)
|
||||
{
|
||||
QFileInfo dirInfo(dirPath);
|
||||
|
||||
if (!dirInfo.exists())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QSet<QString> out;
|
||||
|
||||
QDirIterator iter(dirPath, QDirIterator::Subdirectories);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
QString value = iter.next();
|
||||
QFileInfo info(value);
|
||||
if(info.isFile())
|
||||
{
|
||||
out.insert(value);
|
||||
qDebug() << value;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace AssetsUtils
|
||||
@@ -36,7 +64,7 @@ namespace AssetsUtils
|
||||
* Returns true on success, with index populated
|
||||
* index is undefined otherwise
|
||||
*/
|
||||
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
|
||||
bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index)
|
||||
{
|
||||
/*
|
||||
{
|
||||
@@ -60,7 +88,7 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
|
||||
qCritical() << "Failed to read assets index file" << path;
|
||||
return false;
|
||||
}
|
||||
index->id = assetsId;
|
||||
index.id = assetsId;
|
||||
|
||||
// Read the file and close it.
|
||||
QByteArray jsonData = file.readAll();
|
||||
@@ -89,7 +117,13 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
|
||||
QJsonValue isVirtual = root.value("virtual");
|
||||
if (!isVirtual.isUndefined())
|
||||
{
|
||||
index->isVirtual = isVirtual.toBool(false);
|
||||
index.isVirtual = isVirtual.toBool(false);
|
||||
}
|
||||
|
||||
QJsonValue mapToResources = root.value("map_to_resources");
|
||||
if (!mapToResources.isUndefined())
|
||||
{
|
||||
index.mapToResources = mapToResources.toBool(false);
|
||||
}
|
||||
|
||||
QJsonValue objects = root.value("objects");
|
||||
@@ -121,13 +155,14 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
|
||||
}
|
||||
}
|
||||
|
||||
index->objects.insert(iter.key(), object);
|
||||
index.objects.insert(iter.key(), object);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QDir reconstructAssets(QString assetsId)
|
||||
// FIXME: ugly code duplication
|
||||
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
|
||||
{
|
||||
QDir assetsDir = QDir("assets/");
|
||||
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
|
||||
@@ -140,24 +175,77 @@ QDir reconstructAssets(QString assetsId)
|
||||
|
||||
if (!indexFile.exists())
|
||||
{
|
||||
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
|
||||
qCritical() << "No assets index file" << indexPath << "; can't determine assets path!";
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
|
||||
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
|
||||
AssetsIndex index;
|
||||
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
|
||||
{
|
||||
qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!";
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
QString targetPath;
|
||||
if(index.isVirtual)
|
||||
{
|
||||
return virtualRoot;
|
||||
}
|
||||
else if(index.mapToResources)
|
||||
{
|
||||
return QDir(resourcesFolder);
|
||||
}
|
||||
return virtualRoot;
|
||||
}
|
||||
|
||||
// FIXME: ugly code duplication
|
||||
bool reconstructAssets(QString assetsId, QString resourcesFolder)
|
||||
{
|
||||
QDir assetsDir = QDir("assets/");
|
||||
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
|
||||
QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects"));
|
||||
QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual"));
|
||||
|
||||
QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json");
|
||||
QFile indexFile(indexPath);
|
||||
QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
|
||||
|
||||
if (!indexFile.exists())
|
||||
{
|
||||
qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!";
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
|
||||
|
||||
AssetsIndex index;
|
||||
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
|
||||
|
||||
if (loadAssetsIndex && index.isVirtual)
|
||||
if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
|
||||
{
|
||||
qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
|
||||
qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!";
|
||||
return false;
|
||||
}
|
||||
|
||||
QString targetPath;
|
||||
bool removeLeftovers = false;
|
||||
if(index.isVirtual)
|
||||
{
|
||||
targetPath = virtualRoot.path();
|
||||
removeLeftovers = true;
|
||||
qDebug() << "Reconstructing virtual assets folder at" << targetPath;
|
||||
}
|
||||
else if(index.mapToResources)
|
||||
{
|
||||
targetPath = resourcesFolder;
|
||||
qDebug() << "Reconstructing resources folder at" << targetPath;
|
||||
}
|
||||
|
||||
if (!targetPath.isNull())
|
||||
{
|
||||
auto presentFiles = collectPathsFromDir(targetPath);
|
||||
for (QString map : index.objects.keys())
|
||||
{
|
||||
AssetObject asset_object = index.objects.value(map);
|
||||
QString target_path = FS::PathCombine(virtualRoot.path(), map);
|
||||
QString target_path = FS::PathCombine(targetPath, map);
|
||||
QFile target(target_path);
|
||||
|
||||
QString tlk = asset_object.hash.left(2);
|
||||
@@ -166,24 +254,32 @@ QDir reconstructAssets(QString assetsId)
|
||||
QFile original(original_path);
|
||||
if (!original.exists())
|
||||
continue;
|
||||
|
||||
presentFiles.remove(target_path);
|
||||
|
||||
if (!target.exists())
|
||||
{
|
||||
QFileInfo info(target_path);
|
||||
QDir target_dir = info.dir();
|
||||
// qDebug() << target_dir;
|
||||
if (!target_dir.exists())
|
||||
QDir("").mkpath(target_dir.path());
|
||||
|
||||
qDebug() << target_dir.path();
|
||||
FS::ensureFolderPathExists(target_dir.path());
|
||||
|
||||
bool couldCopy = original.copy(target_path);
|
||||
qDebug() << " Copying" << original_path << "to" << target_path
|
||||
<< QString::number(couldCopy); // << original.errorString();
|
||||
qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Write last used time to virtualRoot/.lastused
|
||||
if(removeLeftovers)
|
||||
{
|
||||
for(auto & file: presentFiles)
|
||||
{
|
||||
qDebug() << "Would remove" << file;
|
||||
}
|
||||
|
||||
return virtualRoot;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -212,7 +308,7 @@ QString AssetObject::getLocalPath()
|
||||
|
||||
QUrl AssetObject::getUrl()
|
||||
{
|
||||
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
|
||||
return URLConstants::RESOURCE_BASE + getRelPath();
|
||||
}
|
||||
|
||||
QString AssetObject::getRelPath()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,11 +38,16 @@ struct AssetsIndex
|
||||
QString id;
|
||||
QMap<QString, AssetObject> objects;
|
||||
bool isVirtual = false;
|
||||
bool mapToResources = false;
|
||||
};
|
||||
|
||||
/// FIXME: this is absolutely horrendous. REDO!!!!
|
||||
namespace AssetsUtils
|
||||
{
|
||||
bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
|
||||
bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index);
|
||||
|
||||
QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder);
|
||||
|
||||
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
|
||||
QDir reconstructAssets(QString assetsId);
|
||||
bool reconstructAssets(QString assetsId, QString resourcesFolder);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ QDateTime Component::getReleaseDateTime()
|
||||
bool Component::isEnabled()
|
||||
{
|
||||
return !canBeDisabled() || !m_disabled;
|
||||
};
|
||||
}
|
||||
|
||||
bool Component::canBeDisabled()
|
||||
{
|
||||
@@ -171,7 +171,7 @@ bool Component::setEnabled(bool state)
|
||||
bool Component::isCustom()
|
||||
{
|
||||
return m_file != nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
bool Component::isCustomizable()
|
||||
{
|
||||
@@ -323,7 +323,7 @@ bool Component::customize()
|
||||
m_metaVersion.reset();
|
||||
emit dataChanged();
|
||||
}
|
||||
catch (Exception &error)
|
||||
catch (const Exception &error)
|
||||
{
|
||||
qWarning() << "Version could not be loaded:" << error.cause();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -195,7 +195,7 @@ static bool loadComponentList(ComponentList * parent, const QString & filename,
|
||||
container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
|
||||
}
|
||||
}
|
||||
catch (JSONValidationError &err)
|
||||
catch (const JSONValidationError &err)
|
||||
{
|
||||
qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
|
||||
container.clear();
|
||||
@@ -635,6 +635,9 @@ void ComponentList::componentDataChanged()
|
||||
qWarning() << "ComponentList got dataChenged signal from a non-Component!";
|
||||
return;
|
||||
}
|
||||
if(objPtr->getID() == "net.minecraft") {
|
||||
emit minecraftChanged();
|
||||
}
|
||||
// figure out which one is it... in a seriously dumb way.
|
||||
int index = 0;
|
||||
for (auto component: d->components)
|
||||
@@ -1150,7 +1153,7 @@ std::shared_ptr<LaunchProfile> ComponentList::getProfile() const
|
||||
}
|
||||
d->m_profile = profile;
|
||||
}
|
||||
catch (Exception & error)
|
||||
catch (const Exception &error)
|
||||
{
|
||||
qWarning() << "Couldn't apply profile patches because: " << error.cause();
|
||||
}
|
||||
@@ -1172,11 +1175,16 @@ bool ComponentList::setComponentVersion(const QString& uid, const QString& versi
|
||||
auto iter = d->componentIndex.find(uid);
|
||||
if(iter != d->componentIndex.end())
|
||||
{
|
||||
ComponentPtr component = *iter;
|
||||
// set existing
|
||||
(*iter)->setVersion(version);
|
||||
(*iter)->setImportant(important);
|
||||
if(component->revert())
|
||||
{
|
||||
component->setVersion(version);
|
||||
component->setImportant(important);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add new
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -104,6 +104,9 @@ public:
|
||||
/// if there is a save scheduled, do it now.
|
||||
void saveNow();
|
||||
|
||||
signals:
|
||||
void minecraftChanged();
|
||||
|
||||
public:
|
||||
/// get the profile component by id
|
||||
Component * getComponent(const QString &id);
|
||||
|
||||
@@ -124,6 +124,8 @@ static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>
|
||||
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)
|
||||
@@ -147,6 +149,7 @@ static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<T
|
||||
}
|
||||
return result;
|
||||
}
|
||||
*/
|
||||
|
||||
static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
|
||||
{
|
||||
@@ -583,6 +586,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
|
||||
{
|
||||
component->m_version = "3.1.2";
|
||||
}
|
||||
else if (add.uid == "net.fabricmc.intermediary")
|
||||
{
|
||||
auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){
|
||||
return cmp->getID() == "net.minecraft";
|
||||
});
|
||||
if(minecraft != components.end()) {
|
||||
component->m_version = (*minecraft)->getVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
|
||||
// ############################################################################################################
|
||||
|
||||
@@ -18,13 +18,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
|
||||
if(local && !overridePath.isEmpty())
|
||||
{
|
||||
QString fileName = out.fileName();
|
||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||
qDebug() << fullPath;
|
||||
QFileInfo fileinfo(fullPath);
|
||||
if(fileinfo.exists())
|
||||
{
|
||||
return fileinfo.absoluteFilePath();
|
||||
}
|
||||
return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath();
|
||||
}
|
||||
return out.absoluteFilePath();
|
||||
};
|
||||
@@ -51,61 +45,53 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
|
||||
}
|
||||
}
|
||||
|
||||
QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache,
|
||||
QStringList& failedFiles, const QString & overridePath) const
|
||||
QList< std::shared_ptr< NetAction > > Library::getDownloads(
|
||||
OpSys system,
|
||||
class HttpMetaCache* cache,
|
||||
QStringList& failedLocalFiles,
|
||||
const QString & overridePath
|
||||
) const
|
||||
{
|
||||
QList<NetActionPtr> out;
|
||||
bool isAlwaysStale = (hint() == "always-stale");
|
||||
bool stale = isAlwaysStale();
|
||||
bool local = isLocal();
|
||||
bool isForge = (hint() == "forge-pack-xz");
|
||||
|
||||
auto add_download = [&](QString storage, QString url, QString sha1 = QString())
|
||||
auto check_local_file = [&](QString storage)
|
||||
{
|
||||
QFileInfo fileinfo(storage);
|
||||
QString fileName = fileinfo.fileName();
|
||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||
QFileInfo localFileInfo(fullPath);
|
||||
if(!localFileInfo.exists())
|
||||
{
|
||||
failedLocalFiles.append(localFileInfo.filePath());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto add_download = [&](QString storage, QString url, QString sha1)
|
||||
{
|
||||
if(local)
|
||||
{
|
||||
return check_local_file(storage);
|
||||
}
|
||||
auto entry = cache->resolveEntry("libraries", storage);
|
||||
if(isAlwaysStale)
|
||||
if(stale)
|
||||
{
|
||||
entry->setStale(true);
|
||||
}
|
||||
if (!entry->isStale())
|
||||
return true;
|
||||
if(local)
|
||||
{
|
||||
if(!overridePath.isEmpty())
|
||||
{
|
||||
QString fileName;
|
||||
int position = storage.lastIndexOf('/');
|
||||
if(position == -1)
|
||||
{
|
||||
fileName = storage;
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = storage.mid(position);
|
||||
}
|
||||
auto fullPath = FS::PathCombine(overridePath, fileName);
|
||||
QFileInfo fileinfo(fullPath);
|
||||
if(fileinfo.exists())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
QFileInfo fileinfo(entry->getFullPath());
|
||||
if(!fileinfo.exists())
|
||||
{
|
||||
failedFiles.append(entry->getFullPath());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Net::Download::Options options;
|
||||
if(isAlwaysStale)
|
||||
if(stale)
|
||||
{
|
||||
options |= Net::Download::Option::AcceptLocalFiles;
|
||||
}
|
||||
if (isForge)
|
||||
if (isForge())
|
||||
{
|
||||
qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url;
|
||||
out.append(ForgeXzDownload::make(storage, entry));
|
||||
out.append(ForgeXzDownload::make(url, storage, entry));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -184,7 +170,8 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
|
||||
}
|
||||
else
|
||||
{
|
||||
auto raw_dl = [&](){
|
||||
auto raw_dl = [&]()
|
||||
{
|
||||
if (!m_absoluteURL.isEmpty())
|
||||
{
|
||||
return m_absoluteURL;
|
||||
@@ -192,7 +179,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
|
||||
|
||||
if (m_repositoryURL.isEmpty())
|
||||
{
|
||||
return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage;
|
||||
return URLConstants::LIBRARY_BASE + raw_storage;
|
||||
}
|
||||
|
||||
if(m_repositoryURL.endsWith('/'))
|
||||
@@ -208,14 +195,14 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
|
||||
{
|
||||
QString cooked_storage = raw_storage;
|
||||
QString cooked_dl = raw_dl;
|
||||
add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"));
|
||||
add_download(cooked_storage.replace("${arch}", "32"), cooked_dl.replace("${arch}", "32"), QString());
|
||||
cooked_storage = raw_storage;
|
||||
cooked_dl = raw_dl;
|
||||
add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"));
|
||||
add_download(cooked_storage.replace("${arch}", "64"), cooked_dl.replace("${arch}", "64"), QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
add_download(raw_storage, raw_dl);
|
||||
add_download(raw_storage, raw_dl, QString());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
@@ -251,6 +238,16 @@ bool Library::isLocal() const
|
||||
return m_hint == "local";
|
||||
}
|
||||
|
||||
bool Library::isAlwaysStale() const
|
||||
{
|
||||
return m_hint == "always-stale";
|
||||
}
|
||||
|
||||
bool Library::isForge() const
|
||||
{
|
||||
return m_hint == "forge-pack-xz";
|
||||
}
|
||||
|
||||
void Library::setStoragePrefix(QString prefix)
|
||||
{
|
||||
m_storagePrefix = prefix;
|
||||
|
||||
@@ -148,9 +148,15 @@ public: /* methods */
|
||||
/// Returns true if the library is contained in an instance and false if it is shared
|
||||
bool isLocal() const;
|
||||
|
||||
/// Returns true if the library is to always be checked for updates
|
||||
bool isAlwaysStale() const;
|
||||
|
||||
/// Return true if the library requires forge XZ hacks
|
||||
bool isForge() const;
|
||||
|
||||
// Get a list of downloads for this library
|
||||
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache,
|
||||
QStringList & failedFiles, const QString & overridePath) const;
|
||||
QStringList & failedLocalFiles, const QString & overridePath) const;
|
||||
|
||||
private: /* methods */
|
||||
/// the default storage prefix used by MultiMC
|
||||
|
||||
@@ -65,7 +65,7 @@ slots:
|
||||
test.setHint("local");
|
||||
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
|
||||
QCOMPARE(downloads.size(), 0);
|
||||
QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar"));
|
||||
QCOMPARE(failedFiles, {"testname-testversion.jar"});
|
||||
}
|
||||
void test_legacy_url_local_override()
|
||||
{
|
||||
@@ -170,11 +170,11 @@ slots:
|
||||
QCOMPARE(jar, {});
|
||||
QCOMPARE(native, {});
|
||||
QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
|
||||
QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
|
||||
QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()});
|
||||
QStringList failedFiles;
|
||||
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
|
||||
QCOMPARE(dls.size(), 0);
|
||||
QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")});
|
||||
QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"});
|
||||
}
|
||||
}
|
||||
void test_onenine()
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
#include "minecraft/launch/DirectJavaLaunch.h"
|
||||
#include "minecraft/launch/ModMinecraftJar.h"
|
||||
#include "minecraft/launch/ClaimAccount.h"
|
||||
#include "minecraft/launch/ReconstructAssets.h"
|
||||
#include "java/launch/CheckJava.h"
|
||||
#include "java/JavaUtils.h"
|
||||
#include "meta/Index.h"
|
||||
#include "meta/VersionList.h"
|
||||
|
||||
#include "ModList.h"
|
||||
#include "SimpleModList.h"
|
||||
#include "WorldList.h"
|
||||
|
||||
#include "icons/IIconList.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "AssetsUtils.h"
|
||||
#include "MinecraftUpdate.h"
|
||||
#include "MinecraftLoadAndCheck.h"
|
||||
#include <minecraft/gameoptions/GameOptions.h>
|
||||
|
||||
#define IBUS "@im=ibus"
|
||||
|
||||
@@ -111,10 +113,6 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
|
||||
m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
|
||||
}
|
||||
|
||||
void MinecraftInstance::init()
|
||||
{
|
||||
}
|
||||
|
||||
void MinecraftInstance::saveNow()
|
||||
{
|
||||
m_components->saveNow();
|
||||
@@ -145,7 +143,7 @@ QSet<QString> MinecraftInstance::traits() const
|
||||
return profile->getTraits();
|
||||
}
|
||||
|
||||
QString MinecraftInstance::minecraftRoot() const
|
||||
QString MinecraftInstance::gameRoot() const
|
||||
{
|
||||
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
|
||||
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
|
||||
@@ -158,7 +156,7 @@ QString MinecraftInstance::minecraftRoot() const
|
||||
|
||||
QString MinecraftInstance::binRoot() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "bin");
|
||||
return FS::PathCombine(gameRoot(), "bin");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::getNativePath() const
|
||||
@@ -173,44 +171,55 @@ QString MinecraftInstance::getLocalLibraryPath() const
|
||||
return libraries_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString MinecraftInstance::jarModsDir() const
|
||||
{
|
||||
QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
|
||||
return jarmods_dir.absolutePath();
|
||||
}
|
||||
|
||||
QString MinecraftInstance::loaderModsDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "mods");
|
||||
return FS::PathCombine(gameRoot(), "mods");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::modsCacheLocation() const
|
||||
{
|
||||
return FS::PathCombine(instanceRoot(), "mods.cache");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::coreModsDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "coremods");
|
||||
return FS::PathCombine(gameRoot(), "coremods");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::resourcePacksDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "resourcepacks");
|
||||
return FS::PathCombine(gameRoot(), "resourcepacks");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::texturePacksDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "texturepacks");
|
||||
return FS::PathCombine(gameRoot(), "texturepacks");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::instanceConfigFolder() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "config");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::jarModsDir() const
|
||||
{
|
||||
return FS::PathCombine(instanceRoot(), "jarmods");
|
||||
return FS::PathCombine(gameRoot(), "config");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::libDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "lib");
|
||||
return FS::PathCombine(gameRoot(), "lib");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::worldDir() const
|
||||
{
|
||||
return FS::PathCombine(minecraftRoot(), "saves");
|
||||
return FS::PathCombine(gameRoot(), "saves");
|
||||
}
|
||||
|
||||
QString MinecraftInstance::resourcesDir() const
|
||||
{
|
||||
return FS::PathCombine(gameRoot(), "resources");
|
||||
}
|
||||
|
||||
QDir MinecraftInstance::librariesPath() const
|
||||
@@ -330,7 +339,7 @@ QMap<QString, QString> MinecraftInstance::getVariables() const
|
||||
out.insert("INST_NAME", name());
|
||||
out.insert("INST_ID", id());
|
||||
out.insert("INST_DIR", QDir(instanceRoot()).absolutePath());
|
||||
out.insert("INST_MC_DIR", QDir(minecraftRoot()).absolutePath());
|
||||
out.insert("INST_MC_DIR", QDir(gameRoot()).absolutePath());
|
||||
out.insert("INST_JAVA", settings()->get("JavaPath").toString());
|
||||
out.insert("INST_JAVA_ARGS", javaArguments().join(' '));
|
||||
return out;
|
||||
@@ -401,12 +410,11 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
|
||||
|
||||
token_mapping["version_type"] = profile->getMinecraftVersionType();
|
||||
|
||||
QString absRootDir = QDir(minecraftRoot()).absolutePath();
|
||||
QString absRootDir = QDir(gameRoot()).absolutePath();
|
||||
token_mapping["game_directory"] = absRootDir;
|
||||
QString absAssetsDir = QDir("assets/").absolutePath();
|
||||
auto assets = profile->getMinecraftAssets();
|
||||
// FIXME: this is wrong and should be run as an async task
|
||||
token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath();
|
||||
token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath();
|
||||
|
||||
// 1.7.3+ assets tokens
|
||||
token_mapping["assets_root"] = absAssetsDir;
|
||||
@@ -636,13 +644,11 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
|
||||
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
|
||||
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
|
||||
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
|
||||
addToFilter(sessionRef.player_name, tr("<PROFILE NAME>"));
|
||||
|
||||
auto i = sessionRef.u.properties.begin();
|
||||
while (i != sessionRef.u.properties.end())
|
||||
{
|
||||
if(i.key() == "preferredLanguage")
|
||||
{
|
||||
if(i.value().length() <= 3) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
@@ -711,7 +717,7 @@ IPathMatcher::Ptr MinecraftInstance::getLogFileMatcher()
|
||||
|
||||
QString MinecraftInstance::getLogFileRoot()
|
||||
{
|
||||
return minecraftRoot();
|
||||
return gameRoot();
|
||||
}
|
||||
|
||||
QString MinecraftInstance::prettifyTimeDuration(int64_t duration)
|
||||
@@ -764,28 +770,28 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
|
||||
}
|
||||
case Net::Mode::Online:
|
||||
{
|
||||
return shared_qobject_ptr<Task>(new OneSixUpdate(this));
|
||||
return shared_qobject_ptr<Task>(new MinecraftUpdate(this));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
|
||||
{
|
||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
|
||||
// FIXME: get rid of shared_from_this ...
|
||||
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
|
||||
auto pptr = process.get();
|
||||
|
||||
ENV.icons()->saveIcon(iconKey(), FS::PathCombine(minecraftRoot(), "icon.png"), "PNG");
|
||||
ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG");
|
||||
|
||||
// print a header
|
||||
{
|
||||
process->appendStep(std::make_shared<TextPrint>(pptr, "Minecraft folder is:\n" + minecraftRoot() + "\n\n", MessageLevel::MultiMC));
|
||||
process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
|
||||
}
|
||||
|
||||
// check java
|
||||
{
|
||||
auto step = std::make_shared<CheckJava>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new CheckJava(pptr));
|
||||
}
|
||||
|
||||
// check launch method
|
||||
@@ -793,51 +799,52 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
QString method = launchMethod();
|
||||
if(!validMethods.contains(method))
|
||||
{
|
||||
process->appendStep(std::make_shared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
||||
process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
|
||||
return process;
|
||||
}
|
||||
|
||||
// run pre-launch command if that's needed
|
||||
if(getPreLaunchCommand().size())
|
||||
{
|
||||
auto step = std::make_shared<PreLaunchCommand>(pptr);
|
||||
step->setWorkingDirectory(minecraftRoot());
|
||||
auto step = new PreLaunchCommand(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
|
||||
// if we aren't in offline mode,.
|
||||
if(session->status != AuthSession::PlayableOffline)
|
||||
{
|
||||
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
|
||||
process->appendStep(new ClaimAccount(pptr, session));
|
||||
process->appendStep(new Update(pptr, Net::Mode::Online));
|
||||
}
|
||||
else
|
||||
{
|
||||
process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
|
||||
process->appendStep(new Update(pptr, Net::Mode::Offline));
|
||||
}
|
||||
|
||||
// if there are any jar mods
|
||||
{
|
||||
auto step = std::make_shared<ModMinecraftJar>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new ModMinecraftJar(pptr));
|
||||
}
|
||||
|
||||
// print some instance info here...
|
||||
{
|
||||
auto step = std::make_shared<PrintInstanceInfo>(pptr, session);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new PrintInstanceInfo(pptr, session));
|
||||
}
|
||||
|
||||
// create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
|
||||
{
|
||||
auto step = std::make_shared<CreateServerResourcePacksFolder>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new CreateServerResourcePacksFolder(pptr));
|
||||
}
|
||||
|
||||
// extract native jars if needed
|
||||
{
|
||||
auto step = std::make_shared<ExtractNatives>(pptr);
|
||||
process->appendStep(step);
|
||||
process->appendStep(new ExtractNatives(pptr));
|
||||
}
|
||||
|
||||
// reconstruct assets if needed
|
||||
{
|
||||
process->appendStep(new ReconstructAssets(pptr));
|
||||
}
|
||||
|
||||
{
|
||||
@@ -845,15 +852,15 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
auto method = launchMethod();
|
||||
if(method == "LauncherPart")
|
||||
{
|
||||
auto step = std::make_shared<LauncherPartLaunch>(pptr);
|
||||
step->setWorkingDirectory(minecraftRoot());
|
||||
auto step = new LauncherPartLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
process->appendStep(step);
|
||||
}
|
||||
else if (method == "DirectJava")
|
||||
{
|
||||
auto step = std::make_shared<DirectJavaLaunch>(pptr);
|
||||
step->setWorkingDirectory(minecraftRoot());
|
||||
auto step = new DirectJavaLaunch(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
step->setAuthSession(session);
|
||||
process->appendStep(step);
|
||||
}
|
||||
@@ -862,8 +869,8 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
|
||||
// run post-exit command if that's needed
|
||||
if(getPostExitCommand().size())
|
||||
{
|
||||
auto step = std::make_shared<PostLaunchCommand>(pptr);
|
||||
step->setWorkingDirectory(minecraftRoot());
|
||||
auto step = new PostLaunchCommand(pptr);
|
||||
step->setWorkingDirectory(gameRoot());
|
||||
process->appendStep(step);
|
||||
}
|
||||
if (session)
|
||||
@@ -885,41 +892,41 @@ JavaVersion MinecraftInstance::getJavaVersion() const
|
||||
return JavaVersion(settings()->get("JavaVersion").toString());
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> MinecraftInstance::loaderModList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::loaderModList() const
|
||||
{
|
||||
if (!m_loader_mod_list)
|
||||
{
|
||||
m_loader_mod_list.reset(new ModList(loaderModsDir()));
|
||||
m_loader_mod_list.reset(new SimpleModList(loaderModsDir()));
|
||||
}
|
||||
m_loader_mod_list->update();
|
||||
return m_loader_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> MinecraftInstance::coreModList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::coreModList() const
|
||||
{
|
||||
if (!m_core_mod_list)
|
||||
{
|
||||
m_core_mod_list.reset(new ModList(coreModsDir()));
|
||||
m_core_mod_list.reset(new SimpleModList(coreModsDir()));
|
||||
}
|
||||
m_core_mod_list->update();
|
||||
return m_core_mod_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> MinecraftInstance::resourcePackList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::resourcePackList() const
|
||||
{
|
||||
if (!m_resource_pack_list)
|
||||
{
|
||||
m_resource_pack_list.reset(new ModList(resourcePacksDir()));
|
||||
m_resource_pack_list.reset(new SimpleModList(resourcePacksDir()));
|
||||
}
|
||||
m_resource_pack_list->update();
|
||||
return m_resource_pack_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<ModList> MinecraftInstance::texturePackList() const
|
||||
std::shared_ptr<SimpleModList> MinecraftInstance::texturePackList() const
|
||||
{
|
||||
if (!m_texture_pack_list)
|
||||
{
|
||||
m_texture_pack_list.reset(new ModList(texturePacksDir()));
|
||||
m_texture_pack_list.reset(new SimpleModList(texturePacksDir()));
|
||||
}
|
||||
m_texture_pack_list->update();
|
||||
return m_texture_pack_list;
|
||||
@@ -934,6 +941,15 @@ std::shared_ptr<WorldList> MinecraftInstance::worldList() const
|
||||
return m_world_list;
|
||||
}
|
||||
|
||||
std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const
|
||||
{
|
||||
if (!m_game_options)
|
||||
{
|
||||
m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt")));
|
||||
}
|
||||
return m_game_options;
|
||||
}
|
||||
|
||||
QList< Mod > MinecraftInstance::getJarMods() const
|
||||
{
|
||||
auto profile = m_components->getProfile();
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
#include <QDir>
|
||||
#include "multimc_logic_export.h"
|
||||
|
||||
class ModList;
|
||||
class ModsModel;
|
||||
class SimpleModList;
|
||||
class WorldList;
|
||||
class GameOptions;
|
||||
class LaunchStep;
|
||||
class ComponentList;
|
||||
|
||||
@@ -17,8 +19,7 @@ class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
|
||||
public:
|
||||
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
|
||||
virtual ~MinecraftInstance() {};
|
||||
virtual void init() override;
|
||||
virtual void saveNow();
|
||||
virtual void saveNow() override;
|
||||
|
||||
// FIXME: remove
|
||||
QString typeName() const override;
|
||||
@@ -41,32 +42,43 @@ public:
|
||||
QString texturePacksDir() const;
|
||||
QString loaderModsDir() const;
|
||||
QString coreModsDir() const;
|
||||
QString modsCacheLocation() const;
|
||||
QString libDir() const;
|
||||
QString worldDir() const;
|
||||
QString resourcesDir() const;
|
||||
QDir jarmodsPath() const;
|
||||
QDir librariesPath() const;
|
||||
QDir versionsPath() const;
|
||||
QString instanceConfigFolder() const override;
|
||||
QString minecraftRoot() const; // Path to the instance's minecraft directory.
|
||||
QString binRoot() const; // Path to the instance's minecraft bin directory.
|
||||
QString getNativePath() const; // where to put the natives during/before launch
|
||||
QString getLocalLibraryPath() const; // where the instance-local libraries should be
|
||||
|
||||
// Path to the instance's minecraft directory.
|
||||
QString gameRoot() const override;
|
||||
|
||||
// Path to the instance's minecraft bin directory.
|
||||
QString binRoot() const;
|
||||
|
||||
// where to put the natives during/before launch
|
||||
QString getNativePath() const;
|
||||
|
||||
// where the instance-local libraries should be
|
||||
QString getLocalLibraryPath() const;
|
||||
|
||||
|
||||
////// Profile management //////
|
||||
std::shared_ptr<ComponentList> getComponentList() const;
|
||||
|
||||
////// Mod Lists //////
|
||||
std::shared_ptr<ModList> loaderModList() const;
|
||||
std::shared_ptr<ModList> coreModList() const;
|
||||
std::shared_ptr<ModList> resourcePackList() const;
|
||||
std::shared_ptr<ModList> texturePackList() const;
|
||||
std::shared_ptr<ModsModel> modsModel() const;
|
||||
std::shared_ptr<SimpleModList> loaderModList() const;
|
||||
std::shared_ptr<SimpleModList> coreModList() const;
|
||||
std::shared_ptr<SimpleModList> resourcePackList() const;
|
||||
std::shared_ptr<SimpleModList> texturePackList() const;
|
||||
std::shared_ptr<WorldList> worldList() const;
|
||||
|
||||
std::shared_ptr<GameOptions> gameOptionsModel() const;
|
||||
|
||||
////// Launch stuff //////
|
||||
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
|
||||
std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
|
||||
QStringList extraArguments() const override;
|
||||
QStringList verboseDescription(AuthSessionPtr session) override;
|
||||
QList<Mod> getJarMods() const;
|
||||
@@ -101,9 +113,6 @@ public:
|
||||
|
||||
virtual JavaVersion getJavaVersion() const;
|
||||
|
||||
signals:
|
||||
void versionReloaded();
|
||||
|
||||
protected:
|
||||
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
|
||||
QStringList validLaunchMethods();
|
||||
@@ -114,11 +123,13 @@ private:
|
||||
|
||||
protected: // data
|
||||
std::shared_ptr<ComponentList> m_components;
|
||||
mutable std::shared_ptr<ModList> m_loader_mod_list;
|
||||
mutable std::shared_ptr<ModList> m_core_mod_list;
|
||||
mutable std::shared_ptr<ModList> m_resource_pack_list;
|
||||
mutable std::shared_ptr<ModList> m_texture_pack_list;
|
||||
mutable std::shared_ptr<ModsModel> m_mods_model;
|
||||
mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_core_mod_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
|
||||
mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
|
||||
mutable std::shared_ptr<WorldList> m_world_list;
|
||||
mutable std::shared_ptr<GameOptions> m_game_options;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;
|
||||
|
||||
@@ -28,7 +28,7 @@ void MinecraftLoadAndCheck::subtaskSucceeded()
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
return;
|
||||
}
|
||||
emitSucceeded();
|
||||
@@ -38,7 +38,7 @@ void MinecraftLoadAndCheck::subtaskFailed(QString error)
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
return;
|
||||
}
|
||||
emitFailed(error);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ class MinecraftLoadAndCheck : public Task
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
|
||||
virtual ~MinecraftLoadAndCheck() {};
|
||||
void executeTask() override;
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2013-2017 MultiMC Contributors
|
||||
/* Copyright 2013-2019 MultiMC Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -37,11 +37,11 @@
|
||||
#include <meta/Index.h>
|
||||
#include <meta/Version.h>
|
||||
|
||||
OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
|
||||
{
|
||||
}
|
||||
|
||||
void OneSixUpdate::executeTask()
|
||||
void MinecraftUpdate::executeTask()
|
||||
{
|
||||
m_tasks.clear();
|
||||
// create folders
|
||||
@@ -83,7 +83,7 @@ void OneSixUpdate::executeTask()
|
||||
next();
|
||||
}
|
||||
|
||||
void OneSixUpdate::next()
|
||||
void MinecraftUpdate::next()
|
||||
{
|
||||
if(m_abort)
|
||||
{
|
||||
@@ -99,10 +99,10 @@ void OneSixUpdate::next()
|
||||
if(m_currentTask > 0)
|
||||
{
|
||||
auto task = m_tasks[m_currentTask - 1];
|
||||
disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
|
||||
disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
|
||||
disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
|
||||
disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
|
||||
disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
|
||||
disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||
disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||
disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
}
|
||||
if(m_currentTask == m_tasks.size())
|
||||
{
|
||||
@@ -113,13 +113,13 @@ void OneSixUpdate::next()
|
||||
// if the task is already finished by the time we look at it, skip it
|
||||
if(task->isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
|
||||
qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
|
||||
next();
|
||||
}
|
||||
connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
|
||||
connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
|
||||
connect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
|
||||
connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
|
||||
connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
|
||||
connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
|
||||
connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
|
||||
connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
|
||||
// if the task is already running, do not start it again
|
||||
if(!task->isRunning())
|
||||
{
|
||||
@@ -127,35 +127,35 @@ void OneSixUpdate::next()
|
||||
}
|
||||
}
|
||||
|
||||
void OneSixUpdate::subtaskSucceeded()
|
||||
void MinecraftUpdate::subtaskSucceeded()
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
|
||||
return;
|
||||
}
|
||||
auto senderTask = QObject::sender();
|
||||
auto currentTask = m_tasks[m_currentTask].get();
|
||||
if(senderTask != currentTask)
|
||||
{
|
||||
qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order.";
|
||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order.";
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
void OneSixUpdate::subtaskFailed(QString error)
|
||||
void MinecraftUpdate::subtaskFailed(QString error)
|
||||
{
|
||||
if(isFinished())
|
||||
{
|
||||
qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
|
||||
return;
|
||||
}
|
||||
auto senderTask = QObject::sender();
|
||||
auto currentTask = m_tasks[m_currentTask].get();
|
||||
if(senderTask != currentTask)
|
||||
{
|
||||
qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order.";
|
||||
qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order.";
|
||||
m_failed_out_of_order = true;
|
||||
m_fail_reason = error;
|
||||
return;
|
||||
@@ -164,7 +164,7 @@ void OneSixUpdate::subtaskFailed(QString error)
|
||||
}
|
||||
|
||||
|
||||
bool OneSixUpdate::abort()
|
||||
bool MinecraftUpdate::abort()
|
||||
{
|
||||
if(!m_abort)
|
||||
{
|
||||
@@ -178,7 +178,7 @@ bool OneSixUpdate::abort()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OneSixUpdate::canAbort() const
|
||||
bool MinecraftUpdate::canAbort() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user