Compare commits

...

497 Commits

Author SHA1 Message Date
Petr Mrázek
19ab1251c2 SCRATCH pass initial parameters to the JavaSettingsWidget from outside 2019-07-13 23:40:28 +02:00
Petr Mrázek
62e1bf327d Merge pull request #2740 from jturnism/patch-1
Update changelog.md
2019-07-12 08:19:20 +02:00
Joseph Turner
280e0e6e36 Update changelog.md
Just installing "qt5-qtbase" on Fedora 30 does not allow MultiMC to run. It still needs "libQt5Widgets.so.5" and by running "dnf whatprovides" it tells me "qt5-qtbase-gui" provides that file and also pulls in "qt5-qtbase" as a dependency if not already installed. I am assuming this is the same situation for CentOS/RHEL
2019-07-11 21:00:55 -05:00
Petr Mrázek
3a67990acd NOISSUE bump deb package version 2019-07-11 01:28:48 +02:00
Petr Mrázek
23eab74e6d NOISSUE make the deb package depend on Qt5 2019-07-11 01:26:33 +02:00
Petr Mrázek
b9d4293552 NOISSUE update component buttons some more when the versions change 2019-07-11 01:01:47 +02:00
Petr Mrázek
5110b58def NOISSUE update version and changelog 2019-07-11 00:35:44 +02:00
Petr Mrázek
791a8227b6 NOISSUE disable component install buttons in impossible cases 2019-07-10 22:30:42 +02:00
Petr Mrázek
725ec35635 NOISSUE recognize curseforge URLs dropped on top of MultiMC 2019-07-09 22:04:52 +02:00
Petr Mrázek
739a86f171 Revert "NOISSUE Import page is now a MultiMC pack page"
This reverts commit f74e3db804.
2019-07-09 21:51:19 +02:00
Petr Mrázek
48b2f95129 Revert "NOISSUE simple/stupid default game options, UI only"
This reverts commit 497d9bec02.
2019-07-09 21:43:12 +02:00
Petr Mrázek
497d9bec02 NOISSUE simple/stupid default game options, UI only 2019-07-09 02:37:04 +02:00
Petr Mrázek
c01d020afc GH-2723 disable deprecation warnings
We are targeting version 5.4 of the Qt ABI.
Deprecations from 2019 are irrelevant.
2019-07-03 01:11:18 +02:00
Petr Mrázek
ee83d432f6 GH-2724 update group view geometries in more cases
Fixes crashes when adding instances to groups that didn't exist before.
2019-07-02 02:09:41 +02:00
Petr Mrázek
8ee11b1a8e GH-2716 do not censor values shorter than 4 in logs 2019-07-01 00:00:34 +02:00
Petr Mrázek
0b86a7ebf3 Merge pull request #2718 from therealfarfetchd/hidpi-icon-fix
Enable HiDPI pixmaps to fix icon scaling for HiDPI displays
2019-06-30 15:39:29 +02:00
Petr Mrázek
63330bf111 NOISSUE connect twitch URL resolving to modpack resolving. works now. 2019-06-30 11:03:59 +02:00
Petr Mrázek
f74e3db804 NOISSUE Import page is now a MultiMC pack page 2019-06-29 01:13:39 +02:00
therealfarfetchd
a55fa04353 Enable HiDPI pixmaps to fix icon scaling for HiDPI displays 2019-06-27 23:04:53 +02:00
Petr Mrázek
fde43c993e NOISSUE add silly twitch URL and CCIP resolving page to 'add instance'
It needs a few more steps and it will handle all kinds of twitch packs.
2019-06-27 03:20:11 +02:00
Petr Mrázek
917f148fc4 NOISSUE add support for 'experiment' Minecraft versions 2019-06-26 20:51:04 +02:00
Petr Mrázek
34611c00e3 Merge branch 'feature/update_translation_fix' into develop 2019-06-25 23:41:16 +02:00
Petr Mrázek
44a7c5867b Merge pull request #2703 from Janrupf/feature/apply_proxy_settings
GH-2499 Apply proxy settings immediately
2019-06-23 21:38:30 +02:00
Petr Mrázek
75ddbc8851 Merge pull request #2705 from Janrupf/feature/fix_external_deletion_interaction
GH-2515 Save instance ID before display dialog
2019-06-23 21:31:56 +02:00
Petr Mrázek
2f1d31cf43 Merge pull request #2706 from Janrupf/feature/fix_hashtag_in_notes
Feature/fix hashtag in notes
2019-06-23 21:19:10 +02:00
Petr Mrázek
e7c5b266c8 Merge pull request #2708 from Janrupf/feature/single_imgur_uploads
GH-689 Don't create album for single screenshot
2019-06-23 21:15:06 +02:00
Petr Mrázek
384979bf94 Merge pull request #2704 from Janrupf/feature/autoselect_new_instances
GH-2592 Autoselect newly created instances
2019-06-23 19:58:21 +02:00
janrupf
b5a16935b7 NOISSUE Renaming for better understanding 2019-06-23 14:54:17 +02:00
janrupf
320637e8dc GH-1701 Check job size for translation 2019-06-22 01:54:08 +02:00
janrupf
77f3f028fa GH-2499 Apply proxy settings immediately 2019-06-22 01:48:37 +02:00
janrupf
2a96e16902 GH-689 Don't create album for single screenshot 2019-06-22 01:47:07 +02:00
janrupf
1ed84eddd5 GH-2515 Save instance ID before display dialog 2019-06-21 23:55:16 +02:00
janrupf
7b52b8689b NOISSUE Test comment escaping with unit tests 2019-06-21 23:46:54 +02:00
janrupf
d21700ee91 NOISSUE Revert INI parser back to single pass 2019-06-21 23:46:54 +02:00
janrupf
f87c890912 GH-1813 Escape # in INI (and better reader) 2019-06-21 23:46:54 +02:00
janrupf
306b98edac GH-2592 Autoselect newly created instances 2019-06-21 22:38:26 +02:00
Petr Mrázek
ce12f1a734 Merge pull request #2694 from Janrupf/fix-disabled-mods
Operate on copy of QString instead of appending
2019-06-17 23:21:26 +02:00
janrupf
c1953739d9 NOISSUE Don't append .disabled to string 2019-06-17 15:49:26 +02:00
Petr Mrázek
e8bf9cef24 Merge branch 'develop' of https://github.com/lelandliu/MultiMC5 into develop 2019-06-15 22:47:36 +02:00
Petr Mrázek
8aa4b9dac5 NOISSUE limit fabric intermediary selection options in UI by minecraft version 2019-06-15 22:45:56 +02:00
Petr Mrázek
4836ba22cd NOISSUE fix fabric tooltip string 2019-06-15 22:29:34 +02:00
Petr Mrázek
6c30076b6c GH-2639 Add simple fabric loader installation support 2019-06-15 21:25:23 +02:00
Petr Mrázek
83c48a0f1a Merge pull request #2690 from asiekierka/fabric-modparse
Add Fabric mod JSON parsing support
2019-06-15 20:41:42 +02:00
asie
d251097545 fix author name parsing 2019-06-15 16:36:13 +02:00
asie
c35dbd972e Add Fabric mod JSON parsing support 2019-06-15 13:54:20 +02:00
Leland Liu
0c5d121452 Fixed adding a disabled duplicate 2019-06-14 15:48:24 -04:00
Petr Mrázek
53ad5cb436 Merge pull request #2684 from isoraqathedh/patch-1
Pass command line arguments through to the executable
2019-06-08 15:53:01 +02:00
isoraqathedh
d87bd4b588 Update run.sh
Pass command line arguments through to the executable
2019-06-08 21:21:22 +08:00
Petr Mrázek
86850ef5d0 NOISSUE fix macOS build, remove bundled dependencies on linux
Your copy of MultiMC might stop working after this update
because we no longer bundle Qt and other system libraries.

Contact us at https://discord.gg/0k2zsXGNHs0fE4Wm if
you need help with installing Qt.

Qt 5.4.x or newer is required.
2019-06-08 15:08:24 +02:00
Petr Mrázek
30fba4d407 NOISSUE make the global settings button context sensitive 2019-06-01 18:05:42 +02:00
Petr Mrázek
932160818e NOISSUE add option to open global settings from instance settings
This should hopefully giude people towards using the right thing.
2019-06-01 12:28:53 +02:00
Petr Mrázek
59e1ed3d87 NOISSUE add the pocket fox icon 2019-06-01 00:59:02 +02:00
Petr Mrázek
3470a3df96 NOISSUE improve icon handling while importing and exporting instances
Now it handles formats other than png.
2019-05-31 21:53:58 +02:00
Petr Mrázek
61913daaf3 Merge pull request #2603 from AlexTMjugador/develop
Support launching profiler in offline mode
2019-04-14 23:34:10 +02:00
Alejandro González
cb71dfa605 Tweak strings as per peterix review 2019-04-14 23:02:01 +02:00
AlexTMjugador
2be98baaba Conform to existing code style 2019-04-15 00:44:59 +02:00
AlexTMjugador
0ce637bf0e Support launching profiler in offline mode
This commit adds the necessary GUI controls to let the user choose
whether they want to launch Minecraft in offline mode with a configured profiler.
2019-04-15 00:42:06 +02:00
Petr Mrázek
70ed30f9e6 GH-2591 less std::shared_ptr and more shared_qobject_ptr
This eliminates some weird crashes.
2019-04-07 23:59:04 +02:00
Petr Mrázek
414946cad9 Merge remote-tracking branch 'origin/feature/trim_server_details' into develop 2019-04-02 23:39:13 +02:00
Elise
1fc199697c GH-2551 Trim at serialization 2019-03-29 11:25:37 -04:00
Petr Mrázek
9292d846b6 Merge pull request #2565 from MultiMC/feature/deletion_harder
GH-2487 Make deleting instances harder
2019-03-23 14:23:45 +01:00
Elise
22db95ce3b Merge branch 'develop' into feature/deletion_harder 2019-03-19 16:29:40 -04:00
Elise
597fe50d37 GH-2551 Trim server name and IP strings 2019-03-19 13:28:11 -04:00
Petr Mrázek
bf93ba02e9 NOISSUE fix metadata URL 2019-03-08 02:04:08 +01:00
Petr Mrázek
07c1685ff1 NOISSUE create translations folder before starting to watch it for changes
Fixes issue where on first run, the translations don't show up.
2019-03-08 01:21:04 +01:00
Elise
a5e531d4f1 GH-2487 Make NO the default button 2019-02-26 16:56:08 -05:00
Petr Mrázek
ae956d8fd6 NOISSUE update changelog 2019-02-21 00:45:36 +01:00
Petr Mrázek
73e487a31e NOISSUE hide the game options screen again 2019-02-21 00:44:36 +01:00
Petr Mrázek
0b95d2b03f NOISSUE fix build 2019-02-19 01:11:54 +01:00
Petr Mrázek
9c82adaee5 GH-2209 Fix sounds in old (pre-1.6) versions 2019-02-19 01:00:03 +01:00
Petr Mrázek
0a99d037c4 Merge pull request #2532 from andersmmg/patch-1
Update title of Add Empty dialog
2019-02-05 08:44:07 +01:00
Joshua Anderson
6e8e4c5dfa Update title of Add Empty dialog 2019-02-05 00:06:29 -07:00
Petr Mrázek
e4599ee2d1 NOISSUE fix builds? 2019-01-30 01:12:21 +01:00
Petr Mrázek
62c9fcdc6c NOISSUE first step towards having game options management 2019-01-30 00:35:24 +01:00
Petr Mrázek
c1ea42d3d9 Merge branch 'stable' of https://github.com/Scotsguy/MultiMC5 into develop 2019-01-17 00:51:54 +01:00
AppleTheGolden
437dec91f9 Update Copyright Year 2019-01-16 21:14:24 +01:00
Petr Mrázek
7436c94976 NOISSUE Replace Quality with Completeness in language widget
Completeness does no imply quality.
2019-01-14 01:36:04 +01:00
Petr Mrázek
c08053d8b8 NOISSUE split out language selection widget, use it in settings 2019-01-09 04:38:35 +01:00
Petr Mrázek
e71786d7b9 NOISSUE language selection wizard improvements
Same needs to be applied to the application settings later.
2019-01-08 02:20:36 +01:00
Petr Mrázek
819503d530 NOISSUE bump version to 0.6.5 2019-01-06 22:56:05 +01:00
Petr Mrázek
d6ace374d8 NOISSUE update changelog some more 2019-01-06 22:54:00 +01:00
Petr Mrázek
6a21c043ce NOISSUE bump version to 0.6.4 and update changelog 2019-01-06 22:28:27 +01:00
Petr Mrázek
4474d269cc NOISSUE granular model updates for language model 2019-01-06 22:14:13 +01:00
Petr Mrázek
ec2732ccd1 NOISSUE update FTB URLs 2019-01-04 01:48:36 +01:00
Petr Mrázek
4b7971f60f NOISSUE hotloading of translations and use of local PO files
The hotloading is still inefficient
2019-01-02 01:41:07 +01:00
Petr Mrázek
4cbd1a7692 Fix version number 2018-12-26 02:44:07 +01:00
Petr Mrázek
f481940aeb NOISSUE fix up changelog formatting 2018-12-26 01:56:25 +01:00
Petr Mrázek
037fb6fdb7 NOISSUE update the changelog 2018-12-26 01:54:37 +01:00
Petr Mrázek
3e60e770b5 NOISSUE just don't use std::abs, it doesn't work 2018-12-24 14:49:53 +01:00
Petr Mrázek
70052b180c NOISSUE maybe fix macos build, pngcrush images 2018-12-24 14:31:34 +01:00
Petr Mrázek
2e58429b6a NOISSUE kitty cat in a silly hat! 2018-12-24 03:26:14 +01:00
Petr Mrázek
56a9b65efb NOISSUE add missing Q_OBJECT macros 2018-12-23 01:05:11 +01:00
Petr Mrázek
4a4ba954ed GH-2488 fix Qt's relative URL redirect problems some more 2018-12-14 02:48:55 +01:00
Petr Mrázek
14bb666a20 GH-2485 fix crash bug while creating instances 2018-12-14 01:18:18 +01:00
Petr Mrázek
075e173fbd NOISSUE fix build 2018-12-12 00:05:53 +01:00
Petr Mrázek
3fe9165201 NOISSUE fix logging for gametype 2018-12-11 23:54:29 +01:00
Petr Mrázek
13b293dd65 GH-2374 fix missing alternating backgrounds in worlds, add gametype column 2018-12-11 23:53:14 +01:00
Petr Mrázek
de568b32b8 NOISSUE model Task states as one enum instead of multiple flags
This adds Task::State::AbortedByUser as a possibility
2018-12-10 20:50:15 +01:00
Petr Mrázek
fb29e45bd0 NOISSUE make instance creation task abortable
This may or may not expose issues when it actually IS used.
2018-12-10 20:49:21 +01:00
Petr Mrázek
3018310be3 NOISSUE fix typo 2018-12-06 00:36:31 +01:00
Petr Mrázek
f111d1bc0c GH-2483 fix minor issue while checking for missing local files 2018-12-06 00:33:49 +01:00
Petr Mrázek
0ee915200b GH-2478 fix jarmods being detected as missing 2018-11-26 09:57:51 +01:00
Petr Mrázek
9eb456336d NOISSUE fix unit tests 2018-11-26 03:22:20 +01:00
Petr Mrázek
3f6aecf5a2 GH-2475 fix reporting missing local libraries on launch 2018-11-26 03:06:58 +01:00
Petr Mrázek
e8c382bede GH-2101 select everything when editing instance names 2018-11-23 21:47:23 +01:00
Petr Mrázek
54e857a7f5 NOISSUE proper inline editable instance names
Pretty!
2018-11-23 04:56:56 +01:00
Petr Mrázek
74c598d756 GH-2101 fix enter and double click activation of instances 2018-11-22 02:02:53 +01:00
Petr Mrázek
c214c13fb3 GH-2101 POC for inline renaming 2018-11-22 01:50:32 +01:00
Petr Mrázek
c4a472981f GH-2467 remove unused sorting indicators from version page 2018-11-21 00:55:40 +01:00
Petr Mrázek
33a7cc1890 NOISSUE fix up mod installation and add a lot of logging for it 2018-11-21 00:29:41 +01:00
Petr Mrázek
a8e77f0ecc NOISSUE remove some nonsense and dead code 2018-11-15 00:36:47 +01:00
Petr Mrázek
5603133822 GH-2384 when adding mods with the same filename, rotate the files
Current will be disabled and renamed to '$name-old'.
Old one, if present, will be removed.
2018-11-12 02:39:52 +01:00
Petr Mrázek
47b1f9a860 NOISSUE fix up unit test for the changed Library behavior
No more fallback for local libraries. They must be in the instance, always.
2018-11-12 02:02:07 +01:00
Petr Mrázek
0f0dc30729 GH-2144 Fix translatable string 2018-11-12 01:57:26 +01:00
janrupf
1648b34aed GH-1552 Hide PermGen when using an autodetected java version greater than 8 2018-11-12 01:50:04 +01:00
janrupf
4cc7329ce3 GH-2144 Append '(installed)' to the installed version name 2018-11-12 01:49:52 +01:00
janrupf
16df8d7b88 GH-2384 Replace existing mod files 2018-11-12 01:49:35 +01:00
janrupf
3d630c7856 GH-2452 Ask before deleting group 2018-11-12 01:48:53 +01:00
janrupf
51a6883737 NOISSUE Ignore stuff from CLion 2018-11-12 01:48:38 +01:00
Petr Mrázek
d6367407b0 NOISSUE Rename OneSixUpdate to MinecraftUpdate 2018-11-11 23:55:50 +01:00
Petr Mrázek
defa911705 NOISSUE fix groups not being updated in UI correctly
The model was not sending the appropriate signals.
2018-11-11 23:54:16 +01:00
Petr Mrázek
17e09a292d NOISSUE read local libraries only from the local location
This removes the fallback to global `libraries` folder for `local` libraries.
2018-11-11 23:50:36 +01:00
Petr Mrázek
8a7f1e405f NOISSUE take forge xz download url base from the metadata file
Instead of hardcoding it.
2018-11-04 13:41:21 +01:00
Petr Mrázek
58260da861 NOISSUE remove use of obsolete URL constants, simplify the rest 2018-11-04 13:18:35 +01:00
Chris Lane
16cc20aefd NOISSUE fix 404 with liteloader https url 2018-11-02 12:59:37 +00:00
Chris Lane
0572a1e4e6 NOISSUE use https more widely 2018-11-02 12:04:08 +00:00
Petr Mrázek
9b74e73ad3 NOISSUE use https for downloading assets 2018-11-02 10:28:50 +01:00
Petr Mrázek
24fd2918c7 NOISSUE fix more rhel 7.6 problems? 2018-11-01 23:30:13 +01:00
Petr Mrázek
9eb165bfee iNOISSUE fix build issue with pack200 on rhel 7.6 2018-11-01 22:08:15 +01:00
Petr Mrázek
e4ce74e622 GH-2382 fix exact version filter not being exact... 2018-11-01 00:34:31 +01:00
Petr Mrázek
59e2f52db7 GH-2238 fix issues with whitespace/newlines in folder and instance names 2018-11-01 00:18:49 +01:00
Petr Mrázek
d5037d4f79 GH-2412: collect dead processes on linux properly
Issues were caused by use of `popen()` with no `pclose()` counterpart...
2018-10-31 22:44:23 +01:00
Petr Mrázek
aef0ccb1a2 GH-2232 add gif icon support (not animated) 2018-10-31 21:54:22 +01:00
Noah
d9c1fc09e7 add game and minecraft keywords 2018-10-31 20:40:19 +01:00
Noah
8b56d77f5e add minecraft keyword 2018-10-31 20:40:19 +01:00
Petr Mrázek
e3ab393cec NOISSUE make LaunchStep::bind private
Static analysis was complaining about it.
2018-10-31 00:04:21 +01:00
Petr Mrázek
2c03c67c36 NOISSUE stop mentioning the translations server in README 2018-09-10 03:37:35 +02:00
Petr Mrázek
a279df8bda NOISSUE fix build on linux? 2018-08-02 01:12:41 +02:00
Petr Mrázek
5f2d3f014a NOISSUE get rid of remaining tabs 2018-08-02 01:01:55 +02:00
Jannis Lübke
6aada8adf7 NOISSUE FTB pack code implementation, cleaned up 2018-08-02 00:52:31 +02:00
Petr Mrázek
6cee50eac6 NOISSUE gate new mods page behind cheat code 2018-08-01 20:05:18 +02:00
Petr Mrázek
9cc93ae81d NOISSUE fix crash bug caused by urge to maker code pretty
Also added a note so this doesn't happen again...
2018-07-31 02:20:04 +02:00
Petr Mrázek
0c73ddee73 NOISSUE set groups for instances by not setting groups for instances
So simple. Better in every way.
2018-07-31 01:54:08 +02:00
Petr Mrázek
9965decd81 NOISSUE squish. 2018-07-28 22:12:57 +02:00
Petr Mrázek
9e7cdbfe11 NOISSUE pretty multi-line formatting... 2018-07-28 22:12:57 +02:00
Petr Mrázek
76d6ec91a4 NOISSUE simplify. 2018-07-28 22:12:57 +02:00
Petr Mrázek
7b439c85c0 SCRATCH things and stuff, related to grou saving 2018-07-28 22:12:57 +02:00
Petr Mrázek
12f2716f31 GH-2355 Do not allow instances to be created with whitespace prefix or suffix 2018-07-28 22:08:09 +02:00
Petr Mrázek
4169f53b19 NOISSUE fix build on macOS? 2018-07-28 00:00:04 +02:00
Petr Mrázek
e4c33458f2 GH-2352 Add Minecraft folder button for instances, rearrange buttons by importance 2018-07-27 23:57:09 +02:00
Petr Mrázek
14f85813c8 NOISSUE hide the new mods page 2018-07-22 09:06:31 +02:00
Petr Mrázek
bbb3b3e6f6 NOISSUE tabs -> spaces 2018-07-15 14:51:05 +02:00
Petr Mrázek
03280cc62e NOISSUE separate new mods model from the simple one
It should list mods in various locations...
2018-07-15 14:04:09 +02:00
Petr Mrázek
128bce6acb SCRATCH second mods page, placeholder 2018-07-15 12:48:41 +02:00
Petr Mrázek
8108c61745 NOISSUE fix unitialized data warning on Arch Linux 2018-07-06 19:46:28 +02:00
Petr Mrázek
a222c94d34 NOISSUE add cancel button to the new instance dialog 2018-06-30 19:44:30 +02:00
Petr Mrázek
a872085f9a NOISSUE remove refresh button from servers page 2018-06-30 19:44:21 +02:00
Petr Mrázek
8516a6646e NOISSUE fix saving the servers.dat file when it doesn't exist yet 2018-06-30 01:27:44 +02:00
Petr Mrázek
44381c09d7 NOISSUE more warnings 2018-06-28 23:51:26 +02:00
Petr Mrázek
bb599abf59 NOISSUE fix a bug with mutexes on Windows, more warnings 2018-06-28 23:42:44 +02:00
Petr Mrázek
07f7ec8eef NOISSUE fix some warnings so builds can go further 2018-06-28 23:35:04 +02:00
Petr Mrázek
7fe94ca7b4 NOISSUE fix all sorts of warnings, enable Werror and pedantic 2018-06-28 23:18:45 +02:00
Petr Mrázek
b5f636b3d5 NOISSUE do not keep downloads in memory, add (some) missing virtual dtors 2018-06-28 21:34:56 +02:00
Petr Mrázek
19bb50b872 NOISSUE sync up quazip merge commit 2018-06-05 01:01:04 +02:00
Sergey Shatunov
4d68c1b509 GH-2291 Fix build with Qt 5.11+ 2018-06-02 17:22:43 +07:00
Petr Mrázek
f0ff2db4e1 GH-2277 fix even more exception catches by value 2018-05-20 01:53:05 +02:00
Charles Milette
72c0002b45 Catch C++ exceptions by const reference
Fixes #2277
2018-05-19 19:18:26 -04:00
Petr Mrázek
b9fd381eee GH-2268 update the deb package to not depend on latest jre
* `openjdk-8-jre` is now recommended
* the package now depends on `desktop-file-utils`
* the desktop icon has been updated to the current one
2018-05-17 23:14:12 +02:00
Petr Mrázek
584f1e89b9 GH-2252 make URL drop signal use a queued connection instead of direct call
Hopefully this will fix the issues with stuck applications on Windows.
2018-04-23 08:45:58 +02:00
Petr Mrázek
72ff342d63 GH-2053 basics of the servers.dat management 2018-04-23 02:05:22 +02:00
Petr Mrázek
6284f070c1 NOISSUE update changelog for 0.6.2 2018-04-08 23:43:03 +02:00
Petr Mrázek
be9063317e NOISSUE hide the twitch platform page 2018-04-08 21:22:41 +02:00
Petr Mrázek
8ec36e2cb9 NOISSUE bump release version to 0.6.2 2018-04-08 21:22:07 +02:00
Petr Mrázek
8cefc76108 NOISSUE show release dates of the meta versions in version picker views 2018-04-08 19:49:02 +02:00
Petr Mrázek
172f83c7e2 NOISSUE and even more bad includes 2018-04-07 22:45:03 +02:00
Petr Mrázek
b1e0cbf852 NOISSUE add more missing includes 2018-04-07 22:42:01 +02:00
Petr Mrázek
9b7f82ff26 NOISSUE fix build problem with missing <functional> include 2018-04-07 22:36:57 +02:00
Petr Mrázek
67cef79d81 NOISSUE add logging to zip subfolder extraction 2018-04-07 22:33:26 +02:00
Janrupf
7e1c5d439a #2228, #2229 - Auto import pack icons and fixed to big version selection - Closes #2228, Closes #2229 2018-04-07 22:09:19 +02:00
Janrupf
38ed0c2a1f NOISSUE Fixed ftb downloads always latest version 2018-04-07 00:46:49 +02:00
Lukas Haigner
9b7564e967 NOISSUE Fixed progress dialog 2018-04-07 00:46:49 +02:00
Petr Mrázek
15926b2b4a NOISSUE make FTB pack selection fancier 2018-04-06 21:59:04 +02:00
Petr Mrázek
6323aae56f NOISSUE move FtbListModel to where it is actually used 2018-04-06 21:04:34 +02:00
Petr Mrázek
c9c6037f50 NOISSUE fix Qt moc warning 2018-04-06 21:02:37 +02:00
Janrupf
97b74ef56a NOISSUE Fixed code for PR 2018-04-06 19:39:12 +02:00
Janrupf
df6e66101c NOISSUE Added 3rd party pack support 2018-04-05 19:33:31 +02:00
Janrupf
bbd523acb8 NOISSUE Added FTB Pack logos to chooser and fixed some missing includes 2018-04-02 23:02:33 +02:00
Janrupf
67d2f283da NOISSUE Fixed compilation error, but needs to be revisited 2018-04-02 23:02:33 +02:00
Petr Mrázek
4530d9064b NOISSUE fix latent bugs in RWStorage 2018-04-02 22:58:54 +02:00
Petr Mrázek
3406335cd8 GH-2219 fix crash and bad view scaling in Java VersionSelectDialog 2018-04-02 22:58:02 +02:00
Petr Mrázek
c9832d0d86 GH-2208 fix FTB pack download caching 2018-03-29 20:55:47 +02:00
Petr Mrázek
fa8f19b046 GH-2211 fix crash in first launch wizard 2018-03-29 20:41:16 +02:00
Petr Mrázek
370bbada87 NOISSUE polish the new instance UI a bit more 2018-03-28 21:57:41 +02:00
Petr Mrázek
1ef416cb56 NOISSUE add pointless fun things, because. 2018-03-28 00:51:24 +02:00
Petr Mrázek
b46a34d0ae NOISSUE make vanilla refresh button work 2018-03-27 23:19:29 +02:00
Petr Mrázek
6188c577e3 NOISSUE fix page container spacing in UIs 2018-03-27 22:21:41 +02:00
Petr Mrázek
40a30b67f4 NOISSUE save the new instance dialog geometry when the dialog is accepted 2018-03-27 22:02:57 +02:00
Petr Mrázek
12b304ea73 NOISSUE fix crash in NewInstanceDialog 2018-03-27 21:05:41 +02:00
Petr Mrázek
8e44ab2338 NOISSUE redo new instance dialog 2018-03-27 09:25:36 +02:00
Petr Mrázek
4c7ea0f99a NOISSUE explain the custom commands better 2018-03-26 22:08:55 +02:00
Petr Mrázek
a1c713811c NOISSUE preserve minecraft.jar while migrating Legacy instances
It can be manually modded. It must be preserved when it's the only jar around.
2018-03-23 23:39:18 +01:00
Petr Mrázek
106155dd62 NOISSUE move modpack platform related files to 'modplatform' subfolders 2018-03-16 23:33:58 +01:00
Petr Mrázek
303842a19e NOISSUE Add Konami Code
Fun little thing for hiding extra debug options in the future.
2018-03-15 09:27:45 +01:00
Petr Mrázek
ea151ca9d4 NOISSUE pre-fill analytics ID and paste.ee API key for all new builds
This means custom builds now get the option of using analytics and
log upload without users having to fill in IDs.
2018-03-13 08:02:11 +01:00
Petr Mrázek
6e69370fbf NOISSUE disable useless broken unit test to fix win32 and osx64 builds 2018-03-13 01:41:33 +01:00
Petr Mrázek
82208be49e NOISSUE add linux distro name and release stats to analytics
Hopefully this can serve as some sort of guideline for focusing
effort towards the right distro packages to make.
2018-03-13 00:28:51 +01:00
Petr Mrázek
b497aee920 NOISSUE update logo in README.md 2018-03-12 22:53:10 +01:00
Janrupf
0812e3a87b NOISSUE Fixed code for PR 2018-03-12 15:09:07 +01:00
Janrupf
b8ca36372b GH-2124 First complete implementation, installing is working now! GH-2172 Added sorting 2018-03-11 19:30:47 +01:00
Petr Mrázek
2d295d5afb Merge pull request #1911 from maxnordlund/patch-1
Mention casing in ISSUE_TEMPLATE
2018-03-01 17:25:09 +01:00
Janrupf
ab3fe74c97 Added FTB pack selection ad download, WIP 2018-02-28 19:43:56 +01:00
Petr Mrázek
1a43f28297 NOISSUE do not censor player name in logs 2018-02-18 19:27:01 +01:00
Petr Mrázek
093dd22826 GH-2154 Ignore 'hidden' flag of insttance folders 2018-02-18 16:08:11 +01:00
Petr Mrázek
06ccff0f47 GH-2150 Separate Java settings UI used in the wizard into a widget 2018-02-18 12:39:35 +01:00
Petr Mrázek
65bca65489 GH-2150 Split out custom commands into a custom widget
Now it is used from a global page and from a sub-page in the instance settings.
2018-02-17 00:57:54 +01:00
Petr Mrázek
a7957f24ba GH-2134 Totally overengineer skin upload input validation
* It autocorrects local paths and file:// URLs to valid local paths.
* It recognizes other URL schemes as 'remote' and will show an error for them.
* The error dialogs have been fixed (they all had titles and content swapped).
2018-02-15 00:40:23 +01:00
Petr Mrázek
2ea22d407d GH-604 use the same font for 'Other Logs' as for the main log
This doesn't mean coloring, just the same font and font size.
2018-02-14 21:37:32 +01:00
Petr Mrázek
22b32fce12 GH-2143 Switch Mojang status icons to current set of services
This also removes the Web and Account web icons, because they are simply
not relevant to MultiMC (it does not use those). You can always
check their status by opening them in a browser.
2018-02-13 21:35:05 +01:00
Petr Mrázek
2c219df061 NOISSUE clean up and fix win32 includes in FileSystem implementation 2018-02-11 01:29:43 +01:00
Petr Mrázek
604295e6d5 NOISSUE fix some warnings 2018-02-11 01:21:32 +01:00
Petr Mrázek
f259e9f727 NOISSUE update copyright dates 2018-02-11 00:40:01 +01:00
Petr Mrázek
38e669dbf5 NOISSUE change FS::updateTimestamp to work with directories too, use it to fix icon issues on macOS 2018-02-11 00:35:56 +01:00
Petr Mrázek
ca11765436 NOISSUE split logo into 'logo' and 'multimc' 2018-02-10 19:13:59 +01:00
Petr Mrázek
354ed2e7f0 NOISSUE version the discord variant of the icon 2018-02-10 16:13:05 +01:00
Petr Mrázek
a08afb8172 NOISSUE update the MultiMC logo 2018-02-10 16:09:14 +01:00
Petr Mrázek
2dac9d02d8 GH-2134 fix model selection when uploading a skin 2018-02-10 11:54:59 +01:00
Petr Mrázek
b3fb437f8e NOISSUE When changing version of or installing a package, remove customized version 2018-02-09 00:54:17 +01:00
Petr Mrázek
f115bdf5b8 NOISSUE make visualvm work with relative paths (inside the MultiMC folder) 2018-02-06 01:51:22 +01:00
Petr Mrázek
41aef8414a NOISSUE add an 'open folder' button to the icon dialog 2018-02-05 02:01:12 +01:00
Petr Mrázek
83649b5d52 NOISSUE implement basic search in Other Logs page 2018-02-05 01:40:38 +01:00
Petr Mrázek
595d157180 NOISSUE fix changelog typo 2018-01-30 00:20:27 +01:00
Petr Mrázek
3843a4ddb9 NOISSUE change version to 0.6.1 and update the changelog 2018-01-29 23:56:03 +01:00
Petr Mrázek
088e8e0eff NOISSUE remove unneeded URL fixing code and fix up the exception thrown by invalid Flame URLs 2018-01-29 00:47:18 +01:00
Petr Mrázek
418251bd86 NOISSUE use a variable for binary build definitions 2018-01-28 19:04:39 +01:00
Petr Mrázek
0bcb24502e GH-2119 Update group view scrollbar when the size of rows doesn't change
Previously, it would only update when you resize the window horizontally
enough to change the number of icons that fit in a row.
2018-01-28 02:04:47 +01:00
Petr Mrázek
3277b820a7 NOISSUE fix the macOS bundle utilities problem 2018-01-27 23:53:10 +01:00
Petr Mrázek
d66ae206dd NOISSUE move bundle utilities magic back to the application folder
This may fix macOS issues?
2018-01-27 21:59:06 +01:00
Petr Mrázek
cd55674b36 NOISSUE do not install .a files for shared libraries on Windows 2018-01-27 02:42:27 +01:00
Petr Mrázek
166e5a03d6 NOISSUE rearrange build system
* Added install commands to the libraries instead of force installing files
* Most of the application cmake stuff moved to top level
* RPATH should now be set/cleared correctly
* Contains a fix for GH-1780
2018-01-27 02:00:20 +01:00
Petr Mrázek
0c2e2094ee NOISSUE clean up download redirects and handle their errors as fatal 2018-01-22 03:09:00 +01:00
Petr Mrázek
c33b4e252f NOISSUE fix bad redirect URLs provided by the curse CDN
MultiMC now parses the HTTP Location header in a (more) tolerant mode.
2018-01-21 03:49:54 +01:00
Samuel Rakitničan
0942867ecc GH-2103 Make /usr/local the default prefix for lin-system
/usr/local is a sane default since /usr is meant to be used by packages.
2018-01-18 12:09:03 +01:00
Petr Mrázek
e8336babad NOISSUE fix the installation prefix mess 2018-01-16 07:08:59 +01:00
Petr Mrázek
d0e58acd84 GH-2103 add suggested changes from the pull request 2018-01-16 06:48:10 +01:00
Carl Philipp Reh
360d877abf GH-2103 Take CMAKE_INSTALL_PREFIX into account in MULTIMC_JARS_LOCATION
When installing MultiMC with the lin-system layout and specifying an
install prefix that is not the empty string, then MultiMC looks for its
Jars in the wrong location. Fix this by appending CMAKE_INSTALL_PREFIX.
2018-01-16 06:47:58 +01:00
srakitnican
7ea1d68244 GH-2102 multimc.desktop: Remove deprecated entries 2018-01-14 08:23:17 +01:00
Petr Mrázek
b7f28a92d4 NOISSUE change default install layout on linux to lin-nodeps
This avoids issues with included bundle utilities on certain systems
and is a step in the intended direction (not distributing dependencies).
2018-01-08 01:42:50 +01:00
Petr Mrázek
1dbc4e16f7 NOISSUE remove the jar-modded jar after the instance finishes 2018-01-08 00:59:47 +01:00
Petr Mrázek
0636c03d7c GH-2087 remove the revert to vanilla functionality, add file download button to version page 2018-01-05 04:26:46 +01:00
Petr Mrázek
ee341b78ba GH-2089 update wording of the instance delete confirmation dialog 2018-01-04 04:16:31 +01:00
Petr Mrázek
9510a1bbf2 NOISSUE stop logging process environment and MC launch script 2018-01-04 03:50:05 +01:00
Petr Mrázek
03a93b8e4a NOISSUE Add mention of Nikki's and Dries007's work on CurseMeta 2018-01-03 02:04:23 +01:00
Petr Mrázek
b08ec1e97a NOISSUE fix changelog typos 2017-12-31 19:39:46 +01:00
Petr Mrázek
d7ed472e68 NOISSUE update changelog for 0.6.0 2017-12-31 07:21:14 +01:00
Petr Mrázek
7cd13302c5 NOISSUE only show pack import warnings when there are some 2017-12-31 07:20:28 +01:00
Petr Mrázek
4340068a84 NOISSUE add button for creating empty, properly registered, components 2017-12-31 01:37:50 +01:00
Petr Mrázek
c7032ce68a NOISSUE add missing QStringList include 2017-12-30 19:02:09 +01:00
Petr Mrázek
58ead6a1f4 NOISSUE handle 'folder' Flame packages by ignoring them, show warnings for minor Flame import problems 2017-12-30 18:57:46 +01:00
Petr Mrázek
5937b1c3d4 NOISSUE make the patreon button text slightly shorter
"Support MultiMC" instead of "Support us on Patreon!"
2017-12-30 05:18:09 +01:00
Petr Mrázek
6c30c84b11 NOISSUE switch MultiMC to the v1 meta endpoint 2017-12-29 03:32:22 +01:00
Petr Mrázek
75c0046f41 NOISSUE initial meta version will be 1, map 0 to 1
Metadata for version 1 will be at v1.meta.multimc.org
2017-12-29 01:51:00 +01:00
Petr Mrázek
719f3e863a NOISSUE add versioning to component metadata format and use it 2017-12-29 00:37:14 +01:00
Petr Mrázek
50ca6cbb4d NOISSUE fix crash bug in version page of instances
This was caused by generation of temporary component objects
when no such thing should have been happening.
2017-12-29 00:35:10 +01:00
Petr Mrázek
257f8ca9fd NOISSUE fix typo when deleting log files
"Do you really want to these files?" was missing "delete".
2017-12-25 13:06:38 +01:00
Petr Mrázek
642c3f1d09 NOISSUE sort export dialog contents in ascending order by default 2017-12-21 01:25:43 +01:00
Petr Mrázek
c2726037ce NOISSUE add missing world and help icons 2017-12-19 01:49:46 +01:00
Petr Mrázek
9eb0525dab NOISSUE preserve log page checkbox state when the instance window is closed
Only for a single session, not between sessions.
2017-12-18 01:19:43 +01:00
Petr Mrázek
fb7897a6f4 NOISSUE remopve instance settings button from the main window
This should steer new users to the main settings even more, while keeping
the instance settings still available.
2017-12-17 22:17:29 +01:00
Petr Mrázek
5858483592 NOISSUE ignore merge commits in dev build changelog 2017-12-17 21:08:20 +01:00
Petr Mrázek
6fd91be137 NOISSUE Revert "Removed workflowy link and info because no one uses it"
This reverts commit 16e390d2e7.
2017-12-17 21:07:34 +01:00
ask6155
245a19e9df Merge pull request #2069 from ask6155/develop
Removed workflowy link and info because no one uses it
2017-12-15 17:20:05 +05:30
ask6155
16e390d2e7 Removed workflowy link and info because no one uses it 2017-12-14 20:40:02 +05:30
Petr Mrázek
daf9d0eaa7 NOISSUE do not override already loaded metadata entities with partial data 2017-12-14 02:22:20 +01:00
Petr Mrázek
f18afd3d1e NOISSUE fix a bunch of warnings thrown by Qt internals
Badly connected signals/slots and similar things.
2017-12-14 00:29:00 +01:00
Petr Mrázek
ef2cbe16e6 NOISSUE when there is a version added already, preselect it in the version select dialog 2017-12-09 01:30:23 +01:00
Petr Mrázek
57accb1cbb NOISSUE Reorder and reword main toolbar actions to save space
```
Folder, Help, Check for Updates, Settings
  -> Folders, Settings, Help, Update
```
2017-12-06 02:01:42 +01:00
Petr Mrázek
6d034bda82 GH-2059 fix instance directory not being created on first launch 2017-12-05 09:52:04 +01:00
Petr Mrázek
44475350eb NOISSUE placeholder for proper 'change version' functionality
When using this on forge or liteloader, it now uses the 'install'
dialogs instead.
This will have to be done properly using the component version metadata
later.
2017-12-05 00:40:45 +01:00
Petr Mrázek
df1ec1f7c2 GH-2057 remove bundled libz 2017-12-04 21:26:49 +01:00
Petr Mrázek
00814830c4 NOISSUE bundle libproxy and libz on linux
This is an experiment...
2017-12-04 02:55:28 +01:00
Petr Mrázek
c2766e5c6e NOISSUE add the material icons license 2017-12-03 22:36:46 +01:00
Petr Mrázek
90a3997d2c NOISSUE add flat icon theme by Michael
It's the google icon font, all scalable and grey.
2017-12-03 21:52:40 +01:00
Petr Mrázek
d6fc37e486 NOISSUE make MultiMC respond to account manipulation better
* Setting and resetting default account will update the account list properly
* Removing the active account will now also reset it (previously, it would 'stay around')
* The accounts model is no longer reset by every action
2017-12-03 20:54:28 +01:00
Petr Mrázek
8eb1397a8a NOISSUE fix wrong look of checkboxes in the account list 2017-12-03 19:34:54 +01:00
Petr Mrázek
4bae6fe491 GH-2050 fix cancel button in file browse dialogs filling text fields 2017-12-03 19:21:04 +01:00
Petr Mrázek
95e6f37d39 NOISSUE force saving of any outstanding instance component state on exit 2017-12-03 18:36:28 +01:00
Petr Mrázek
e0bea1e46a NOISSUE watch added Component for changes in order to trigger ComponentList saves 2017-12-03 15:57:21 +01:00
Petr Mrázek
6a462d0778 GH-1082 allow disabling components
Currently only ones that are removable and aren't dep-only
2017-12-03 15:48:25 +01:00
Petr Mrázek
0a56b56286 NOISSUE in offline mode, do not contact the auth server if there is a valid account already 2017-12-03 14:05:35 +01:00
Petr Mrázek
30fad998a6 NOISSUE normalize instances path in FolderInstanceProvider
This resolves some issues with the instance export dialog when the instances folder
path contains '..' and '.', or involves symlinks.
2017-12-03 02:38:53 +01:00
Petr Mrázek
34de313feb NOISSUE disable the unfinished 'Packages' page in settings 2017-12-03 02:01:43 +01:00
Petr Mrázek
85ae710d40 GH-2026 implement changes necessary to support 1.13 snapshots 2017-12-03 01:22:34 +01:00
Petr Mrázek
17c8f31a09 NOISSUE split out the LaunchProfile out of the ComponentList 2017-11-17 15:44:13 +01:00
MinecraftZuriki
3470158943 Support for classic multiplayer via mpticket 2017-11-18 01:27:57 +11:00
Petr Mrázek
b000b33661 NOISSUE fix display of svg icons in instance toolbar 2017-11-14 01:03:32 +01:00
Petr Mrázek
322922e013 NOISSUE add svg instance icon support 2017-11-14 00:16:04 +01:00
Petr Mrázek
fede712a26 NOISSUE rename MinecraftProfile to ComponentList
It is realistically a list of components. The fact that it also holds the final
launch parameters is a design bug.
2017-11-04 15:23:49 +01:00
Petr Mrázek
87edaa7dcd NOISSUE and one more build for the build gods... ssl error catching again. 2017-11-01 23:21:00 +01:00
Petr Mrázek
e5da2e36c7 NOISSUE actually do catch the ssl errors 2017-11-01 23:04:49 +01:00
Petr Mrázek
e44a0cb944 NOISSUE catch and log SSL errors for Download(s) 2017-11-01 22:52:11 +01:00
Petr Mrázek
e6d734d9ac NOISSUE fix build some more 2017-10-29 12:28:26 +01:00
Petr Mrázek
1489720b90 NOISSUE fix build 2017-10-29 12:27:12 +01:00
Petr Mrázek
b76bdf9368 GH-2026 avoid using awt Dimension class to fix input issues on macOS 2017-10-29 12:24:49 +01:00
Petr Mrázek
7add9de1cf GH-2026 remove some macOS stuff from launcher part to fix input issues on 1.13 2017-10-29 10:02:01 +01:00
Petr Mrázek
a6a642eb7e GH-2026 only run versions with FirstThreadOnMacOS trait on first thread 2017-10-29 09:15:10 +01:00
Petr Mrázek
c78498f40c GH-2026 actually do the previous on macOS, not Windows 2017-10-28 23:29:18 +02:00
Petr Mrázek
4b80d34be4 GH-2026 start Minecraft on first thread on macOS
This should fix issues with the 1.13 snapshots
2017-10-28 22:56:18 +02:00
Petr Mrázek
d4b82f11ca GH-2026 fix failing library test case for native libraries 2017-10-28 22:21:49 +02:00
Petr Mrázek
a87d96349a NOISSUE show hidden files in instance export dialog (like .minecraft) 2017-10-28 21:44:29 +02:00
Petr Mrázek
3eebc641f9 GH-2026 fix native library downloads
If a single library had both native and java jars, they would randomly get confused.
2017-10-28 21:12:12 +02:00
Petr Mrázek
ab870648bd NOISSUE remove debug dump to file in paste upload 2017-10-11 09:13:26 +02:00
Petr Mrázek
1388751fd4 NOISSUE clean up and fix paste.ee upload 2017-10-11 08:55:42 +02:00
Petr Mrázek
eba8e61ce9 NOISSUE change behaviour of the +tweakers patch item
Patch application will either add tweakers, or move them
to the end if they are already present.

This allows fixing up tweaker order in subsequent version patches.
2017-10-08 02:02:52 +02:00
Petr Mrázek
b88206907e NOISSUE code comments 2017-10-08 02:02:34 +02:00
Petr Mrázek
4b90a078de NOISSUE add tooltips to new menu buttons and make them translateable 2017-10-04 02:35:28 +02:00
Petr Mrázek
d8c8a41dfa NOISSUE rearrange main toolbar for clarity
It is now set to text beside icons and many actions were moved to sub-menus.
2017-10-02 02:30:14 +02:00
Petr Mrázek
e2a4fbc589 NOISSUE disable the refresh action on the main toolbar 2017-10-02 01:34:01 +02:00
Petr Mrázek
edfca7da66 NOISSUE remove bogus duplicated toolbars 2017-10-02 01:33:30 +02:00
Petr Mrázek
a1d501d394 NOISSUE refactor the MainWindow UI creation
It was ugly generated code with no rhyme or reason to it.
Now all the relevant code is grouped and language switching works a little better.
2017-10-02 00:55:34 +02:00
Petr Mrázek
ea71281629 NOISSUE fix aspect ratio issues with the instance icon in the instance toolbar 2017-09-28 02:47:54 +02:00
Petr Mrázek
c51512f940 NOISSUE use classparser for importing Legacy instances with undecided Minecraft versions 2017-09-27 15:39:13 +02:00
Petr Mrázek
9a2d203c0d GH-1993 swap min/max memory settings when they are the wrong way around 2017-09-27 12:45:07 +02:00
Petr Mrázek
79d208795c GH-1997 fix off by one error in 8.3 path logic 2017-09-27 04:28:21 +02:00
Petr Mrázek
d276da1359 GH-1997 try to fix Windows build
Coding blind. Much !!FUN!!.
2017-09-27 04:10:09 +02:00
Petr Mrázek
464bc0f770 GH-1997 replace use of weird hacks with normal java arguments
This affects classpath and java.library.path.

The catch is that if the strings cannot be expressed in system codepage
on Windows, it tries to use 8.3 paths.
2017-09-27 04:04:19 +02:00
Petr Mrázek
0595a00090 NOISSUE discourage using java > 8 by sorting it below everything else 2017-09-26 19:16:46 +02:00
Petr Mrázek
1a38587877 NOISSUE Legacy migration success now closes the instance window 2017-09-26 19:04:37 +02:00
Petr Mrázek
719f112f64 NOISSUE add #testing discord channel link to Legacy instance migration page 2017-09-26 14:35:26 +02:00
Petr Mrázek
2b998bb8cc NOISSUE implement LEgacy instance migration 2017-09-26 13:38:34 +02:00
Petr Mrázek
eac892965e NOISSUE make sure MultiMC and Flame import is mutually exclusive 2017-09-26 01:39:39 +02:00
Petr Mrázek
678c7ab271 NOISSUE remove Flame manifest on import
This prevents treating the instance as a Flame pack later.
2017-09-26 01:37:50 +02:00
Petr Mrázek
f26ca143c4 NOISSUE do not fail when the Flame overrides folder is missing 2017-09-26 01:36:52 +02:00
Petr Mrázek
a269e0c92f NOISSUE force travis.ci to use precise, fix ppa for Qt 2017-09-25 12:14:28 +02:00
Petr Mrázek
f8c5cee982 NOISSUE ignore the 'Example Mod' mod name, use filename instead 2017-09-25 08:18:42 +02:00
Petr Mrázek
c82042dcfa GH-2000 translate 'required' from Flame pack manifests to '.disabled' 2017-09-25 08:06:23 +02:00
Petr Mrázek
b5b16d0972 NOISSUE make instance traits() const 2017-09-22 00:27:30 +02:00
Petr Mrázek
76c7e0fe1c NOISSUE remove some dead code from MinecraftInstance (moved elsewhere) 2017-09-22 00:13:07 +02:00
Petr Mrázek
fce2b0ce5f NOISSUE make the paste.ee links in settings clickable 2017-09-22 00:04:33 +02:00
Petr Mrázek
e9434fce3d NOISSUE prefer to use '.minecraft' instead of 'minecraft' folder 2017-09-20 23:58:39 +02:00
Petr Mrázek
c707042dd7 NOISSUE set haspaid parameter to true when launching old versions 2017-09-20 23:57:20 +02:00
Petr Mrázek
102804ef82 NOISSUE remove obsolete LWJGL folder setting 2017-09-20 23:43:55 +02:00
Petr Mrázek
ba3cbb7330 NOISSUE more work on Legacy migration 2017-09-20 23:38:31 +02:00
Petr Mrázek
9a6c2b0e2c NOISSUE Add back Legacy for migration purposes 2017-09-17 19:24:39 +02:00
Petr Mrázek
b2b0487600 NOISSUE clean up moc warnings 2017-09-10 13:25:32 +02:00
Petr Mrázek
9491396292 NOISSUE put back missing OneSix upgrade logic 2017-09-10 12:41:32 +02:00
Petr Mrázek
13628e7a82 NOISSUE merging of strategy into profile, onesix into minecraft 2017-09-09 19:19:05 +02:00
Petr Mrázek
b29382c748 NOISSUE Remove Legacy support 2017-09-09 18:30:22 +02:00
Petr Mrázek
4c01983f47 NOISSUE remove FTB integration 2017-09-09 18:29:52 +02:00
Petr Mrázek
3fb4ce713f NOISSUE add support for Flame packs with resource packs
And a bunch of undefined things we don't handle intentionally just yet...
2017-09-08 09:02:27 +02:00
Petr Mrázek
32a2cb5a0d NOISSUE fix hardcoded link color in other places 2017-09-07 01:20:11 +02:00
Petr Mrázek
a1ef043030 NOISSUE fix hardcoded link color in about dialog
Should respect theme colors now.
2017-09-07 00:53:09 +02:00
Petr Mrázek
b61407a75d NOISSUE retry committing instances if it fails a few times
This should fix issues with antivirus locking files/folders on Windows.
2017-09-05 23:38:17 +02:00
Petr Mrázek
d80382180e NOISSUE refactor pack import (extraction and paths)
It now:
* Doesn't extract until it knows the content format is good.
* Extracts in a predictable location, not requiring to use a second path for the actual pack root.
2017-09-04 08:17:25 +02:00
Petr Mrázek
b8adbb9b73 GH-1971 do not check filesystem boundaries when committing instances
The check wasn't very good and was breaking because it assumed uniform paths.
2017-09-02 13:58:57 +02:00
Petr Mrázek
6381bfdb88 NOISSUE handle error 201 in Auth code
This is something I ran into when one of my accounts stopped working.
The auth token probably expired.

This should now be handled as a normal auth error, not a network failure.
2017-09-02 13:37:12 +02:00
Petr Mrázek
3ed990861a NOISSUE simplify ProblemProvider 2017-08-28 22:09:53 +02:00
flcmc
2c1ca040f8 NOISSUE Update Instance-Version Help Page 2017-08-27 19:15:51 +02:00
Petr Mrázek
300aa9e6d6 NOISSUE update te logo in the README 2017-08-27 12:56:21 +02:00
Petr Mrázek
8227725adb NOISSUE update README to reference workflowy and discord 2017-08-27 11:41:02 +02:00
Petr Mrázek
53bf21ddcd NOISSUE select whole items in screenshot list
Items = rows. It should work properly now.
2017-08-26 21:08:43 +02:00
Joona
59b681a174 NOISSUE implement more error handling 2017-08-22 18:35:10 -07:00
Joona
6e25624623 NOISSUE remove debug statement 2017-08-22 18:12:23 -07:00
Joona
60d10201e9 NOISSUE Add GPU vendor and driver version 2017-08-22 18:02:03 -07:00
Joona
0cd55d943e CPU and GPU model on Linux 2017-08-22 17:47:06 -07:00
Joona
48274e889f move env cleaning and clean before java test 2017-08-22 15:23:35 -07:00
Joona
9d3a847555 forgot to remove debug statement 2017-08-22 22:38:01 +02:00
Joona
ced67a7400 remove public option 2017-08-22 22:38:01 +02:00
Joona
4d54d2662a switch to new paste.ee API 2017-08-22 22:38:01 +02:00
Petr Mrázek
140c31293c NOISSUE use rows when uploading screenshots
Using all indexes was quadrupling the uploaded items.
2017-08-19 23:14:06 +02:00
Petr Mrázek
8cf88ffc58 GH-1314 add UI for custom minecraft jar addition
Also changes the text of the jar mod addition button.
It should be clearer what it does and hopefully will not confuse
as many people.
2017-08-07 00:46:29 +02:00
Petr Mrázek
117bfef151 NOISSUE add JDK build dependency on Windows into BUILD.md 2017-07-29 16:22:53 +02:00
Petr Mrázek
632f232e36 NOISSUE update build instructions
It needs more. It needs a cleanup. And it needs someone to go and actually test that it works.
2017-07-29 16:06:38 +02:00
Petr Mrázek
bea1b5de5e GH-1929 do not allow non-current update task to affect the update process
Errors are handled by setting a flag and failing on the next call to next()
2017-07-21 08:49:58 +02:00
Petr Mrázek
c19f6d4dcd NOISSUE allow running legacy without the applet wrapper
Add 'noapplet' as a trait to do that.
2017-07-14 08:43:35 +02:00
Petr Mrázek
afb0db24a8 NOISSUE set max of java heap spinboxes to detected physical memory 2017-07-11 22:43:35 +02:00
Petr Mrázek
5ea170db78 GH-1927 fix potential issue with FMLLibrariesTask succeeding twice. 2017-07-07 19:50:24 +02:00
Petr Mrázek
e5b4b5d295 GH-1927 Add more specific task status logging
* Tasks are now described by class name and object name (or memory address).
* Tasks starts are logged.
* Aborted tasks are now treated just as the other cases.
2017-07-07 19:46:56 +02:00
Petr Mrázek
fbeceaa98c NOISSUE remove verbose dl progress logging 2017-07-07 18:16:09 +02:00
Petr Mrázek
50697735b5 NOISSUE fix jvisualvm website links 2017-07-07 01:28:58 +02:00
Petr Mrázek
1797f45e8f NOISSUE fix jumpy download progress bars
They are not as precise, the new logic gives every
download 1000 'units' instead of the actual (initially unknown) sizes.
2017-07-06 15:38:16 +02:00
Petr Mrázek
8dd9987a9c NOISSUE remove obsolete declaration for logger shutdown 2017-07-06 15:38:01 +02:00
Petr Mrázek
259021bc98 NOISSUE fix startup issues caused by code reorganization 2017-07-06 01:59:31 +02:00
Petr Mrázek
26f7f017d4 NOISSUE attept to detach from console on Windows
This only applies to MultiMC started from a console.
2017-07-05 19:45:10 +02:00
Petr Mrázek
71b129538b NOISSUE add disgnostic print to MultiMC destructor 2017-07-05 19:09:14 +02:00
Petr Mrázek
d6ab4b4a7f NOISSUE rearrange global initialization 2017-07-05 18:02:49 +02:00
Petr Mrázek
74c455ff35 NOISSUE add note to Job_Failed_Proceed
It should be removed, it just signifies that there is a system missing.
2017-06-27 08:20:12 +02:00
Petr Mrázek
36f3e24cf3 NOISSUE remove some bad code in various Task related classes 2017-06-27 04:32:53 +02:00
Petr Mrázek
89d3a66658 NOISSUE some safe refactors and changes of the task subsystem
Possibly also some bug fixes.
2017-06-26 01:14:32 +02:00
Max Nordlund
9583d0d8ee Mention casing in ISSUE_TEMPLATE 2017-06-17 23:31:21 +02:00
Petr Mrázek
2973b11d3e NOISSUE instance export filter should use covers, not contains 2017-05-31 09:37:45 +02:00
Petr Mrázek
8a1da91219 NOISSUE finish cleaning quazip
All LGPL code is back in the customized quazip fork
2017-05-31 09:20:24 +02:00
Petr Mrázek
cdc9bed83f NOISSUE move some zip utility functions back to quazip
It's not entirely clean yet.
2017-05-31 09:20:24 +02:00
Petr Mrázek
7acf1998eb NOISSUE split wrapper commands into command and args
This lets you use commands with parameters.
2017-05-29 01:36:01 +02:00
Petr Mrázek
50b8412a26 NOISSUE do not try to restore file permissions when importing modpacks 2017-05-25 01:24:27 +02:00
Petr Mrázek
2a81e21f5e GH-1876 Clarify license of GroupView
The LGPL code that came from KDE has been removed back in 2014.
It was replaced with a different implementation, made in
a separate repository.

See commits:
a17caba2c9
b82eb5873e
2017-05-22 23:50:20 +02:00
Petr Mrázek
3597a50854 NOISSUE log native extraction failures also to game log 2017-05-22 08:57:13 +02:00
Petr Mrázek
4133247bff NOISSUE and even more warnings gone 2017-05-21 22:30:53 +02:00
Petr Mrázek
c329bc73c0 NOISSUE more compiler warning removal 2017-05-21 22:24:06 +02:00
Petr Mrázek
d432d8ecfc NOISSUE get rid of some more compiler warnings 2017-05-21 22:14:40 +02:00
Petr Mrázek
8bd8be95f0 NOISSUE fix a bunch of compiler warnings 2017-05-21 20:20:37 +02:00
Petr Mrázek
572a6026b5 GH-1895 update LWJGL list during legacy instance update 2017-05-21 02:34:02 +02:00
Petr Mrázek
d70c783de8 NOISSUE bump version to 0.6.0 - MultiMC changed a lot since 0.5.x 2017-05-13 11:01:38 +02:00
Petr Mrázek
e1a03e8724 NOISSUE make the linux package binary name configurable and default to 'multimc' 2017-05-13 10:43:56 +02:00
Petr Mrázek
868669a497 NOISSUE add a linux system packaging install layout 2017-05-13 01:24:15 +02:00
Petr Mrázek
4cf4110d9d NOISSUE log even more about 'Flame' resolving issues 2017-05-09 21:56:33 +02:00
Petr Mrázek
e029b7764e NOISSUE log 'Flame' mod resolver parsing errors. 2017-05-09 21:49:36 +02:00
Petr Mrázek
8a526fab0e GH-1885 make FileSink save (even empty) files when the HTTP response is 200 or 203 2017-05-09 03:08:38 +02:00
Petr Mrázek
8fe18cfabc GH-1882 save UpdateDialog geometry 2017-05-09 01:54:28 +02:00
Petr Mrázek
0f311e12ee GH-1886 warn users about proxy settings not applying to the game 2017-05-08 23:37:36 +02:00
Petr Mrázek
01058b1db1 NOISSUE all Qt5 libraries are REQUIRED 2017-05-08 23:34:23 +02:00
Petr Mrázek
c407004e3a NOISSUE make qt.conf downloadable in presence of GH-1885
Now it has a single space inside.
2017-05-08 00:07:20 +02:00
Petr Mrázek
3fb15e4a4d NOISSUE include qt.conf in all bundles again
Looks like it just doesn't work properly without that.
2017-05-07 22:19:01 +02:00
Petr Mrázek
a379d43d7c NOISSUE replace bad symlink to COPYING.md with a relative path in qrc 2017-05-06 18:32:28 +02:00
Petr Mrázek
3263b52e0c GH-1876 replace ColumnResizer with a newer, BSD-3 licensed version
Also, keep licensing info only in one file.
The COPYING.md is now a resource and rendered to HTML in the About dialog.
2017-05-06 18:27:47 +02:00
Petr Mrázek
15c829fd3c GH-1876 Fork and update quazip
* It is added as a new submodule: https://github.com/MultiMC/quazip/tree/multimc-1
* Its build system has been entirely replaced to remove the existing issues with it
* It now has working unit tests
* No more patches needed
* It has a static linking exception in its license now, but we use it shared anyway
2017-05-06 17:36:57 +02:00
Petr Mrázek
349381cb2b NOISSUE remove debug symbols of Qt plugins from macOS bundle 2017-05-06 11:58:57 +02:00
Petr Mrázek
4183cc203f NOISSUE add back qt.conf on macOS
Looks like it doesn't start without this magical empty file.
Who would have guessed? Not me.
2017-05-06 03:41:11 +02:00
Petr Mrázek
2b3e87b7d1 NOISSUE reorganize build system to allow avoiding the bundle utilities 2017-05-06 03:11:38 +02:00
Petr Mrázek
e5147e6b65 NOISSUE remove (assumed) obsolete parts of the application cmake script 2017-05-05 23:09:26 +02:00
Petr Mrázek
9d4c188fd4 GH-1876 turn iconfix into a shared library 2017-05-05 09:43:20 +02:00
Petr Mrázek
e854894a3c GH-1876 turn pack200 into a shared library 2017-05-05 01:34:01 +02:00
Petr Mrázek
0ce44dbd41 GH-1864 fix imgur album creation 2017-05-04 22:58:22 +02:00
Petr Mrázek
35836c7709 NOISSUE herp derp custom icon theme support
* Put icon theme in iconthemes/custom/
* Select 'Custom' in the UI.
* ...
* Maybe it won't explode.
2017-05-04 00:03:47 +02:00
Petr Mrázek
e76e6329cd NOISSUE Revert all recent changes to NetAction and NetJob 2017-05-03 23:13:49 +02:00
Petr Mrázek
0efa714ba5 NOISSUE replace std::shared_ptr with shared_qobject_ptr for all download tasks 2017-05-03 21:09:36 +02:00
Petr Mrázek
907aa36704 GH-1874 Do not allow launching instances during an update 2017-05-02 23:29:47 +02:00
Petr Mrázek
6a8bb3691b GH-1874 do not allow updating while an instance is running
This is a nasty hack. Proper solution will require moving all
update related functionality out of the main window.

Running instances and updating should be mutually exclusive.
2017-05-02 01:43:18 +02:00
Affe Ali
0132fd9929 GH-1855 add launch offline button to instance UI 2017-05-01 23:18:00 +02:00
Petr Mrázek
9bde1c8512 GH-1874 do not pass instance launch args to updated MultiMC 2017-05-01 16:53:20 +02:00
Petr Mrázek
b20688a18d GH-1875 plug holes in instance/window refcount logic 2017-05-01 12:55:10 +02:00
Petr Mrázek
21df531db1 GH-1873 allow closing main window, fix window ref count 2017-05-01 01:27:10 +02:00
Petr Mrázek
f06ac02396 NOISSUE block use of "-XX-MaxHeapSize" and "-XX:InitialHeapSize" java args.
Synonyms for "-Xms" and "-Xmx" that I missed originally.
These are ALWAYS managed by MultiMC. Do not touch them.
2017-04-30 15:12:46 +02:00
Petr Mrázek
794102b32c NOISSUE less jumpy download progress bars and redirect URL fix 2017-04-30 02:54:37 +02:00
Petr Mrázek
1be99b075a NOISSUE more NetAction and NetJob cleanups 2017-04-29 21:34:36 +02:00
Petr Mrázek
e1465f4848 NOISSUE refactor NetAction to be based on Task
Still missing some things, this is part 1.
2017-04-29 02:24:00 +02:00
Petr Mrázek
243f7e4fb4 NOISSUE remove AA_UseHighDpiPixmaps 2017-04-26 22:57:02 +02:00
Petr Mrázek
edc25dab17 NOISSUE Set AA_EnableHighDpiScaling for Qt >= 5.6.0 2017-04-26 22:17:27 +02:00
Petr Mrázek
6dc1bc65e1 NOISSUE fix Mojang JSON format unit test on Qt 5.6 by dumping JSON to byte arrays 2017-04-25 23:33:19 +02:00
Petr Mrázek
6fe9258161 NOISSUE remove macOS SSL workarounds
Should not be necessary anymore...
2017-04-25 23:03:11 +02:00
Petr Mrázek
4fa3e2a714 GH-1856 Fix metadata version and list loading
Shouldn't crash anymore, shouldn't overwrite data in some bad way anymore either.
2017-04-24 01:30:51 +02:00
Petr Mrázek
d25a7ad3a6 NOISSUE accept URL drop events in GroiupView 2017-04-23 16:50:48 +02:00
Petr Mrázek
3f24c4cfe5 GH-1856 Make MultiMC fail hard when things are missing
Things like:
* jar mods
* valid version files
2017-04-23 02:34:16 +02:00
flcmc
b414bbe395 NOISSUE Update COPYING.MD Formatting 2017-04-22 21:31:43 +02:00
Petr Mrázek
8084f27ec8 NOISSUE do not write library display name twice 2017-04-22 20:27:00 +02:00
Petr Mrázek
249baf6fcf GH-1860 fix old jarmods
Names were not set for the libraries, making them return invalid things
2017-04-22 19:38:28 +02:00
Petr Mrázek
8ace5fa91d NOISSUE Add flame. 2017-04-22 18:51:04 +02:00
Petr Mrázek
30863a88ab NOISSUE add pack import using drag&drop
Straight from the browser or the downloads folder.
2017-04-22 06:11:26 +02:00
Petr Mrázek
77a1d39f6b NOISSUE fix problems in old curse packs
Added:
* jarmod importing
* fixing of bad minecraft versions
* mapping of undefined 'recommended' forge versions to something appropriate
* some fake guessing of pack icons
* fixes for some more issues found with the pack manifest format
2017-04-22 05:20:06 +02:00
Petr Mrázek
6a525db78d NOISSUE 'required' attribute of curse manifests is optional, libraries attribute reading 2017-04-22 00:29:24 +02:00
Petr Mrázek
1d71214d4a NOISSUE fix build: there is no qInfo in old Qt5. 2017-04-21 22:30:39 +02:00
Petr Mrázek
ab5045b54c NOISSUE finalize curse modpack import work 2017-04-21 22:23:31 +02:00
Petr Mrázek
f3c46dbf11 NOISSUE silly/simple implementation of mod metadata in OneSix version format 2017-04-21 22:23:00 +02:00
Petr Mrázek
581460dcf9 NOISSUE add missing log levels to MultiMC logging
Info and System are now recognized.
2017-04-21 22:08:33 +02:00
Petr Mrázek
6bd2605a79 NOISSUE add import from curse zip packs
Does not actually grab mods, but resolves them and prints the results in logs.
2017-04-20 05:22:04 +02:00
Petr Mrázek
e9a6199507 NOISSUE fix benign issue in pack200 unpacker binary. 2017-04-19 22:23:00 +02:00
Petr Mrázek
60777ad8ce GH-1856 always pull new versions of metadata when requested
The only block is if it's already happening.
2017-04-19 22:23:00 +02:00
Rafael Ristovski
88041783e6 Add missing <functional> include.
Compiling under Linux fails because of a missing include directive
Ref:
http://en.cppreference.com/w/cpp/utility/functional/bind
http://en.cppreference.com/w/cpp/utility/functional/placeholders
2017-04-19 22:11:12 +02:00
Petr Mrázek
47e075babd NOISSUE set metadata source to main 2017-04-18 17:55:05 +02:00
Petr Mrázek
5565a2f85e NOISSUE fix crash because of early destruction of java list load task 2017-04-18 16:45:58 +02:00
Petr Mrázek
c4c8e99681 NOISSUE jar mods as libraries, fix for customizing net.minecraft 2017-04-17 22:51:30 +02:00
Petr Mrázek
fc28aacdea NOISSUE stop using the path attributes from Mojang download info
It really should not dictate how are things stored and it just makes
the metadata bigger for no reason.
2017-04-15 11:40:22 +02:00
Petr Mrázek
07cde802e4 GH-1853 fix FTB and parsing of old version files 2017-04-14 12:33:54 +02:00
Petr Mrázek
2aaf9827a6 NOISSUE slightly refactor jarMod entry reading 2017-04-14 12:21:10 +02:00
Petr Mrázek
910766458d GH-1854 prevent a crash if the instance is closed while the kill confirmation dialog is open 2017-04-14 10:41:52 +02:00
Petr Mrázek
be53eb66f8 NOISSUE implement mainJar support in OneSix format
This allows customizing the main jar like any other library.
2017-04-13 09:28:25 +02:00
Petr Mrázek
db7357d008 Revert "NOISSUE disable version customization until further notice"
This reverts commit d864c95e2b.
2017-04-08 22:04:07 +02:00
Petr Mrázek
795889d934 Merge branch 'feature/meta' into develop 2017-04-07 00:27:24 +02:00
Petr Mrázek
8e58d61150 NOISSUE fix issue with the narrator feature by splitting java and native libraries 2017-04-07 00:20:02 +02:00
Petr Mrázek
dddc5cedf3 NOISSUE make a call to ensureBoolean in the JSON format code less ambiguous 2017-04-07 00:20:02 +02:00
Petr Mrázek
d864c95e2b NOISSUE disable version customization until further notice 2017-04-07 00:20:02 +02:00
Petr Mrázek
dff307557b NOISSUE fix liteloader uid where it is hardcoded hardcoded 2017-04-07 00:20:02 +02:00
Petr Mrázek
643d74f66c NOISSUE implement recommended versions using the new JSON format 2017-04-07 00:20:02 +02:00
Petr Mrázek
f565798650 NOISSUE remove unused version range filtering 2017-04-07 00:20:02 +02:00
Petr Mrázek
e0596d3c86 NOISSUE Make forge installable again 2017-04-07 00:20:02 +02:00
Petr Mrázek
2ac0edbbdb NOISSUE preview of LWJGL version changing
It still needs work - some LWJGL versions are exclusive to macOS.
This has to be encoded in the json.
2017-04-07 00:20:02 +02:00
Petr Mrázek
53188386b8 NOISSUE refactor builtin patch loading slightly 2017-04-07 00:20:02 +02:00
Petr Mrázek
af3384c649 NOISSUE add filename to newly created jar mod patches 2017-04-07 00:20:02 +02:00
Petr Mrázek
22735f275e NOISSUE remove dead code 2017-04-07 00:20:02 +02:00
Petr Mrázek
5fabb4f254 NOISSUE Rough refactor of ProfilePatch and VersionFile internals.
They are now distinct classes with distinct responsibilities.

* ProfilePatch is an entry in MinecraftProfile and can hold VersionFile or Meta::Version.
* VersionFile is the basic element that holds version information loaded from JSON.
* Meta::Version is the loader class for VersionFile(s) from a server.
2017-04-07 00:20:02 +02:00
Petr Mrázek
6f2a87167a NOISSUE remove some dead code from version related classes 2017-04-07 00:20:02 +02:00
Petr Mrázek
3aa28bd64a NOISSUE fix some warning on linux related to the GNU C library 2017-04-07 00:20:02 +02:00
Petr Mrázek
da4ae1bc1e NOISSUE reimplement package dependencies
It is now stored as a hashmap
There is also a parentUid to limit depsolving by encapsulating by version
2017-04-07 00:20:02 +02:00
Petr Mrázek
77f27a628f NOISSUE bring back instance creation 2017-04-07 00:20:02 +02:00
Petr Mrázek
f557c13679 NOISSUE stuff and things happened. Maybe. 2017-04-07 00:20:02 +02:00
Petr Mrázek
2660418d58 NOISSUE hack it together enough to get launching back
Meta index will now always return valid objects.
They just might never load if they don't exist on the server.
2017-04-07 00:20:02 +02:00
Petr Mrázek
e46aba9da5 NOISSUE sanitize loading and downloading of metadata files 2017-04-07 00:20:02 +02:00
Petr Mrázek
0060b50625 NOISSUE simplify. 2017-04-07 00:20:01 +02:00
Petr Mrázek
40cf38bc32 NOISSUE remove liteloader and forge 2017-04-07 00:20:01 +02:00
Petr Mrázek
f6eb8fa1e4 NOISSUE Remove hardcoded LWJGL (modern) 2017-04-07 00:20:01 +02:00
Petr Mrázek
2980322c3b NOISSUE Remove Minecraft version list and versions. 2017-04-07 00:20:01 +02:00
Petr Mrázek
8321187a20 NOISSUE fix downloading of metadata files 2017-04-07 00:20:01 +02:00
Petr Mrázek
ab868df50e NOISSUE Wonko is the new Meta
And then Wonko was the Meta.
2017-04-07 00:20:01 +02:00
Petr Mrázek
1fbe03f982 NOISSUE remove unused wonkoclient 2017-04-07 00:20:01 +02:00
Petr Mrázek
7382360771 NOISSUE remove builtin Minecraft versions 2017-04-07 00:19:56 +02:00
Petr Mrázek
8cbe13c656 NOISSUE remove legacy version blacklist 2017-04-07 00:19:51 +02:00
Petr Mrázek
160b5033a7 GH-1828 hardcode legacy assets location to fix legacy launch 2017-02-28 20:52:50 +01:00
Petr Mrázek
01cb5ebb35 GH-1828 do not load assets info from custom version files
Fixes issue where this causes the launcher to use the old assets URL
2017-02-28 20:37:51 +01:00
862 changed files with 66775 additions and 65563 deletions

View File

@@ -1,5 +1,5 @@
{ {
"project_id": "MultiMC5", "project_id": "MultiMC5",
"conduit_uri": "http://ph.multimc.org" "conduit_uri": "http://ph.multimc.org"
} }

View File

@@ -1,4 +1,4 @@
UseTab: true UseTab: false
IndentWidth: 4 IndentWidth: 4
TabWidth: 4 TabWidth: 4
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4

View File

@@ -2,15 +2,16 @@
Before submitting this issue, please make sure you have: Before submitting this issue, please make sure you have:
1. Filled out this form completely, the only optional field is "additional info". 1. Filled out this form completely, the only optional field is "additional info".
- Use as many details as possible and state the problem clearly. - Use as many details as possible and state the problem clearly.
2. Proof-read your ENTIRE issue report. 2. Proof-read your ENTIRE issue report.
- Grammar and spelling mistakes make issue reports harder to understand. - Grammar and spelling mistakes make issue reports harder to understand.
3. Made sure your problem is not caused by an issue in your own modpack. 3. Made sure your problem is not caused by an issue in your own modpack.
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored. - We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
4. Given the issue a descriptive title. 4. Given the issue a descriptive title.
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!". - A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
Use of UPPERCASE is discouraged, as it reads like someone is screaming.
5. Place all information below the ---- of lines. 5. Place all information below the ---- of lines.
- It makes the issue look pretty - It makes the issue look pretty
If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
--> -->

2
.gitignore vendored
View File

@@ -15,6 +15,8 @@ CMakeLists.txt.user
CMakeLists.txt.user.* CMakeLists.txt.user.*
/.project /.project
/.settings /.settings
/.idea
cmake-build-*/
# Build dirs # Build dirs
build build

7
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "depends/libnbtplusplus"] [submodule "depends/libnbtplusplus"]
path = libraries/libnbtplusplus path = libraries/libnbtplusplus
url = https://github.com/MultiMC/libnbtplusplus.git url = https://github.com/MultiMC/libnbtplusplus.git
[submodule "libraries/quazip"]
path = libraries/quazip
url = https://github.com/MultiMC/quazip.git

View File

@@ -2,29 +2,28 @@
language: cpp language: cpp
cache: apt cache: apt
# Build matrix set up
compiler:
- gcc
# - clang
os:
- linux
# - osx
env:
- QT_VERSION=5.4.2
- QT_VERSION=5.5.1
# - QT_VERSION=5.6.2
matrix: matrix:
exclude: include:
# only use clang on OS X - os: linux
- os: osx dist: precise
sudo: required
compiler: gcc compiler: gcc
# only use the qt available from homebrew env: TRAVIS_DIST=precise QT_VERSION=5.4.2
- os: osx - os: linux
env: QT_VERSION=5.4.2 dist: precise
- os: osx sudo: required
env: QT_VERSION=5.5.1 compiler: gcc
# - os: osx env: TRAVIS_DIST=precise QT_VERSION=5.6.2
# env: QT_VERSION=5.6 - os: linux
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
- os: linux
dist: trusty
sudo: required
compiler: gcc
env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
# Install dependencies # Install dependencies
install: install:

View File

@@ -32,11 +32,11 @@ git submodule update
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution. Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
## Build dependencies ## Build dependencies
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above). * A C++ compiler capable of building C++11 code.
* Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution. * Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version.
* cmake 3.1 or newer * cmake 3.1 or newer
* zlib (for example, `zlib1g-dev`) * zlib (for example, `zlib1g-dev`)
* java (for example, `openjdk-8-jdk`) * Java JDK 8 (for example, `openjdk-8-jdk`)
* GL headers (for example, `libgl1-mesa-dev`) * GL headers (for example, `libgl1-mesa-dev`)
### Building from command line ### Building from command line
@@ -64,7 +64,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
1. Run the Qt installer. 1. Run the Qt installer.
2. Choose a place to install Qt. 2. Choose a place to install Qt.
3. Choose the components you want to install. 3. Choose the components you want to install.
- You need Qt 5.4.1/gcc 64-bit ticked. - You need Qt 5.6.x 64-bit ticked.
- You need Tools/Qt Creator ticked. - You need Tools/Qt Creator ticked.
- Other components are selected by default, you can untick them if you don't need them. - Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements. 4. Accept the license agreements.
@@ -77,7 +77,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt. 3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
4. Read the instructions that just popped up about a build location and choose one. 4. Read the instructions that just popped up about a build location and choose one.
5. You should see "Run CMake" in the window. 5. You should see "Run CMake" in the window.
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)". - Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
- Hit the "Run CMake" button. - Hit the "Run CMake" button.
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window. - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully. - Hit "Finish" if CMake ran successfully.
@@ -91,13 +91,13 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only. Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
## Dependencies ## Dependencies
* [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows * [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light * [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download. - Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though. - We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip) * [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer) * [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
Put it somewhere on the `PATH`, so that it is accessible from the console. Put it somewhere on the `PATH`, so that it is accessible from the console.
@@ -107,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
1. Run the Qt installer 1. Run the Qt installer
2. Choose a place to install Qt (C:\Qt is the default), 2. Choose a place to install Qt (C:\Qt is the default),
3. Choose the components you want to install 3. Choose the components you want to install
- You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked, - You need Qt 5.6 (32 bit) ticked,
- You need Tools/Qt Creator ticked, - You need Tools/Qt Creator ticked,
- Other components are selected by default, you can untick them if you don't need them. - Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements, 4. Accept the license agreements,
@@ -132,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it, 5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- Otherwise you can skip this step. - Otherwise you can skip this step.
6. You should see "Run CMake" in the window, 6. You should see "Run CMake" in the window,
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)", - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)",
- Hit the "Run CMake" button, - Hit the "Run CMake" button,
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window. - You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully. - Hit "Finish" if CMake ran successfully.

View File

@@ -2,18 +2,18 @@ cmake_minimum_required(VERSION 3.1)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD) string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD) if(IS_IN_SOURCE_BUILD)
message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!") message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!")
endif() endif()
if(WIN32) if(WIN32)
# In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows # In Qt 5.1+ we have our own main() function, don't autolink to qtmain on Windows
cmake_policy(SET CMP0020 OLD) cmake_policy(SET CMP0020 OLD)
endif() endif()
project(MultiMC) project(MultiMC)
enable_testing() enable_testing()
######## Set CMake options ######## ##################################### Set CMake options #####################################
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -22,7 +22,7 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
# Output all executables and shared libs in the main build folder, not in subfolders. # Output all executables and shared libs in the main build folder, not in subfolders.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
if(UNIX) if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
endif() endif()
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars) set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
@@ -32,22 +32,61 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader) 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) if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}") set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif() endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
##################################### Set Application options #####################################
######## Set URLs ########
set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
set(MultiMC_VERSION_HOTFIX 6)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
# Build platform.
set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
# Channel list URL
set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
# Notification URL
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
# paste.ee API key
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
# Google analytics ID
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
#### Custom target to just print the version.
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
################################ 3rd Party Libs ################################ ################################ 3rd Party Libs ################################
# Find the required Qt parts # Find the required Qt parts
find_package(Qt5Core) find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets) find_package(Qt5Widgets REQUIRED)
find_package(Qt5Concurrent) find_package(Qt5Concurrent REQUIRED)
find_package(Qt5Network) find_package(Qt5Network REQUIRED)
find_package(Qt5Test) find_package(Qt5Test REQUIRED)
find_package(Qt5Xml) find_package(Qt5Xml REQUIRED)
# The Qt5 cmake files don't provide its install paths, so ask qmake. # The Qt5 cmake files don't provide its install paths, so ask qmake.
include(QMakeQuery) include(QMakeQuery)
@@ -62,34 +101,143 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON) SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif() endif()
####################################### Install layout #######################################
# How to install the build results
set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
if(MultiMC_LAYOUT STREQUAL "auto")
if(UNIX AND APPLE)
set(MultiMC_LAYOUT_REAL "mac-bundle")
elseif(UNIX)
set(MultiMC_LAYOUT_REAL "lin-nodeps")
elseif(WIN32)
set(MultiMC_LAYOUT_REAL "win-bundle")
else()
message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
endif()
else()
set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
endif()
if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
set(BUNDLE_DEST_DIR ".")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
# Mac bundle settings
set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2019 MultiMC Contributors")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
# Add the icon
install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "bin/jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
set(BINARY_DEST_DIR "bin")
set(LIBRARY_DEST_DIR "bin")
set(PLUGIN_DEST_DIR "plugins")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "bin/jars")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
# Set RPATH
SET(MultiMC_BINARY_RPATH "$ORIGIN/")
# Install basic runner script
install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
# install as bundle with no dependencies included
set(INSTALL_BUNDLE "nodeps")
elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
set(BINARY_DEST_DIR ".")
set(LIBRARY_DEST_DIR ".")
set(PLUGIN_DEST_DIR ".")
set(BUNDLE_DEST_DIR ".")
set(RESOURCES_DEST_DIR ".")
set(JARS_DEST_DIR "jars")
# Apps to bundle
set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
# install as bundle
set(INSTALL_BUNDLE "full")
else()
message(FATAL_ERROR "No sensible install layout set.")
endif()
################################ Included Libs ################################ ################################ Included Libs ################################
include(ExternalProject) include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE External) set_directory_properties(PROPERTIES EP_BASE External)
# Add quazip
add_definitions(-DQUAZIP_STATIC)
set(QUAZIP_VERSION "0.7.1")
if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
file(DOWNLOAD http://downloads.sourceforge.net/project/quazip/quazip/${QUAZIP_VERSION}/quazip-${QUAZIP_VERSION}.tar.gz ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
endif()
ExternalProject_Add(QuaZIP
SOURCE_DIR <BINARY_DIR>/../Source/quazip-${QUAZIP_VERSION}
DOWNLOAD_COMMAND ${CMAKE_COMMAND} -E chdir <SOURCE_DIR>/.. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz
PATCH_COMMAND patch -p0 -i ${CMAKE_SOURCE_DIR}/quazip.patch
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} -DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)
include_directories("${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/include/quazip")
if(UNIX)
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip z)
else()
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip)
endif()
option(NBT_BUILD_SHARED "Build NBT shared library" ON) option(NBT_BUILD_SHARED "Build NBT shared library" ON)
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF) option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests. option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++) set(NBT_NAME MultiMC_nbt++)
set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus) add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/ganalytics) # google analytics library add_subdirectory(libraries/ganalytics) # google analytics library
@@ -98,15 +246,17 @@ add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/quazip) # zip manipulation library
add_subdirectory(libraries/pack200) # java pack200 compression add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
############################### Built Artifacts ############################### ############################### Built Artifacts ###############################
add_subdirectory(api/logic) add_subdirectory(api/logic)
add_subdirectory(api/gui) add_subdirectory(api/gui)
# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
add_subdirectory(application) add_subdirectory(application)
add_subdirectory(wonkoclient)

View File

@@ -1,205 +1,254 @@
#MultiMC # MultiMC
Copyright 2012-2017 MultiMC Contributors Copyright 2012-2019 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
#MinGW runtime (Windows) # MinGW runtime (Windows)
Copyright (c) 2012 MinGW.org project Copyright (c) 2012 MinGW.org project
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the 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: Software is furnished to do so, subject to the following conditions:
The above copyright notice, this permission notice and the below disclaimer The above copyright notice, this permission notice and the below disclaimer
shall be included in all copies or substantial portions of the Software. 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 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 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
#Qt 5 # Qt 5
Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
Contact: http://www.qt-project.org/legal Contact: http://www.qt-project.org/legal
Licensed under LGPL v2.1 Licensed under LGPL v2.1
#libnbt++ # libnbt++
libnbt++ - A library for the Minecraft Named Binary Tag format. libnbt++ - A library for the Minecraft Named Binary Tag format.
Copyright (C) 2013, 2015 ljfa-ag Copyright (C) 2013, 2015 ljfa-ag
libnbt++ is free software: you can redistribute it and/or modify libnbt++ is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
libnbt++ is distributed in the hope that it will be useful, libnbt++ is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License You should have received a copy of the GNU Lesser General Public License
along with libnbt++. If not, see <http://www.gnu.org/licenses/>. along with libnbt++. If not, see <http://www.gnu.org/licenses/>.
#Group View # rainbow (KGuiAddons)
Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org> Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
Copyright (C) 2007 John Tapsell <tapsell@kde.org> Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
Copyright (C) 2007 Zack Rusin <zack@kde.org>
Copyright (C) 2015 Petr Mrazek <peterix@gmail.com>
This library is free software; you can redistribute it and/or This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version. version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details. Library General Public License for more details.
You should have received a copy of the GNU Library General Public License You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA. Boston, MA 02110-1301, USA.
#rainbow (KGuiAddons) # Hoedown
Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> Copyright (c) 2008, Natacha Porté
Copyright (C) 2007 Olaf Schmidt <ojschmidt@kde.org> Copyright (c) 2011, Vicent Martí
Copyright (C) 2007 Thomas Zander <zander@kde.org> Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
Copyright (C) 2007 Zack Rusin <zack@kde.org>
Copyright (C) 2015 Petr Mrazek <peterix@gmail.com>
This library is free software; you can redistribute it and/or Permission to use, copy, modify, and distribute this software for any
modify it under the terms of the GNU Library General Public purpose with or without fee is hereby granted, provided that the above
License as published by the Free Software Foundation; either copyright notice and this permission notice appear in all copies.
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
but WITHOUT ANY WARRANTY; without even the implied warranty of WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
Library General Public License for more details. ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
You should have received a copy of the GNU Library General Public License # Batch icon set
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
#Hoedown You are free to use Batch (the "icon set") or any part thereof (the "icons")
in any personal, open-source or commercial work without obligation of payment
(monetary or otherwise) or attribution. Do not sell the icon set, host
the icon set or rent the icon set (either in existing or modified form).
Copyright (c) 2008, Natacha Porté While attribution is optional, it is always appreciated.
Copyright (c) 2011, Vicent Martí
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
Permission to use, copy, modify, and distribute this software for any Intellectual property rights are not transferred with the download of the icons.
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#Batch icon set # Material Design Icons
You are free to use Batch (the "icon set") or any part thereof (the "icons") Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
in any personal, open-source or commercial work without obligation of payment with Reserved Font Name Material Design Icons.
(monetary or otherwise) or attribution. Do not sell the icon set, host Copyright (c) 2014, Google (http://www.google.com/design/)
the icon set or rent the icon set (either in existing or modified form). uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE
While attribution is optional, it is always appreciated. This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
Intellectual property rights are not transferred with the download of the icons. # Pack200
EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL ADAM WHITCROFT The GNU General Public License (GPL)
BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL,
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
#Pack200 Version 2, June 1991
The GNU General Public License (GPL) + "CLASSPATH" EXCEPTION TO THE GPL
Version 2, June 1991 Certain source files distributed by Oracle America and/or its affiliates are
subject to the following clarification and special exception to the GPL, but
only where Oracle has expressly included in the particular source file's header
the words "Oracle designates this particular file as subject to the "Classpath"
exception as provided by Oracle in the LICENSE file that accompanied this code."
+ "CLASSPATH" EXCEPTION TO THE GPL Linking this library statically or dynamically with other modules is making
a combined work based on this library. Thus, the terms and conditions of
the GNU General Public License cover the whole combination.
Certain source files distributed by Oracle America and/or its affiliates are As a special exception, the copyright holders of this library give you
subject to the following clarification and special exception to the GPL, but permission to link this library with independent modules to produce an
only where Oracle has expressly included in the particular source file's header executable, regardless of the license terms of these independent modules,
the words "Oracle designates this particular file as subject to the "Classpath" and to copy and distribute the resulting executable under terms of your
exception as provided by Oracle in the LICENSE file that accompanied this code." choice, provided that you also meet, for each linked independent module,
the terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library. If
you modify this library, you may extend this exception to your version of
the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.
Linking this library statically or dynamically with other modules is making # Quazip
a combined work based on this library. Thus, the terms and conditions of
the GNU General Public License cover the whole combination.
As a special exception, the copyright holders of this library give you Copyright (C) 2005-2011 Sergey A. Tachenov
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,
and to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent module,
the terms and conditions of the license of that module. An independent
module is a module which is not derived from or based on this library. If
you modify this library, you may extend this exception to your version of
the library, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.
#Quazip This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
Copyright (C) 2005-2011 Sergey A. Tachenov This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details.
This program is free software; you can redistribute it and/or modify it You should have received a copy of the GNU Lesser General Public License
under the terms of the GNU Lesser General Public License as published by along with this program; if not, write to the Free Software Foundation,
the Free Software Foundation; either version 2 of the License, or (at Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
your option) any later version.
This program is distributed in the hope that it will be useful, but See COPYING file for the full LGPL text.
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License Original ZIP package is copyrighted by Gilles Vollant, see
along with this program; if not, write to the Free Software Foundation, quazip/(un)zip.h files for details, basically it's zlib license.
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See COPYING file for the full LGPL text. # xz-minidec
Original ZIP package is copyrighted by Gilles Vollant, see XZ decompressor
quazip/(un)zip.h files for details, basically it's zlib license.
#xz-minidec Authors: Lasse Collin <lasse.collin@tukaani.org>
Igor Pavlov <http://7-zip.org/>
XZ decompressor This file has been put into the public domain.
You can do whatever you want with this file.
Authors: Lasse Collin <lasse.collin@tukaani.org> # ColumnResizer
Igor Pavlov <http://7-zip.org/>
This file has been put into the public domain. Copyright (c) 2011-2016 Aurélien Gâteau and contributors.
You can do whatever you want with this file.
#ColumnResizer All rights reserved.
Copyright 2011 Aurélien Gâteau <agateau@kde.org> Redistribution and use in source and binary forms, with or without
License: LGPL v2.1 or later (see COPYING) modification, are permitted (subject to the limitations in the
disclaimer below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* The name of the contributors may not be used to endorse or
promote products derived from this software without specific prior
written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# lionshead
Code has been taken from https://github.com/natefoo/lionshead and loosely
translated to C++ laced with Qt.
MIT License
Copyright (c) 2017 Nate Coraor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,5 +0,0 @@
As a special exception to the GNU Lesser General Public License version 2.1, the object code form of a "work
that uses the Library" may incorporate material from a header file that is part of the Library. You may
distribute such object code under terms of your choice, provided that the incorporated material (i) does not
exceed more than 5% of the total size of the Library; and (ii) is limited to numerical parameters, data
structure layouts, accessors, macros, inline functions and templates.

View File

@@ -1,502 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
0the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -1,4 +1,6 @@
![MultiMC](http://i.imgur.com/QJXbz.png) <p align="center">
<img src="https://avatars2.githubusercontent.com/u/5411890" alt="MultiMC logo"/>
</p>
MultiMC 5 MultiMC 5
========= =========
@@ -11,7 +13,7 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features. We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [![Stories in Ready](https://badge.waffle.io/MultiMC/MultiMC5.svg?label=ready&title=Ready)](http://waffle.io/MultiMC/MultiMC5) If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
### Building ### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions. If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
@@ -25,9 +27,7 @@ We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the
## Translations ## Translations
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). 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).
Currently, MultiMC is [![Translation Status](http://translate.multimc.org/widgets/multimc/-/shields-badge.svg)](http://translate.multimc.org/engage/multimc/?utm_source=widget)
## Forking/Redistributing ## 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. We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
@@ -38,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License ## License
Copyright &copy; 2013-2017 MultiMC Contributors Copyright &copy; 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). 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).

View File

@@ -1,17 +1,17 @@
project(MultiMC_gui LANGUAGES CXX) project(MultiMC_gui LANGUAGES CXX)
set(GUI_SOURCES set(GUI_SOURCES
DesktopServices.h DesktopServices.h
DesktopServices.cpp DesktopServices.cpp
# Icons # Icons
icons/MMCIcon.h icons/MMCIcon.h
icons/MMCIcon.cpp icons/MMCIcon.cpp
icons/IconList.h icons/IconList.h
icons/IconList.cpp icons/IconList.cpp
SkinUtils.cpp SkinUtils.cpp
SkinUtils.h SkinUtils.h
) )
################################ COMPILE ################################ ################################ COMPILE ################################
@@ -21,8 +21,14 @@ set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBI
generate_export_header(MultiMC_gui) generate_export_header(MultiMC_gui)
# Link # Link
target_link_libraries(MultiMC_gui iconfix MultiMC_logic) target_link_libraries(MultiMC_gui MultiMC_iconfix MultiMC_logic Qt5::Gui)
qt5_use_modules(MultiMC_gui Gui)
# Mark and export headers # Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
# Install it
install(
TARGETS MultiMC_gui
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -17,132 +17,132 @@
template <typename T> template <typename T>
bool IndirectOpen(T callable, qint64 *pid_forked = nullptr) bool IndirectOpen(T callable, qint64 *pid_forked = nullptr)
{ {
auto pid = fork(); auto pid = fork();
if(pid_forked) if(pid_forked)
{ {
if(pid > 0) if(pid > 0)
*pid_forked = pid; *pid_forked = pid;
else else
*pid_forked = 0; *pid_forked = 0;
} }
if(pid == -1) if(pid == -1)
{ {
qWarning() << "IndirectOpen failed to fork: " << errno; qWarning() << "IndirectOpen failed to fork: " << errno;
return false; return false;
} }
// child - do the stuff // child - do the stuff
if(pid == 0) if(pid == 0)
{ {
// unset all this garbage so it doesn't get passed to the child process // unset all this garbage so it doesn't get passed to the child process
qunsetenv("LD_PRELOAD"); qunsetenv("LD_PRELOAD");
qunsetenv("LD_LIBRARY_PATH"); qunsetenv("LD_LIBRARY_PATH");
qunsetenv("LD_DEBUG"); qunsetenv("LD_DEBUG");
qunsetenv("QT_PLUGIN_PATH"); qunsetenv("QT_PLUGIN_PATH");
qunsetenv("QT_FONTPATH"); qunsetenv("QT_FONTPATH");
// open the URL // open the URL
auto status = callable(); auto status = callable();
// detach from the parent process group. // detach from the parent process group.
setsid(); setsid();
// die. now. do not clean up anything, it would just hang forever. // die. now. do not clean up anything, it would just hang forever.
_exit(status ? 0 : 1); _exit(status ? 0 : 1);
} }
else else
{ {
//parent - assume it worked. //parent - assume it worked.
int status; int status;
while (waitpid(pid, &status, 0)) while (waitpid(pid, &status, 0))
{ {
if(WIFEXITED(status)) if(WIFEXITED(status))
{ {
return WEXITSTATUS(status) == 0; return WEXITSTATUS(status) == 0;
} }
if(WIFSIGNALED(status)) if(WIFSIGNALED(status))
{ {
return false; return false;
} }
} }
return true; return true;
} }
} }
#endif #endif
namespace DesktopServices { namespace DesktopServices {
bool openDirectory(const QString &path, bool ensureExists) bool openDirectory(const QString &path, bool ensureExists)
{ {
qDebug() << "Opening directory" << path; qDebug() << "Opening directory" << path;
QDir parentPath; QDir parentPath;
QDir dir(path); QDir dir(path);
if (!dir.exists()) if (!dir.exists())
{ {
parentPath.mkpath(dir.absolutePath()); parentPath.mkpath(dir.absolutePath());
} }
auto f = [&]() auto f = [&]()
{ {
return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath()));
}; };
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
return IndirectOpen(f); return IndirectOpen(f);
#else #else
return f(); return f();
#endif #endif
} }
bool openFile(const QString &path) bool openFile(const QString &path)
{ {
qDebug() << "Opening file" << path; qDebug() << "Opening file" << path;
auto f = [&]() auto f = [&]()
{ {
return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); return QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}; };
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
return IndirectOpen(f); return IndirectOpen(f);
#else #else
return f(); return f();
#endif #endif
} }
bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid) bool openFile(const QString &application, const QString &path, const QString &workingDirectory, qint64 *pid)
{ {
qDebug() << "Opening file" << path << "using" << application; qDebug() << "Opening file" << path << "using" << application;
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
return QProcess::startDetached(application, QStringList() << path, workingDirectory); return QProcess::startDetached(application, QStringList() << path, workingDirectory);
}, pid); }, pid);
#else #else
return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid); return QProcess::startDetached(application, QStringList() << path, workingDirectory, pid);
#endif #endif
} }
bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid) bool run(const QString &application, const QStringList &args, const QString &workingDirectory, qint64 *pid)
{ {
qDebug() << "Running" << application << "with args" << args.join(' '); qDebug() << "Running" << application << "with args" << args.join(' ');
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
// FIXME: the pid here is fake. So if something depends on it, it will likely misbehave // FIXME: the pid here is fake. So if something depends on it, it will likely misbehave
return IndirectOpen([&]() return IndirectOpen([&]()
{ {
return QProcess::startDetached(application, args, workingDirectory); return QProcess::startDetached(application, args, workingDirectory);
}, pid); }, pid);
#else #else
return QProcess::startDetached(application, args, workingDirectory, pid); return QProcess::startDetached(application, args, workingDirectory, pid);
#endif #endif
} }
bool openUrl(const QUrl &url) bool openUrl(const QUrl &url)
{ {
qDebug() << "Opening URL" << url.toString(); qDebug() << "Opening URL" << url.toString();
auto f = [&]() auto f = [&]()
{ {
return QDesktopServices::openUrl(url); return QDesktopServices::openUrl(url);
}; };
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
return IndirectOpen(f); return IndirectOpen(f);
#else #else
return f(); return f();
#endif #endif
} }

View File

@@ -10,28 +10,28 @@
*/ */
namespace DesktopServices namespace DesktopServices
{ {
/** /**
* Open a file in whatever application is applicable * Open a file in whatever application is applicable
*/ */
MULTIMC_GUI_EXPORT bool openFile(const QString &path); MULTIMC_GUI_EXPORT bool openFile(const QString &path);
/** /**
* Open a file in the specified application * Open a file in the specified application
*/ */
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0); MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/** /**
* Run an application * Run an application
*/ */
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0); MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/** /**
* Open a directory * Open a directory
*/ */
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false); MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
/** /**
* Open the URL, most likely in a browser. Maybe. * Open the URL, most likely in a browser. Maybe.
*/ */
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url); MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
}; }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -29,19 +29,19 @@ namespace SkinUtils
*/ */
QPixmap getFaceFromCache(QString username, int height, int width) QPixmap getFaceFromCache(QString username, int height, int width)
{ {
QFile fskin(ENV.metacache() QFile fskin(ENV.metacache()
->resolveEntry("skins", username + ".png") ->resolveEntry("skins", username + ".png")
->getFullPath()); ->getFullPath());
if (fskin.exists()) if (fskin.exists())
{ {
QPixmap skin(fskin.fileName()); QPixmap skin(fskin.fileName());
if(!skin.isNull()) if(!skin.isNull())
{ {
return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio); return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
} }
} }
return QPixmap(); return QPixmap();
} }
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,392 +27,393 @@
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent) IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
{ {
QSet<QString> builtinNames; QSet<QString> builtinNames;
// add builtin icons // add builtin icons
for(auto & builtinPath: builtinPaths) for(auto & builtinPath: builtinPaths)
{ {
QDir instance_icons(builtinPath); QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list) for (auto file_info : file_info_list)
{ {
builtinNames.insert(file_info.baseName()); builtinNames.insert(file_info.baseName());
} }
} }
for(auto & builtinName : builtinNames) for(auto & builtinName : builtinNames)
{ {
addThemeIcon(builtinName); addThemeIcon(builtinName);
} }
m_watcher.reset(new QFileSystemWatcher()); m_watcher.reset(new QFileSystemWatcher());
is_watching = false; is_watching = false;
connect(m_watcher.get(), SIGNAL(directoryChanged(QString)), connect(m_watcher.get(), SIGNAL(directoryChanged(QString)),
SLOT(directoryChanged(QString))); SLOT(directoryChanged(QString)));
connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
directoryChanged(path); directoryChanged(path);
} }
void IconList::directoryChanged(const QString &path) void IconList::directoryChanged(const QString &path)
{ {
QDir new_dir (path); QDir new_dir (path);
if(m_dir.absolutePath() != new_dir.absolutePath()) if(m_dir.absolutePath() != new_dir.absolutePath())
{ {
m_dir.setPath(path); m_dir.setPath(path);
m_dir.refresh(); m_dir.refresh();
if(is_watching) if(is_watching)
stopWatching(); stopWatching();
startWatching(); startWatching();
} }
if(!m_dir.exists()) if(!m_dir.exists())
if(!FS::ensureFolderPathExists(m_dir.absolutePath())) if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
return; return;
m_dir.refresh(); m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name); auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
for (auto it = new_list.begin(); it != new_list.end(); it++) for (auto it = new_list.begin(); it != new_list.end(); it++)
{ {
QString &foo = (*it); QString &foo = (*it);
foo = m_dir.filePath(foo); foo = m_dir.filePath(foo);
} }
auto new_set = new_list.toSet(); auto new_set = new_list.toSet();
QList<QString> current_list; QList<QString> current_list;
for (auto &it : icons) for (auto &it : icons)
{ {
if (!it.has(IconType::FileBased)) if (!it.has(IconType::FileBased))
continue; continue;
current_list.push_back(it.m_images[IconType::FileBased].filename); current_list.push_back(it.m_images[IconType::FileBased].filename);
} }
QSet<QString> current_set = current_list.toSet(); QSet<QString> current_set = current_list.toSet();
QSet<QString> to_remove = current_set; QSet<QString> to_remove = current_set;
to_remove -= new_set; to_remove -= new_set;
QSet<QString> to_add = new_set; QSet<QString> to_add = new_set;
to_add -= current_set; to_add -= current_set;
for (auto remove : to_remove) for (auto remove : to_remove)
{ {
qDebug() << "Removing " << remove; qDebug() << "Removing " << remove;
QFileInfo rmfile(remove); QFileInfo rmfile(remove);
QString key = rmfile.baseName(); QString key = rmfile.baseName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
continue; continue;
icons[idx].remove(IconType::FileBased); icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted) if (icons[idx].type() == IconType::ToBeDeleted)
{ {
beginRemoveRows(QModelIndex(), idx, idx); beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx); icons.remove(idx);
reindex(); reindex();
endRemoveRows(); endRemoveRows();
} }
else else
{ {
dataChanged(index(idx), index(idx)); dataChanged(index(idx), index(idx));
} }
m_watcher->removePath(remove); m_watcher->removePath(remove);
emit iconUpdated(key); emit iconUpdated(key);
} }
for (auto add : to_add) for (auto add : to_add)
{ {
qDebug() << "Adding " << add; qDebug() << "Adding " << add;
QFileInfo addfile(add); QFileInfo addfile(add);
QString key = addfile.baseName(); QString key = addfile.baseName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
{ {
m_watcher->addPath(add); m_watcher->addPath(add);
emit iconUpdated(key); emit iconUpdated(key);
} }
} }
} }
void IconList::fileChanged(const QString &path) void IconList::fileChanged(const QString &path)
{ {
qDebug() << "Checking " << path; qDebug() << "Checking " << path;
QFileInfo checkfile(path); QFileInfo checkfile(path);
if (!checkfile.exists()) if (!checkfile.exists())
return; return;
QString key = checkfile.baseName(); QString key = checkfile.baseName();
int idx = getIconIndex(key); int idx = getIconIndex(key);
if (idx == -1) if (idx == -1)
return; return;
QIcon icon(path); QIcon icon(path);
if (!icon.availableSizes().size()) if (!icon.availableSizes().size())
return; return;
icons[idx].m_images[IconType::FileBased].icon = icon; icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx)); dataChanged(index(idx), index(idx));
emit iconUpdated(key); emit iconUpdated(key);
} }
void IconList::SettingChanged(const Setting &setting, QVariant value) void IconList::SettingChanged(const Setting &setting, QVariant value)
{ {
if(setting.id() != "IconsDir") if(setting.id() != "IconsDir")
return; return;
directoryChanged(value.toString()); directoryChanged(value.toString());
} }
void IconList::startWatching() void IconList::startWatching()
{ {
auto abs_path = m_dir.absolutePath(); auto abs_path = m_dir.absolutePath();
FS::ensureFolderPathExists(abs_path); FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path); is_watching = m_watcher->addPath(abs_path);
if (is_watching) if (is_watching)
{ {
qDebug() << "Started watching " << abs_path; qDebug() << "Started watching " << abs_path;
} }
else else
{ {
qDebug() << "Failed to start watching " << abs_path; qDebug() << "Failed to start watching " << abs_path;
} }
} }
void IconList::stopWatching() void IconList::stopWatching()
{ {
m_watcher->removePaths(m_watcher->files()); m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories()); m_watcher->removePaths(m_watcher->directories());
is_watching = false; is_watching = false;
} }
QStringList IconList::mimeTypes() const QStringList IconList::mimeTypes() const
{ {
QStringList types; QStringList types;
types << "text/uri-list"; types << "text/uri-list";
return types; return types;
} }
Qt::DropActions IconList::supportedDropActions() const Qt::DropActions IconList::supportedDropActions() const
{ {
return Qt::CopyAction; return Qt::CopyAction;
} }
bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
const QModelIndex &parent)
{ {
if (action == Qt::IgnoreAction) if (action == Qt::IgnoreAction)
return true; return true;
// check if the action is supported // check if the action is supported
if (!data || !(action & supportedDropActions())) if (!data || !(action & supportedDropActions()))
return false; return false;
// files dropped from outside? // files dropped from outside?
if (data->hasUrls()) if (data->hasUrls())
{ {
auto urls = data->urls(); auto urls = data->urls();
QStringList iconFiles; QStringList iconFiles;
for (auto url : urls) for (auto url : urls)
{ {
// only local files may be dropped... // only local files may be dropped...
if (!url.isLocalFile()) if (!url.isLocalFile())
continue; continue;
iconFiles += url.toLocalFile(); iconFiles += url.toLocalFile();
} }
installIcons(iconFiles); installIcons(iconFiles);
return true; return true;
} }
return false; return false;
} }
Qt::ItemFlags IconList::flags(const QModelIndex &index) const Qt::ItemFlags IconList::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid()) if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags; return Qt::ItemIsDropEnabled | defaultFlags;
else else
return Qt::ItemIsDropEnabled | defaultFlags; return Qt::ItemIsDropEnabled | defaultFlags;
} }
QVariant IconList::data(const QModelIndex &index, int role) const QVariant IconList::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
int row = index.row(); int row = index.row();
if (row < 0 || row >= icons.size()) if (row < 0 || row >= icons.size())
return QVariant(); return QVariant();
switch (role) switch (role)
{ {
case Qt::DecorationRole: case Qt::DecorationRole:
return icons[row].icon(); return icons[row].icon();
case Qt::DisplayRole: case Qt::DisplayRole:
return icons[row].name(); return icons[row].name();
case Qt::UserRole: case Qt::UserRole:
return icons[row].m_key; return icons[row].m_key;
default: default:
return QVariant(); return QVariant();
} }
} }
int IconList::rowCount(const QModelIndex &parent) const int IconList::rowCount(const QModelIndex &parent) const
{ {
return icons.size(); return icons.size();
} }
void IconList::installIcons(const QStringList &iconFiles) void IconList::installIcons(const QStringList &iconFiles)
{ {
for (QString file : iconFiles) for (QString file : iconFiles)
{ {
QFileInfo fileinfo(file); QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile()) if (!fileinfo.isReadable() || !fileinfo.isFile())
continue; continue;
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName()); QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix(); QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico") if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
continue; continue;
if (!QFile::copy(file, target)) if (!QFile::copy(file, target))
continue; continue;
} }
}
void IconList::installIcon(const QString &file, const QString &name)
{
QFileInfo fileinfo(file);
if(!fileinfo.isReadable() || !fileinfo.isFile())
return;
QString target = FS::PathCombine(m_dir.dirName(), name);
QFile::copy(file, target);
} }
bool IconList::iconFileExists(const QString &key) const bool IconList::iconFileExists(const QString &key) const
{ {
auto iconEntry = icon(key); auto iconEntry = icon(key);
if(!iconEntry) if(!iconEntry)
{ {
return false; return false;
} }
return iconEntry->has(IconType::FileBased); return iconEntry->has(IconType::FileBased);
} }
const MMCIcon *IconList::icon(const QString &key) const const MMCIcon *IconList::icon(const QString &key) const
{ {
int iconIdx = getIconIndex(key); int iconIdx = getIconIndex(key);
if (iconIdx == -1) if (iconIdx == -1)
return nullptr; return nullptr;
return &icons[iconIdx]; return &icons[iconIdx];
} }
bool IconList::deleteIcon(const QString &key) bool IconList::deleteIcon(const QString &key)
{ {
int iconIdx = getIconIndex(key); int iconIdx = getIconIndex(key);
if (iconIdx == -1) if (iconIdx == -1)
return false; return false;
auto &iconEntry = icons[iconIdx]; auto &iconEntry = icons[iconIdx];
if (iconEntry.has(IconType::FileBased)) if (iconEntry.has(IconType::FileBased))
{ {
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename); return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
} }
return false; return false;
} }
bool IconList::addThemeIcon(const QString& key) bool IconList::addThemeIcon(const QString& key)
{ {
auto iter = name_index.find(key); auto iter = name_index.find(key);
if (iter != name_index.end()) if (iter != name_index.end())
{ {
auto &oldOne = icons[*iter]; auto &oldOne = icons[*iter];
oldOne.replace(Builtin, key); oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
else else
{ {
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), icons.size(), icons.size());
{ {
MMCIcon mmc_icon; MMCIcon mmc_icon;
mmc_icon.m_name = key; mmc_icon.m_name = key;
mmc_icon.m_key = key; mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key); mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon); icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1; name_index[key] = icons.size() - 1;
} }
endInsertRows(); endInsertRows();
return true; return true;
} }
} }
bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type) bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
{ {
// replace the icon even? is the input valid? // replace the icon even? is the input valid?
QIcon icon(path); QIcon icon(path);
if (!icon.availableSizes().size()) if (icon.isNull())
return false; return false;
auto iter = name_index.find(key); auto iter = name_index.find(key);
if (iter != name_index.end()) if (iter != name_index.end())
{ {
auto &oldOne = icons[*iter]; auto &oldOne = icons[*iter];
oldOne.replace(type, icon, path); oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter)); dataChanged(index(*iter), index(*iter));
return true; return true;
} }
else else
{ {
// add a new icon // add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size()); beginInsertRows(QModelIndex(), icons.size(), icons.size());
{ {
MMCIcon mmc_icon; MMCIcon mmc_icon;
mmc_icon.m_name = name; mmc_icon.m_name = name;
mmc_icon.m_key = key; mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path); mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon); icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1; name_index[key] = icons.size() - 1;
} }
endInsertRows(); endInsertRows();
return true; return true;
} }
} }
void IconList::saveIcon(const QString &key, const QString &path, const char * format) const void IconList::saveIcon(const QString &key, const QString &path, const char * format) const
{ {
auto icon = getIcon(key); auto icon = getIcon(key);
auto pixmap = icon.pixmap(128, 128); auto pixmap = icon.pixmap(128, 128);
pixmap.save(path, format); pixmap.save(path, format);
} }
void IconList::reindex() void IconList::reindex()
{ {
name_index.clear(); name_index.clear();
int i = 0; int i = 0;
for (auto &iter : icons) for (auto &iter : icons)
{ {
name_index[iter.m_key] = i; name_index[iter.m_key] = i;
i++; i++;
} }
} }
QIcon IconList::getIcon(const QString &key) const QIcon IconList::getIcon(const QString &key) const
{ {
int icon_index = getIconIndex(key); int icon_index = getIconIndex(key);
if (icon_index != -1) if (icon_index != -1)
return icons[icon_index].icon(); return icons[icon_index].icon();
// Fallback for icons that don't exist. // Fallback for icons that don't exist.
icon_index = getIconIndex("infinity"); icon_index = getIconIndex("infinity");
if (icon_index != -1) if (icon_index != -1)
return icons[icon_index].icon(); return icons[icon_index].icon();
return QIcon(); return QIcon();
}
QIcon IconList::getBigIcon(const QString &key) const
{
int icon_index = getIconIndex(key);
// Fallback for icons that don't exist.
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
if (icon_index == -1)
return QIcon();
QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
return QIcon(bigone);
} }
int IconList::getIconIndex(const QString &key) const int IconList::getIconIndex(const QString &key) const
{ {
auto iter = name_index.find(key == "default" ? "infinity" : key); auto iter = name_index.find(key == "default" ? "infinity" : key);
if (iter != name_index.end()) if (iter != name_index.end())
return *iter; return *iter;
return -1; return -1;
}
QString IconList::getDirectory() const
{
return m_dir.absolutePath();
} }
//#include "IconList.moc" //#include "IconList.moc"

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -32,56 +32,57 @@ class QFileSystemWatcher;
class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0); explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
virtual ~IconList() {}; virtual ~IconList() {};
QIcon getIcon(const QString &key) const; QIcon getIcon(const QString &key) const;
QIcon getBigIcon(const QString &key) const; int getIconIndex(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 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool addThemeIcon(const QString &key); bool addThemeIcon(const QString &key);
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override; bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override;
void saveIcon(const QString &key, const QString &path, const char * format) const override; void saveIcon(const QString &key, const QString &path, const char * format) const override;
bool deleteIcon(const QString &key) override; bool deleteIcon(const QString &key) override;
bool iconFileExists(const QString &key) const override; bool iconFileExists(const QString &key) const override;
virtual QStringList mimeTypes() const override; virtual QStringList mimeTypes() const override;
virtual Qt::DropActions supportedDropActions() const override; virtual Qt::DropActions supportedDropActions() const override;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void installIcons(const QStringList &iconFiles) override; void installIcons(const QStringList &iconFiles) override;
void installIcon(const QString &file, const QString &name) override;
const MMCIcon * icon(const QString &key) const; const MMCIcon * icon(const QString &key) const;
void startWatching(); void startWatching();
void stopWatching(); void stopWatching();
signals: signals:
void iconUpdated(QString key); void iconUpdated(QString key);
private: private:
// hide copy constructor // hide copy constructor
IconList(const IconList &) = delete; IconList(const IconList &) = delete;
// hide assign op // hide assign op
IconList &operator=(const IconList &) = delete; IconList &operator=(const IconList &) = delete;
void reindex(); void reindex();
public slots: public slots:
void directoryChanged(const QString &path); void directoryChanged(const QString &path);
protected slots: protected slots:
void fileChanged(const QString &path); void fileChanged(const QString &path);
void SettingChanged(const Setting & setting, QVariant value); void SettingChanged(const Setting & setting, QVariant value);
private: private:
std::shared_ptr<QFileSystemWatcher> m_watcher; shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching; bool is_watching;
QMap<QString, int> name_index; QMap<QString, int> name_index;
QVector<MMCIcon> icons; QVector<MMCIcon> icons;
QDir m_dir; QDir m_dir;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,86 +19,100 @@
IconType operator--(IconType &t, int) IconType operator--(IconType &t, int)
{ {
IconType temp = t; IconType temp = t;
switch (t) switch (t)
{ {
case IconType::Builtin: case IconType::Builtin:
t = IconType::ToBeDeleted; t = IconType::ToBeDeleted;
break; break;
case IconType::Transient: case IconType::Transient:
t = IconType::Builtin; t = IconType::Builtin;
break; break;
case IconType::FileBased: case IconType::FileBased:
t = IconType::Transient; t = IconType::Transient;
break; break;
default: default:
{ {
} }
} }
return temp; return temp;
} }
IconType MMCIcon::type() const IconType MMCIcon::type() const
{ {
return m_current_type; return m_current_type;
} }
QString MMCIcon::name() const QString MMCIcon::name() const
{ {
if (m_name.size()) if (m_name.size())
return m_name; return m_name;
return m_key; return m_key;
} }
bool MMCIcon::has(IconType _type) const bool MMCIcon::has(IconType _type) const
{ {
return m_images[_type].present(); return m_images[_type].present();
} }
QIcon MMCIcon::icon() const QIcon MMCIcon::icon() const
{ {
if (m_current_type == IconType::ToBeDeleted) if (m_current_type == IconType::ToBeDeleted)
return QIcon(); return QIcon();
auto & icon = m_images[m_current_type].icon; auto & icon = m_images[m_current_type].icon;
if(!icon.isNull()) if(!icon.isNull())
return icon; return icon;
// FIXME: inject this. // FIXME: inject this.
return XdgIcon::fromTheme(m_images[m_current_type].key); return XdgIcon::fromTheme(m_images[m_current_type].key);
} }
void MMCIcon::remove(IconType rm_type) void MMCIcon::remove(IconType rm_type)
{ {
m_images[rm_type].filename = QString(); m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon(); m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--) for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
{ {
if (m_images[iter].present()) if (m_images[iter].present())
{ {
m_current_type = iter; m_current_type = iter;
return; return;
} }
} }
m_current_type = IconType::ToBeDeleted; m_current_type = IconType::ToBeDeleted;
} }
void MMCIcon::replace(IconType new_type, QIcon icon, QString path) void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
{ {
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
{ {
m_current_type = new_type; m_current_type = new_type;
} }
m_images[new_type].icon = icon; m_images[new_type].icon = icon;
m_images[new_type].filename = path; m_images[new_type].filename = path;
m_images[new_type].key = QString(); m_images[new_type].key = QString();
} }
void MMCIcon::replace(IconType new_type, const QString& key) void MMCIcon::replace(IconType new_type, const QString& key)
{ {
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted) if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
{ {
m_current_type = new_type; m_current_type = new_type;
} }
m_images[new_type].icon = QIcon(); m_images[new_type].icon = QIcon();
m_images[new_type].filename = QString(); m_images[new_type].filename = QString();
m_images[new_type].key = key; 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;
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,27 +23,29 @@
struct MULTIMC_GUI_EXPORT MMCImage struct MULTIMC_GUI_EXPORT MMCImage
{ {
QIcon icon; QIcon icon;
QString key; QString key;
QString filename; QString filename;
bool present() const bool present() const
{ {
return !icon.isNull() || !key.isEmpty(); return !icon.isNull() || !key.isEmpty();
} }
}; };
struct MULTIMC_GUI_EXPORT MMCIcon struct MULTIMC_GUI_EXPORT MMCIcon
{ {
QString m_key; QString m_key;
QString m_name; QString m_name;
MMCImage m_images[ICONS_TOTAL]; MMCImage m_images[ICONS_TOTAL];
IconType m_current_type = ToBeDeleted; IconType m_current_type = ToBeDeleted;
IconType type() const; IconType type() const;
QString name() const; QString name() const;
bool has(IconType _type) const; bool has(IconType _type) const;
QIcon icon() const; QIcon icon() const;
void remove(IconType rm_type); void remove(IconType rm_type);
void replace(IconType new_type, QIcon icon, QString path = QString()); void replace(IconType new_type, QIcon icon, QString path = QString());
void replace(IconType new_type, const QString &key); void replace(IconType new_type, const QString &key);
bool isBuiltIn() const;
QString getFilePath() const;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,46 +16,46 @@
#include <QFile> #include <QFile>
#include "BaseInstaller.h" #include "BaseInstaller.h"
#include "minecraft/onesix/OneSixInstance.h" #include "minecraft/MinecraftInstance.h"
BaseInstaller::BaseInstaller() BaseInstaller::BaseInstaller()
{ {
} }
bool BaseInstaller::isApplied(OneSixInstance *on) bool BaseInstaller::isApplied(MinecraftInstance *on)
{ {
return QFile::exists(filename(on->instanceRoot())); return QFile::exists(filename(on->instanceRoot()));
} }
bool BaseInstaller::add(OneSixInstance *to) bool BaseInstaller::add(MinecraftInstance *to)
{ {
if (!patchesDir(to->instanceRoot()).exists()) if (!patchesDir(to->instanceRoot()).exists())
{ {
QDir(to->instanceRoot()).mkdir("patches"); QDir(to->instanceRoot()).mkdir("patches");
} }
if (isApplied(to)) if (isApplied(to))
{ {
if (!remove(to)) if (!remove(to))
{ {
return false; return false;
} }
} }
return true; return true;
} }
bool BaseInstaller::remove(OneSixInstance *from) bool BaseInstaller::remove(MinecraftInstance *from)
{ {
return QFile::remove(filename(from->instanceRoot())); return QFile::remove(filename(from->instanceRoot()));
} }
QString BaseInstaller::filename(const QString &root) const QString BaseInstaller::filename(const QString &root) const
{ {
return patchesDir(root).absoluteFilePath(id() + ".json"); return patchesDir(root).absoluteFilePath(id() + ".json");
} }
QDir BaseInstaller::patchesDir(const QString &root) const QDir BaseInstaller::patchesDir(const QString &root) const
{ {
return QDir(root + "/patches/"); return QDir(root + "/patches/");
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class OneSixInstance; class MinecraftInstance;
class QDir; class QDir;
class QString; class QString;
class QObject; class QObject;
@@ -30,17 +30,17 @@ typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
class MULTIMC_LOGIC_EXPORT BaseInstaller class MULTIMC_LOGIC_EXPORT BaseInstaller
{ {
public: public:
BaseInstaller(); BaseInstaller();
virtual ~BaseInstaller(){}; virtual ~BaseInstaller(){};
bool isApplied(OneSixInstance *on); bool isApplied(MinecraftInstance *on);
virtual bool add(OneSixInstance *to); virtual bool add(MinecraftInstance *to);
virtual bool remove(OneSixInstance *from); virtual bool remove(MinecraftInstance *from);
virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0; virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
protected: protected:
virtual QString id() const = 0; virtual QString id() const = 0;
QString filename(const QString &root) const; QString filename(const QString &root) const;
QDir patchesDir(const QString &root) const; QDir patchesDir(const QString &root) const;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,285 +23,232 @@
#include "settings/Setting.h" #include "settings/Setting.h"
#include "settings/OverrideSetting.h" #include "settings/OverrideSetting.h"
#include "minecraft/MinecraftVersionList.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Commandline.h" #include "Commandline.h"
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: QObject() : QObject()
{ {
m_settings = settings; m_settings = settings;
m_rootDir = rootDir; m_rootDir = rootDir;
m_settings->registerSetting("name", "Unnamed Instance"); m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default"); m_settings->registerSetting("iconKey", "default");
m_settings->registerSetting("notes", ""); m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("totalTimePlayed", 0);
// Custom Commands // Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false); auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting); m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting); m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting); m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
// Console // Console
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false); auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting); m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting); m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting); m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting); m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr); m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
} }
QString BaseInstance::getPreLaunchCommand() QString BaseInstance::getPreLaunchCommand()
{ {
return settings()->get("PreLaunchCommand").toString(); return settings()->get("PreLaunchCommand").toString();
} }
QString BaseInstance::getWrapperCommand() QString BaseInstance::getWrapperCommand()
{ {
return settings()->get("WrapperCommand").toString(); return settings()->get("WrapperCommand").toString();
} }
QString BaseInstance::getPostExitCommand() QString BaseInstance::getPostExitCommand()
{ {
return settings()->get("PostExitCommand").toString(); return settings()->get("PostExitCommand").toString();
} }
int BaseInstance::getConsoleMaxLines() const int BaseInstance::getConsoleMaxLines() const
{ {
auto lineSetting = settings()->getSetting("ConsoleMaxLines"); auto lineSetting = settings()->getSetting("ConsoleMaxLines");
bool conversionOk = false; bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk); int maxLines = lineSetting->get().toInt(&conversionOk);
if(!conversionOk) if(!conversionOk)
{ {
maxLines = lineSetting->defValue().toInt(); maxLines = lineSetting->defValue().toInt();
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines; qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
} }
return maxLines; return maxLines;
} }
bool BaseInstance::shouldStopOnConsoleOverflow() const bool BaseInstance::shouldStopOnConsoleOverflow() const
{ {
return settings()->get("ConsoleOverflowStop").toBool(); return settings()->get("ConsoleOverflowStop").toBool();
} }
void BaseInstance::iconUpdated(QString key) void BaseInstance::iconUpdated(QString key)
{ {
if(iconKey() == key) if(iconKey() == key)
{ {
emit propertiesChanged(this); emit propertiesChanged(this);
} }
} }
void BaseInstance::invalidate() void BaseInstance::invalidate()
{ {
changeStatus(Status::Gone); changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been invalidated."; 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) void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{ {
Status status = currentStatus(); Status status = currentStatus();
if(status != newStatus) if(status != newStatus)
{ {
m_status = newStatus; m_status = newStatus;
emit statusChanged(status, newStatus); emit statusChanged(status, newStatus);
} }
} }
BaseInstance::Status BaseInstance::currentStatus() const BaseInstance::Status BaseInstance::currentStatus() const
{ {
return m_status; return m_status;
} }
QString BaseInstance::id() const QString BaseInstance::id() const
{ {
return QFileInfo(instanceRoot()).fileName(); return QFileInfo(instanceRoot()).fileName();
} }
bool BaseInstance::isRunning() const bool BaseInstance::isRunning() const
{ {
return m_isRunning; return m_isRunning;
} }
void BaseInstance::setRunning(bool running) void BaseInstance::setRunning(bool running)
{ {
if(running == m_isRunning) if(running == m_isRunning)
return; return;
m_isRunning = running; m_isRunning = running;
if(running) if(running)
{ {
m_timeStarted = QDateTime::currentDateTime(); m_timeStarted = QDateTime::currentDateTime();
} }
else else
{ {
qint64 current = settings()->get("totalTimePlayed").toLongLong(); qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime(); QDateTime timeEnded = QDateTime::currentDateTime();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded)); settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
emit propertiesChanged(this); emit propertiesChanged(this);
} }
emit runningStatusChanged(running); emit runningStatusChanged(running);
} }
int64_t BaseInstance::totalTimePlayed() const int64_t BaseInstance::totalTimePlayed() const
{ {
qint64 current = settings()->get("totalTimePlayed").toLongLong(); qint64 current = settings()->get("totalTimePlayed").toLongLong();
if(m_isRunning) if(m_isRunning)
{ {
QDateTime timeNow = QDateTime::currentDateTime(); QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow); return current + m_timeStarted.secsTo(timeNow);
} }
return current; return current;
} }
void BaseInstance::resetTimePlayed() void BaseInstance::resetTimePlayed()
{ {
settings()->reset("totalTimePlayed"); settings()->reset("totalTimePlayed");
} }
QString BaseInstance::instanceType() const QString BaseInstance::instanceType() const
{ {
return m_settings->get("InstanceType").toString(); return m_settings->get("InstanceType").toString();
} }
QString BaseInstance::instanceRoot() const QString BaseInstance::instanceRoot() const
{ {
return m_rootDir; return m_rootDir;
}
InstancePtr BaseInstance::getSharedPtr()
{
return shared_from_this();
} }
SettingsObjectPtr BaseInstance::settings() const SettingsObjectPtr BaseInstance::settings() const
{ {
return m_settings; return m_settings;
} }
bool BaseInstance::canLaunch() const bool BaseInstance::canLaunch() const
{ {
return (!hasVersionBroken() && !isRunning()); return (!hasVersionBroken() && !isRunning());
} }
bool BaseInstance::reload() bool BaseInstance::reloadSettings()
{ {
return m_settings->reload(); return m_settings->reload();
} }
qint64 BaseInstance::lastLaunch() const qint64 BaseInstance::lastLaunch() const
{ {
return m_settings->get("lastLaunchTime").value<qint64>(); return m_settings->get("lastLaunchTime").value<qint64>();
} }
void BaseInstance::setLastLaunch(qint64 val) void BaseInstance::setLastLaunch(qint64 val)
{ {
//FIXME: if no change, do not set. setting involves saving a file. //FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("lastLaunchTime", val); m_settings->set("lastLaunchTime", val);
emit propertiesChanged(this); 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) void BaseInstance::setNotes(QString val)
{ {
//FIXME: if no change, do not set. setting involves saving a file. //FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("notes", val); m_settings->set("notes", val);
} }
QString BaseInstance::notes() const QString BaseInstance::notes() const
{ {
return m_settings->get("notes").toString(); return m_settings->get("notes").toString();
} }
void BaseInstance::setIconKey(QString val) void BaseInstance::setIconKey(QString val)
{ {
//FIXME: if no change, do not set. setting involves saving a file. //FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("iconKey", val); m_settings->set("iconKey", val);
emit propertiesChanged(this); emit propertiesChanged(this);
} }
QString BaseInstance::iconKey() const QString BaseInstance::iconKey() const
{ {
return m_settings->get("iconKey").toString(); return m_settings->get("iconKey").toString();
} }
void BaseInstance::setName(QString val) void BaseInstance::setName(QString val)
{ {
//FIXME: if no change, do not set. setting involves saving a file. //FIXME: if no change, do not set. setting involves saving a file.
m_settings->set("name", val); m_settings->set("name", val);
emit propertiesChanged(this); emit propertiesChanged(this);
} }
QString BaseInstance::name() const QString BaseInstance::name() const
{ {
return m_settings->get("name").toString(); return m_settings->get("name").toString();
} }
QString BaseInstance::windowTitle() 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!!!
QStringList BaseInstance::extraArguments() const QStringList BaseInstance::extraArguments() const
{ {
return Commandline::splitArgs(settings()->get("JvmArgs").toString()); return Commandline::splitArgs(settings()->get("JvmArgs").toString());
} }
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask() shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
{ {
return m_launchProcess; 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;
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -30,13 +30,14 @@
#include "MessageLevel.h" #include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h" #include "pathmatcher/IPathMatcher.h"
#include "net/Mode.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class QDir; class QDir;
class Task; class Task;
class LaunchTask; class LaunchTask;
class BaseInstance; class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people // pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr; typedef std::shared_ptr<BaseInstance> InstancePtr;
@@ -51,260 +52,217 @@ typedef std::shared_ptr<BaseInstance> InstancePtr;
*/ */
class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance> class MULTIMC_LOGIC_EXPORT BaseInstance : public QObject, public std::enable_shared_from_this<BaseInstance>
{ {
Q_OBJECT Q_OBJECT
protected: protected:
/// no-touchy! /// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir); BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public: /* types */ public: /* types */
enum class Status enum class Status
{ {
Present, Present,
Gone // either nuked or invalidated Gone // either nuked or invalidated
}; };
public: public:
/// virtual destructor to make sure the destruction is COMPLETE /// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {}; virtual ~BaseInstance() {};
virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {} virtual void saveNow() = 0;
virtual void init() = 0; /***
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
* but it has not necessarily been deleted.
*
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
*/
void invalidate();
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is /// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
/// responsible of cleaning up the husk /// be unique.
void nuke(); virtual QString id() const;
/*** void setRunning(bool running);
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason, bool isRunning() const;
* but it has not necessarily been deleted. int64_t totalTimePlayed() const;
* void resetTimePlayed();
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
*/
void invalidate();
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to /// get the type of this instance
/// be unique. QString instanceType() const;
virtual QString id() const;
void setRunning(bool running); /// Path to the instance's root directory.
bool isRunning() const; QString instanceRoot() const;
int64_t totalTimePlayed() const;
void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider); /// Path to the instance's game root directory.
BaseInstanceProvider * provider() const; virtual QString gameRoot() const
{
return instanceRoot();
}
/// get the type of this instance QString name() const;
QString instanceType() const; void setName(QString val);
/// Path to the instance's root directory. /// Value used for instance window titles
QString instanceRoot() const; QString windowTitle() const;
QString name() const; QString iconKey() const;
void setName(QString val); void setIconKey(QString val);
/// Value used for instance window titles QString notes() const;
QString windowTitle() const; void setNotes(QString val);
QString iconKey() const; QString getPreLaunchCommand();
void setIconKey(QString val); QString getPostExitCommand();
QString getWrapperCommand();
QString notes() const; /// guess log level from a line of game log
void setNotes(QString val); virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
return level;
};
QString group() const; virtual QStringList extraArguments() const;
void setGroupInitial(QString val);
void setGroupPost(QString val);
QString getPreLaunchCommand(); /// Traits. Normally inside the version, depends on instance implementation.
QString getPostExitCommand(); virtual QSet <QString> traits() const = 0;
QString getWrapperCommand();
/// guess log level from a line of game log /**
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) * Gets the time that the instance was last launched.
{ * Stored in milliseconds since epoch.
return level; */
}; qint64 lastLaunch() const;
/// Sets the last launched time to 'val' milliseconds since epoch
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
virtual QStringList extraArguments() const; /*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
*/
virtual SettingsObjectPtr settings() const;
virtual QString intendedVersionId() const = 0; /// returns a valid update task
virtual bool setIntendedVersionId(QString version) = 0; virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/*! /// returns a valid launcher (task container)
* The instance's current version. virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
* This value represents the instance's current version. If this value is
* different from the intendedVersion, the instance should be updated.
* \warning Don't change this value unless you know what you're doing.
*/
virtual QString currentVersionId() const = 0;
/*! /// returns the current launch task (if any)
* Whether or not 'the game' should be downloaded when the instance is launched. shared_qobject_ptr<LaunchTask> getLaunchTask();
*/
virtual bool shouldUpdate() const = 0;
virtual void setShouldUpdate(bool val) = 0;
/// Traits. Normally inside the version, depends on instance implementation. /*!
virtual QSet <QString> traits() = 0; * Create envrironment variables for running the instance
*/
virtual QProcessEnvironment createEnvironment() = 0;
/** /*!
* Gets the time that the instance was last launched. * Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
* Stored in milliseconds since epoch. */
*/ virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
qint64 lastLaunch() const;
/// Sets the last launched time to 'val' milliseconds since epoch
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
InstancePtr getSharedPtr(); /*!
* Returns the root folder to use for looking up log files
*/
virtual QString getLogFileRoot() = 0;
/*! virtual QString getStatusbarDescription() = 0;
* \brief Gets a pointer to this instance's version list.
* \return A pointer to the available version list for this instance.
*/
virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
/*! /// FIXME: this really should be elsewhere...
* \brief Gets this instance's settings object. virtual QString instanceConfigFolder() const = 0;
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
*/
virtual SettingsObjectPtr settings() const;
/// returns a valid update task /// get variables this instance exports
virtual shared_qobject_ptr<Task> createUpdateTask() = 0; virtual QMap<QString, QString> getVariables() const = 0;
/// returns a valid launcher (task container) virtual QString typeName() const = 0;
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
/// returns the current launch task (if any) bool hasVersionBroken() const
std::shared_ptr<LaunchTask> getLaunchTask(); {
return m_hasBrokenVersion;
}
void setVersionBroken(bool value)
{
if(m_hasBrokenVersion != value)
{
m_hasBrokenVersion = value;
emit propertiesChanged(this);
}
}
/*! bool hasUpdateAvailable() const
* Returns a task that should be done right before launch {
* This task should do any extra preparations needed return m_hasUpdate;
*/ }
virtual std::shared_ptr<Task> createJarModdingTask() = 0; void setUpdateAvailable(bool value)
{
if(m_hasUpdate != value)
{
m_hasUpdate = value;
emit propertiesChanged(this);
}
}
/*! bool hasCrashed() const
* Create envrironment variables for running the instance {
*/ return m_crashed;
virtual QProcessEnvironment createEnvironment() = 0; }
void setCrashed(bool value)
{
if(m_crashed != value)
{
m_crashed = value;
emit propertiesChanged(this);
}
}
/*! virtual bool canLaunch() const;
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files' virtual bool canEdit() const = 0;
*/ virtual bool canExport() const = 0;
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
/*! bool reloadSettings();
* Returns the root folder to use for looking up log files
*/
virtual QString getLogFileRoot() = 0;
virtual QString getStatusbarDescription() = 0; /**
* 'print' a verbose desription of the instance into a QStringList
*/
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
/// FIXME: this really should be elsewhere... Status currentStatus() const;
virtual QString instanceConfigFolder() const = 0;
/// get variables this instance exports int getConsoleMaxLines() const;
virtual QMap<QString, QString> getVariables() const = 0; bool shouldStopOnConsoleOverflow() const;
virtual QString typeName() const = 0;
bool hasVersionBroken() const
{
return m_hasBrokenVersion;
}
void setVersionBroken(bool value)
{
if(m_hasBrokenVersion != value)
{
m_hasBrokenVersion = value;
emit propertiesChanged(this);
}
}
bool hasUpdateAvailable() const
{
return m_hasUpdate;
}
void setUpdateAvailable(bool value)
{
if(m_hasUpdate != value)
{
m_hasUpdate = value;
emit propertiesChanged(this);
}
}
bool hasCrashed() const
{
return m_crashed;
}
void setCrashed(bool value)
{
if(m_crashed != value)
{
m_crashed = value;
emit propertiesChanged(this);
}
}
bool canLaunch() const;
virtual bool canExport() const = 0;
virtual bool reload();
/**
* 'print' a verbose desription of the instance into a QStringList
*/
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
Status currentStatus() const;
int getConsoleMaxLines() const;
bool shouldStopOnConsoleOverflow() const;
protected: protected:
void changeStatus(Status newStatus); void changeStatus(Status newStatus);
signals: signals:
/*! /*!
* \brief Signal emitted when properties relevant to the instance view change * \brief Signal emitted when properties relevant to the instance view change
*/ */
void propertiesChanged(BaseInstance *inst); 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); void runningStatusChanged(bool running);
void statusChanged(Status from, Status to); void statusChanged(Status from, Status to);
protected slots: protected slots:
void iconUpdated(QString key); void iconUpdated(QString key);
protected: /* data */ protected: /* data */
QString m_rootDir; QString m_rootDir;
QString m_group; SettingsObjectPtr m_settings;
SettingsObjectPtr m_settings; // InstanceFlags m_flags;
// InstanceFlags m_flags; bool m_isRunning = false;
bool m_isRunning = false; shared_qobject_ptr<LaunchTask> m_launchProcess;
std::shared_ptr<LaunchTask> m_launchProcess; QDateTime m_timeStarted;
QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */ private: /* data */
Status m_status = Status::Present; Status m_status = Status::Present;
bool m_crashed = false; bool m_crashed = false;
bool m_hasUpdate = false; bool m_hasUpdate = false;
bool m_hasBrokenVersion = false; bool m_hasBrokenVersion = false;
}; };
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>) Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag) //Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags) //Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View File

@@ -1,57 +0,0 @@
#pragma once
#include <QObject>
#include <QString>
#include "BaseInstance.h"
#include "settings/SettingsObject.h"
#include "multimc_logic_export.h"
using InstanceId = QString;
using InstanceLocator = std::pair<InstancePtr, int>;
enum class InstCreateError
{
NoCreateError = 0,
NoSuchVersion,
UnknownCreateError,
InstExists,
CantCreateDir
};
class MULTIMC_LOGIC_EXPORT BaseInstanceProvider : public QObject
{
Q_OBJECT
public:
BaseInstanceProvider(SettingsObjectPtr settings) : m_globalSettings(settings)
{
// nil
}
public:
virtual QList<InstanceId> discoverInstances() = 0;
virtual InstancePtr loadInstance(const InstanceId &id) = 0;
virtual void loadGroupList() = 0;
virtual void saveGroupList() = 0;
virtual QString getStagedInstancePath()
{
return QString();
}
virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
{
return false;
}
virtual bool destroyStagingPath(const QString & path)
{
return true;
}
signals:
// Emit this when the list of provided instances changed
void instancesChanged();
// Emit when the set of groups your provider supplies changes.
void groupsChanged(QSet<QString> groups);
protected:
SettingsObjectPtr m_globalSettings;
};

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,33 +25,33 @@
class BaseVersion class BaseVersion
{ {
public: public:
virtual ~BaseVersion() {} virtual ~BaseVersion() {}
/*! /*!
* A string used to identify this version in config files. * A string used to identify this version in config files.
* This should be unique within the version list or shenanigans will occur. * This should be unique within the version list or shenanigans will occur.
*/ */
virtual QString descriptor() = 0; virtual QString descriptor() = 0;
/*! /*!
* The name of this version as it is displayed to the user. * The name of this version as it is displayed to the user.
* For example: "1.5.1" * For example: "1.5.1"
*/ */
virtual QString name() = 0; virtual QString name() = 0;
/*! /*!
* This should return a string that describes * This should return a string that describes
* the kind of version this is (Stable, Beta, Snapshot, whatever) * the kind of version this is (Stable, Beta, Snapshot, whatever)
*/ */
virtual QString typeString() const = 0; virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion &a) virtual bool operator<(BaseVersion &a)
{ {
return name() < a.name(); return name() < a.name();
}; };
virtual bool operator>(BaseVersion &a) virtual bool operator>(BaseVersion &a)
{ {
return name() > a.name(); return name() > a.name();
}; };
}; };
typedef std::shared_ptr<BaseVersion> BaseVersionPtr; typedef std::shared_ptr<BaseVersion> BaseVersionPtr;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,83 +22,78 @@ BaseVersionList::BaseVersionList(QObject *parent) : QAbstractListModel(parent)
BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor) BaseVersionPtr BaseVersionList::findVersion(const QString &descriptor)
{ {
for (int i = 0; i < count(); i++) for (int i = 0; i < count(); i++)
{ {
if (at(i)->descriptor() == descriptor) if (at(i)->descriptor() == descriptor)
return at(i); return at(i);
} }
return BaseVersionPtr(); return BaseVersionPtr();
}
BaseVersionPtr BaseVersionList::getLatestStable() const
{
if (count() <= 0)
return BaseVersionPtr();
else
return at(0);
} }
BaseVersionPtr BaseVersionList::getRecommended() const BaseVersionPtr BaseVersionList::getRecommended() const
{ {
return getLatestStable(); if (count() <= 0)
return BaseVersionPtr();
else
return at(0);
} }
QVariant BaseVersionList::data(const QModelIndex &index, int role) const QVariant BaseVersionList::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
if (index.row() > count()) if (index.row() > count())
return QVariant(); return QVariant();
BaseVersionPtr version = at(index.row()); BaseVersionPtr version = at(index.row());
switch (role) switch (role)
{ {
case VersionPointerRole: case VersionPointerRole:
return qVariantFromValue(version); return qVariantFromValue(version);
case VersionRole: case VersionRole:
return version->name(); return version->name();
case VersionIdRole: case VersionIdRole:
return version->descriptor(); return version->descriptor();
case TypeRole: case TypeRole:
return version->typeString(); return version->typeString();
default: default:
return QVariant(); return QVariant();
} }
} }
BaseVersionList::RoleList BaseVersionList::providesRoles() const BaseVersionList::RoleList BaseVersionList::providesRoles() const
{ {
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole}; return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
} }
int BaseVersionList::rowCount(const QModelIndex &parent) const int BaseVersionList::rowCount(const QModelIndex &parent) const
{ {
// Return count // Return count
return count(); return count();
} }
int BaseVersionList::columnCount(const QModelIndex &parent) const int BaseVersionList::columnCount(const QModelIndex &parent) const
{ {
return 1; return 1;
} }
QHash<int, QByteArray> BaseVersionList::roleNames() const QHash<int, QByteArray> BaseVersionList::roleNames() const
{ {
QHash<int, QByteArray> roles = QAbstractListModel::roleNames(); QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(VersionRole, "version"); roles.insert(VersionRole, "version");
roles.insert(VersionIdRole, "versionId"); roles.insert(VersionIdRole, "versionId");
roles.insert(ParentGameVersionRole, "parentGameVersion"); roles.insert(ParentVersionRole, "parentGameVersion");
roles.insert(RecommendedRole, "recommended"); roles.insert(RecommendedRole, "recommended");
roles.insert(LatestRole, "latest"); roles.insert(LatestRole, "latest");
roles.insert(TypeRole, "type"); roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch"); roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path"); roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture"); roles.insert(ArchitectureRole, "architecture");
return roles; return roles;
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "tasks/Task.h" #include "tasks/Task.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "QObjectPtr.h"
/*! /*!
* \brief Class that each instance type's version list derives from. * \brief Class that each instance type's version list derives from.
@@ -37,90 +38,85 @@
*/ */
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
enum ModelRoles enum ModelRoles
{ {
VersionPointerRole = Qt::UserRole, VersionPointerRole = Qt::UserRole,
VersionRole, VersionRole,
VersionIdRole, VersionIdRole,
ParentGameVersionRole, ParentVersionRole,
RecommendedRole, RecommendedRole,
LatestRole, LatestRole,
TypeRole, TypeRole,
BranchRole, BranchRole,
PathRole, PathRole,
ArchitectureRole, ArchitectureRole,
SortRole SortRole
}; };
typedef QList<int> RoleList; typedef QList<int> RoleList;
explicit BaseVersionList(QObject *parent = 0); explicit BaseVersionList(QObject *parent = 0);
/*! /*!
* \brief Gets a task that will reload the version list. * \brief Gets a task that will reload the version list.
* Simply execute the task to load the list. * Simply execute the task to load the list.
* The task returned by this function should reset the model when it's done. * The task returned by this function should reset the model when it's done.
* \return A pointer to a task that reloads the version list. * \return A pointer to a task that reloads the version list.
*/ */
virtual Task *getLoadTask() = 0; virtual shared_qobject_ptr<Task> getLoadTask() = 0;
//! Checks whether or not the list is loaded. If this returns false, the list should be //! Checks whether or not the list is loaded. If this returns false, the list should be
//loaded. //loaded.
virtual bool isLoaded() = 0; virtual bool isLoaded() = 0;
//! Gets the version at the given index. //! Gets the version at the given index.
virtual const BaseVersionPtr at(int i) const = 0; virtual const BaseVersionPtr at(int i) const = 0;
//! Returns the number of versions in the list. //! Returns the number of versions in the list.
virtual int count() const = 0; virtual int count() const = 0;
//////// List Model Functions //////// //////// List Model Functions ////////
virtual QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const override;
virtual int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent) const override;
virtual int columnCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const override;
virtual QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list? //! which roles are provided by this version list?
virtual RoleList providesRoles() const; virtual RoleList providesRoles() const;
/*! /*!
* \brief Finds a version by its descriptor. * \brief Finds a version by its descriptor.
* \param The descriptor of the version to find. * \param descriptor The descriptor of the version to find.
* \return A const pointer to the version with the given descriptor. NULL if * \return A const pointer to the version with the given descriptor. NULL if
* one doesn't exist. * one doesn't exist.
*/ */
virtual BaseVersionPtr findVersion(const QString &descriptor); virtual BaseVersionPtr findVersion(const QString &descriptor);
/*! /*!
* \brief Gets the latest stable version from this list * \brief Gets the recommended version from this list
*/ * If the list doesn't support recommended versions, this works exactly as getLatestStable
virtual BaseVersionPtr getLatestStable() const; */
virtual BaseVersionPtr getRecommended() const;
/*! /*!
* \brief Gets the recommended version from this list * Sorts the version list.
* If the list doesn't support recommended versions, this works exactly as getLatestStable */
*/ virtual void sortVersions() = 0;
virtual BaseVersionPtr getRecommended() const;
/*!
* Sorts the version list.
*/
virtual void sortVersions() = 0;
protected protected
slots: slots:
/*! /*!
* Updates this list with the given list of versions. * Updates this list with the given list of versions.
* This is done by copying each version in the given list and inserting it * This is done by copying each version in the given list and inserting it
* into this one. * into this one.
* We need to do this so that we can set the parents of the versions are set to this * We need to do this so that we can set the parents of the versions are set to this
* version list. This can't be done in the load task, because the versions the load * version list. This can't be done in the load task, because the versions the load
* task creates are on the load task's thread and Qt won't allow their parents * task creates are on the load task's thread and Qt won't allow their parents
* to be set to something created on another thread. * to be set to something created on another thread.
* To get around that problem, we invoke this method on the GUI thread, which * To get around that problem, we invoke this method on the GUI thread, which
* then copies the versions and sets their parents correctly. * then copies the versions and sets their parents correctly.
* \param versions List of versions whose parents should be set. * \param versions List of versions whose parents should be set.
*/ */
virtual void updateListData(QList<BaseVersionPtr> versions) = 0; virtual void updateListData(QList<BaseVersionPtr> versions) = 0;
}; };

View File

@@ -3,461 +3,454 @@ project(MultiMC_logic)
include (UnitTest) include (UnitTest)
set(CORE_SOURCES set(CORE_SOURCES
# LOGIC - Base classes and infrastructure # LOGIC - Base classes and infrastructure
BaseInstaller.h BaseInstaller.h
BaseInstaller.cpp BaseInstaller.cpp
BaseVersionList.h BaseVersionList.h
BaseVersionList.cpp BaseVersionList.cpp
InstanceCreationTask.h InstanceList.h
InstanceCreationTask.cpp InstanceList.cpp
InstanceCopyTask.h InstanceTask.h
InstanceCopyTask.cpp InstanceTask.cpp
InstanceImportTask.h LoggedProcess.h
InstanceImportTask.cpp LoggedProcess.cpp
InstanceList.h MessageLevel.cpp
InstanceList.cpp MessageLevel.h
LoggedProcess.h BaseVersion.h
LoggedProcess.cpp BaseInstance.h
MessageLevel.cpp BaseInstance.cpp
MessageLevel.h NullInstance.h
BaseInstanceProvider.h MMCZip.h
FolderInstanceProvider.h MMCZip.cpp
FolderInstanceProvider.cpp MMCStrings.h
BaseVersion.h MMCStrings.cpp
BaseInstance.h
BaseInstance.cpp
NullInstance.h
MMCZip.h
MMCZip.cpp
MMCStrings.h
MMCStrings.cpp
# Use tracking separate from memory management # Basic instance manipulation tasks (derived from InstanceTask)
Usable.h InstanceCreationTask.h
InstanceCreationTask.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
InstanceImportTask.cpp
# Prefix tree where node names are strings between separators # Use tracking separate from memory management
SeparatorPrefixTree.h Usable.h
# WARNING: globals live here # Prefix tree where node names are strings between separators
Env.h SeparatorPrefixTree.h
Env.cpp
# JSON parsing helpers # WARNING: globals live here
Json.h Env.h
Json.cpp Env.cpp
FileSystem.h # String filters
FileSystem.cpp Filter.h
Filter.cpp
Exception.h # JSON parsing helpers
Json.h
Json.cpp
# RW lock protected map FileSystem.h
RWStorage.h FileSystem.cpp
# A variable that has an implicit default value and keeps track of changes Exception.h
DefaultVariable.h
# a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms # RW lock protected map
QObjectPtr.h RWStorage.h
# Compression support # A variable that has an implicit default value and keeps track of changes
GZip.h DefaultVariable.h
GZip.cpp
# Command line parameter parsing # a smart pointer wrapper intended for safer use with Qt signal/slot mechanisms
Commandline.h QObjectPtr.h
Commandline.cpp
# Version number string support # Compression support
Version.h GZip.h
Version.cpp GZip.cpp
# A Recursive file system watcher # Command line parameter parsing
RecursiveFileSystemWatcher.h Commandline.h
RecursiveFileSystemWatcher.cpp Commandline.cpp
# Version number string support
Version.h
Version.cpp
# A Recursive file system watcher
RecursiveFileSystemWatcher.h
RecursiveFileSystemWatcher.cpp
) )
add_unit_test(FileSystem add_unit_test(FileSystem
SOURCES FileSystem_test.cpp SOURCES FileSystem_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
DATA testdata DATA testdata
) )
add_unit_test(GZip add_unit_test(GZip
SOURCES GZip_test.cpp SOURCES GZip_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
set(PATHMATCHER_SOURCES set(PATHMATCHER_SOURCES
# Path matchers # Path matchers
pathmatcher/FSTreeMatcher.h pathmatcher/FSTreeMatcher.h
pathmatcher/IPathMatcher.h pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h pathmatcher/RegexpMatcher.h
) )
set(NET_SOURCES set(NET_SOURCES
# network stuffs # network stuffs
net/ByteArraySink.h net/ByteArraySink.h
net/ChecksumValidator.h net/ChecksumValidator.h
net/Download.cpp net/Download.cpp
net/Download.h net/Download.h
net/FileSink.cpp net/FileSink.cpp
net/FileSink.h net/FileSink.h
net/HttpMetaCache.cpp net/HttpMetaCache.cpp
net/HttpMetaCache.h net/HttpMetaCache.h
net/MetaCacheSink.cpp net/MetaCacheSink.cpp
net/MetaCacheSink.h net/MetaCacheSink.h
net/NetAction.h net/NetAction.h
net/NetJob.cpp net/NetJob.cpp
net/NetJob.h net/NetJob.h
net/PasteUpload.cpp net/PasteUpload.cpp
net/PasteUpload.h net/PasteUpload.h
net/Sink.h net/Sink.h
net/URLConstants.cpp net/URLConstants.cpp
net/URLConstants.h net/URLConstants.h
net/Validator.h net/Validator.h
) )
# Game launch logic # Game launch logic
set(LAUNCH_SOURCES set(LAUNCH_SOURCES
launch/steps/PostLaunchCommand.cpp launch/steps/PostLaunchCommand.cpp
launch/steps/PostLaunchCommand.h launch/steps/PostLaunchCommand.h
launch/steps/PreLaunchCommand.cpp launch/steps/PreLaunchCommand.cpp
launch/steps/PreLaunchCommand.h launch/steps/PreLaunchCommand.h
launch/steps/TextPrint.cpp launch/steps/TextPrint.cpp
launch/steps/TextPrint.h launch/steps/TextPrint.h
launch/steps/Update.cpp launch/steps/Update.cpp
launch/steps/Update.h launch/steps/Update.h
launch/LaunchStep.cpp launch/LaunchStep.cpp
launch/LaunchStep.h launch/LaunchStep.h
launch/LaunchTask.cpp launch/LaunchTask.cpp
launch/LaunchTask.h launch/LaunchTask.h
launch/LogModel.cpp launch/LogModel.cpp
launch/LogModel.h launch/LogModel.h
) )
# Old update system # Old update system
set(UPDATE_SOURCES set(UPDATE_SOURCES
updater/GoUpdate.h updater/GoUpdate.h
updater/GoUpdate.cpp updater/GoUpdate.cpp
updater/UpdateChecker.h updater/UpdateChecker.h
updater/UpdateChecker.cpp updater/UpdateChecker.cpp
updater/DownloadTask.h updater/DownloadTask.h
updater/DownloadTask.cpp updater/DownloadTask.cpp
) )
add_unit_test(UpdateChecker add_unit_test(UpdateChecker
SOURCES updater/UpdateChecker_test.cpp SOURCES updater/UpdateChecker_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
DATA updater/testdata DATA updater/testdata
) )
add_unit_test(DownloadTask add_unit_test(DownloadTask
SOURCES updater/DownloadTask_test.cpp SOURCES updater/DownloadTask_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
DATA updater/testdata DATA updater/testdata
) )
# Rarely used notifications # Rarely used notifications
set(NOTIFICATIONS_SOURCES set(NOTIFICATIONS_SOURCES
# Notifications - short warning messages # Notifications - short warning messages
notifications/NotificationChecker.h notifications/NotificationChecker.h
notifications/NotificationChecker.cpp notifications/NotificationChecker.cpp
) )
# Backend for the news bar... there's usually no news. # Backend for the news bar... there's usually no news.
set(NEWS_SOURCES set(NEWS_SOURCES
# News System # News System
news/NewsChecker.h news/NewsChecker.h
news/NewsChecker.cpp news/NewsChecker.cpp
news/NewsEntry.h news/NewsEntry.h
news/NewsEntry.cpp news/NewsEntry.cpp
) )
# Icon interface # Icon interface
set(ICONS_SOURCES set(ICONS_SOURCES
# News System # Icons System and related code
icons/IIconList.h icons/IIconList.h
icons/IIconList.cpp icons/IIconList.cpp
icons/IconUtils.h
icons/IconUtils.cpp
) )
# Minecraft services status checker # Minecraft services status checker
set(STATUS_SOURCES set(STATUS_SOURCES
# Status system # Status system
status/StatusChecker.h status/StatusChecker.h
status/StatusChecker.cpp status/StatusChecker.cpp
) )
# Support for Minecraft instances and launch # Support for Minecraft instances and launch
set(MINECRAFT_SOURCES set(MINECRAFT_SOURCES
# Minecraft support # Minecraft support
minecraft/auth/AuthSession.h minecraft/auth/AuthSession.h
minecraft/auth/AuthSession.cpp minecraft/auth/AuthSession.cpp
minecraft/auth/MojangAccountList.h minecraft/auth/MojangAccountList.h
minecraft/auth/MojangAccountList.cpp minecraft/auth/MojangAccountList.cpp
minecraft/auth/MojangAccount.h minecraft/auth/MojangAccount.h
minecraft/auth/MojangAccount.cpp minecraft/auth/MojangAccount.cpp
minecraft/auth/YggdrasilTask.h minecraft/auth/YggdrasilTask.h
minecraft/auth/YggdrasilTask.cpp minecraft/auth/YggdrasilTask.cpp
minecraft/auth/flows/AuthenticateTask.h minecraft/auth/flows/AuthenticateTask.h
minecraft/auth/flows/AuthenticateTask.cpp minecraft/auth/flows/AuthenticateTask.cpp
minecraft/auth/flows/RefreshTask.cpp minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/RefreshTask.cpp minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp minecraft/auth/flows/ValidateTask.cpp
minecraft/onesix/OneSixUpdate.h minecraft/gameoptions/GameOptions.h
minecraft/onesix/OneSixUpdate.cpp minecraft/gameoptions/GameOptions.cpp
minecraft/onesix/OneSixInstance.h minecraft/update/AssetUpdateTask.h
minecraft/onesix/OneSixInstance.cpp minecraft/update/AssetUpdateTask.cpp
minecraft/onesix/OneSixProfileStrategy.cpp minecraft/update/FMLLibrariesTask.cpp
minecraft/onesix/OneSixProfileStrategy.h minecraft/update/FMLLibrariesTask.h
minecraft/onesix/OneSixVersionFormat.cpp minecraft/update/FoldersTask.cpp
minecraft/onesix/OneSixVersionFormat.h minecraft/update/FoldersTask.h
minecraft/onesix/update/AssetUpdateTask.h minecraft/update/LibrariesTask.cpp
minecraft/onesix/update/AssetUpdateTask.cpp minecraft/update/LibrariesTask.h
minecraft/onesix/update/FMLLibrariesTask.cpp minecraft/launch/ClaimAccount.cpp
minecraft/onesix/update/FMLLibrariesTask.h minecraft/launch/ClaimAccount.h
minecraft/onesix/update/FoldersTask.cpp minecraft/launch/CreateServerResourcePacksFolder.cpp
minecraft/onesix/update/FoldersTask.h minecraft/launch/CreateServerResourcePacksFolder.h
minecraft/onesix/update/LibrariesTask.cpp minecraft/launch/ModMinecraftJar.cpp
minecraft/onesix/update/LibrariesTask.h minecraft/launch/ModMinecraftJar.h
minecraft/launch/ClaimAccount.cpp minecraft/launch/DirectJavaLaunch.cpp
minecraft/launch/ClaimAccount.h minecraft/launch/DirectJavaLaunch.h
minecraft/launch/CreateServerResourcePacksFolder.cpp minecraft/launch/ExtractNatives.cpp
minecraft/launch/CreateServerResourcePacksFolder.h minecraft/launch/ExtractNatives.h
minecraft/launch/ModMinecraftJar.cpp minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/ModMinecraftJar.h minecraft/launch/LauncherPartLaunch.h
minecraft/launch/DirectJavaLaunch.cpp minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/DirectJavaLaunch.h minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ExtractNatives.cpp minecraft/launch/ReconstructAssets.cpp
minecraft/launch/ExtractNatives.h minecraft/launch/ReconstructAssets.h
minecraft/launch/LauncherPartLaunch.cpp minecraft/legacy/LegacyModList.h
minecraft/launch/LauncherPartLaunch.h minecraft/legacy/LegacyModList.cpp
minecraft/launch/PrintInstanceInfo.cpp minecraft/legacy/LegacyInstance.h
minecraft/launch/PrintInstanceInfo.h minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyModList.h minecraft/legacy/LegacyUpgradeTask.h
minecraft/legacy/LegacyModList.cpp minecraft/legacy/LegacyUpgradeTask.cpp
minecraft/legacy/LegacyUpdate.h minecraft/GradleSpecifier.h
minecraft/legacy/LegacyUpdate.cpp minecraft/MinecraftInstance.cpp
minecraft/legacy/LegacyInstance.h minecraft/MinecraftInstance.h
minecraft/legacy/LegacyInstance.cpp minecraft/LaunchProfile.cpp
minecraft/legacy/LwjglVersionList.h minecraft/LaunchProfile.h
minecraft/legacy/LwjglVersionList.cpp minecraft/Component.cpp
minecraft/GradleSpecifier.h minecraft/Component.h
minecraft/MinecraftProfile.cpp minecraft/ComponentList.cpp
minecraft/MinecraftProfile.h minecraft/ComponentList.h
minecraft/MojangVersionFormat.cpp minecraft/ComponentUpdateTask.cpp
minecraft/MojangVersionFormat.h minecraft/ComponentUpdateTask.h
minecraft/JarMod.h minecraft/MinecraftLoadAndCheck.h
minecraft/MinecraftInstance.cpp minecraft/MinecraftLoadAndCheck.cpp
minecraft/MinecraftInstance.h minecraft/MinecraftUpdate.h
minecraft/MinecraftVersion.cpp minecraft/MinecraftUpdate.cpp
minecraft/MinecraftVersion.h minecraft/MojangVersionFormat.cpp
minecraft/MinecraftVersionList.cpp minecraft/MojangVersionFormat.h
minecraft/MinecraftVersionList.h minecraft/Rule.cpp
minecraft/Rule.cpp minecraft/Rule.h
minecraft/Rule.h minecraft/OneSixVersionFormat.cpp
minecraft/OpSys.cpp minecraft/OneSixVersionFormat.h
minecraft/OpSys.h minecraft/OpSys.cpp
minecraft/ParseUtils.cpp minecraft/OpSys.h
minecraft/ParseUtils.h minecraft/ParseUtils.cpp
minecraft/ProfileUtils.cpp minecraft/ParseUtils.h
minecraft/ProfileUtils.h minecraft/ProfileUtils.cpp
minecraft/ProfileStrategy.h minecraft/ProfileUtils.h
minecraft/Library.cpp minecraft/Library.cpp
minecraft/Library.h minecraft/Library.h
minecraft/MojangDownloadInfo.h minecraft/MojangDownloadInfo.h
minecraft/VersionBuildError.h minecraft/VersionFile.cpp
minecraft/VersionFile.cpp minecraft/VersionFile.h
minecraft/VersionFile.h minecraft/VersionFilterData.h
minecraft/ProfilePatch.h minecraft/VersionFilterData.cpp
minecraft/VersionFilterData.h minecraft/Mod.h
minecraft/VersionFilterData.cpp minecraft/Mod.cpp
minecraft/Mod.h minecraft/SimpleModList.h
minecraft/Mod.cpp minecraft/SimpleModList.cpp
minecraft/ModList.h minecraft/World.h
minecraft/ModList.cpp minecraft/World.cpp
minecraft/World.h minecraft/WorldList.h
minecraft/World.cpp minecraft/WorldList.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
# FTB # Assets
minecraft/ftb/OneSixFTBInstance.h minecraft/AssetsUtils.h
minecraft/ftb/OneSixFTBInstance.cpp minecraft/AssetsUtils.cpp
minecraft/ftb/LegacyFTBInstance.h
minecraft/ftb/LegacyFTBInstance.cpp
minecraft/ftb/FTBProfileStrategy.h
minecraft/ftb/FTBProfileStrategy.cpp
minecraft/ftb/FTBInstanceProvider.cpp
minecraft/ftb/FTBInstanceProvider.h
minecraft/ftb/FTBPlugin.h
minecraft/ftb/FTBPlugin.cpp
# Assets # Forge and all things forge related
minecraft/AssetsUtils.h minecraft/forge/ForgeXzDownload.h
minecraft/AssetsUtils.cpp minecraft/forge/ForgeXzDownload.cpp
# Forge and all things forge related # Skin upload utilities
minecraft/forge/ForgeVersion.h minecraft/SkinUpload.cpp
minecraft/forge/ForgeVersion.cpp minecraft/SkinUpload.h
minecraft/forge/ForgeVersionList.h )
minecraft/forge/ForgeVersionList.cpp
minecraft/forge/ForgeXzDownload.h
minecraft/forge/ForgeXzDownload.cpp
minecraft/forge/LegacyForge.h
minecraft/forge/LegacyForge.cpp
minecraft/forge/ForgeInstaller.h
minecraft/forge/ForgeInstaller.cpp
# Liteloader and related things
minecraft/liteloader/LiteLoaderInstaller.h
minecraft/liteloader/LiteLoaderInstaller.cpp
minecraft/liteloader/LiteLoaderVersionList.h
minecraft/liteloader/LiteLoaderVersionList.cpp
minecraft/SkinUpload.cpp
minecraft/SkinUpload.h
)
add_unit_test(GradleSpecifier add_unit_test(GradleSpecifier
SOURCES minecraft/GradleSpecifier_test.cpp SOURCES minecraft/GradleSpecifier_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
add_unit_test(MojangVersionFormat add_unit_test(MojangVersionFormat
SOURCES minecraft/MojangVersionFormat_test.cpp SOURCES minecraft/MojangVersionFormat_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
DATA minecraft/testdata DATA minecraft/testdata
) )
add_unit_test(Library add_unit_test(Library
SOURCES minecraft/Library_test.cpp SOURCES minecraft/Library_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
# FIXME: shares data with FileSystem test # FIXME: shares data with FileSystem test
add_unit_test(ModList add_unit_test(SimpleModList
SOURCES minecraft/ModList_test.cpp SOURCES minecraft/SimpleModList_test.cpp
DATA testdata DATA testdata
LIBS MultiMC_logic LIBS MultiMC_logic
) )
add_unit_test(ParseUtils add_unit_test(ParseUtils
SOURCES minecraft/ParseUtils_test.cpp SOURCES minecraft/ParseUtils_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
# the screenshots feature # the screenshots feature
set(SCREENSHOTS_SOURCES set(SCREENSHOTS_SOURCES
screenshots/Screenshot.h screenshots/Screenshot.h
screenshots/ImgurUpload.h screenshots/ImgurUpload.h
screenshots/ImgurUpload.cpp screenshots/ImgurUpload.cpp
screenshots/ImgurAlbumCreation.h screenshots/ImgurAlbumCreation.h
screenshots/ImgurAlbumCreation.cpp screenshots/ImgurAlbumCreation.cpp
) )
set(TASKS_SOURCES set(TASKS_SOURCES
# Tasks # Tasks
tasks/Task.h tasks/Task.h
tasks/Task.cpp tasks/Task.cpp
tasks/ThreadTask.h tasks/SequentialTask.h
tasks/ThreadTask.cpp tasks/SequentialTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
) )
set(SETTINGS_SOURCES set(SETTINGS_SOURCES
# Settings # Settings
settings/INIFile.cpp settings/INIFile.cpp
settings/INIFile.h settings/INIFile.h
settings/INISettingsObject.cpp settings/INISettingsObject.cpp
settings/INISettingsObject.h settings/INISettingsObject.h
settings/OverrideSetting.cpp settings/OverrideSetting.cpp
settings/OverrideSetting.h settings/OverrideSetting.h
settings/PassthroughSetting.cpp settings/PassthroughSetting.cpp
settings/PassthroughSetting.h settings/PassthroughSetting.h
settings/Setting.cpp settings/Setting.cpp
settings/Setting.h settings/Setting.h
settings/SettingsObject.cpp settings/SettingsObject.cpp
settings/SettingsObject.h settings/SettingsObject.h
) )
add_unit_test(INIFile add_unit_test(INIFile
SOURCES settings/INIFile_test.cpp SOURCES settings/INIFile_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
set(JAVA_SOURCES set(JAVA_SOURCES
# Java related code # Java related code
java/launch/CheckJava.cpp java/launch/CheckJava.cpp
java/launch/CheckJava.h java/launch/CheckJava.h
java/JavaChecker.h java/JavaChecker.h
java/JavaChecker.cpp java/JavaChecker.cpp
java/JavaCheckerJob.h java/JavaCheckerJob.h
java/JavaCheckerJob.cpp java/JavaCheckerJob.cpp
java/JavaInstall.h java/JavaInstall.h
java/JavaInstall.cpp java/JavaInstall.cpp
java/JavaInstallList.h java/JavaInstallList.h
java/JavaInstallList.cpp java/JavaInstallList.cpp
java/JavaUtils.h java/JavaUtils.h
java/JavaUtils.cpp java/JavaUtils.cpp
java/JavaVersion.h java/JavaVersion.h
java/JavaVersion.cpp java/JavaVersion.cpp
) )
add_unit_test(JavaVersion add_unit_test(JavaVersion
SOURCES java/JavaVersion_test.cpp SOURCES java/JavaVersion_test.cpp
LIBS MultiMC_logic LIBS MultiMC_logic
) )
set(TRANSLATIONS_SOURCES set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h translations/TranslationsModel.h
translations/TranslationsModel.cpp translations/TranslationsModel.cpp
translations/POTranslator.h
translations/POTranslator.cpp
) )
set(TOOLS_SOURCES set(TOOLS_SOURCES
# Tools # Tools
tools/BaseExternalTool.cpp tools/BaseExternalTool.cpp
tools/BaseExternalTool.h tools/BaseExternalTool.h
tools/BaseProfiler.cpp tools/BaseProfiler.cpp
tools/BaseProfiler.h tools/BaseProfiler.h
tools/JProfiler.cpp tools/JProfiler.cpp
tools/JProfiler.h tools/JProfiler.h
tools/JVisualVM.cpp tools/JVisualVM.cpp
tools/JVisualVM.h tools/JVisualVM.h
tools/MCEditTool.cpp tools/MCEditTool.cpp
tools/MCEditTool.h tools/MCEditTool.h
) )
set(WONKO_SOURCES set(META_SOURCES
# Wonko # Metadata sources
wonko/tasks/BaseWonkoEntityRemoteLoadTask.cpp meta/JsonFormat.cpp
wonko/tasks/BaseWonkoEntityRemoteLoadTask.h meta/JsonFormat.h
wonko/tasks/BaseWonkoEntityLocalLoadTask.cpp meta/BaseEntity.cpp
wonko/tasks/BaseWonkoEntityLocalLoadTask.h meta/BaseEntity.h
wonko/format/WonkoFormatV1.cpp meta/VersionList.cpp
wonko/format/WonkoFormatV1.h meta/VersionList.h
wonko/format/WonkoFormat.cpp meta/Version.cpp
wonko/format/WonkoFormat.h meta/Version.h
wonko/BaseWonkoEntity.cpp meta/Index.cpp
wonko/BaseWonkoEntity.h meta/Index.h
wonko/WonkoVersionList.cpp
wonko/WonkoVersionList.h
wonko/WonkoVersion.cpp
wonko/WonkoVersion.h
wonko/WonkoIndex.cpp
wonko/WonkoIndex.h
wonko/WonkoUtil.cpp
wonko/WonkoUtil.h
wonko/WonkoReference.cpp
wonko/WonkoReference.h
) )
add_unit_test(WonkoIndex set(FTB_SOURCES
SOURCES wonko/WonkoIndex_test.cpp modplatform/ftb/FtbPackFetchTask.h
LIBS MultiMC_logic 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
)
################################ COMPILE ################################ ################################ COMPILE ################################
@@ -465,34 +458,44 @@ add_unit_test(WonkoIndex
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
set(LOGIC_SOURCES set(LOGIC_SOURCES
${CORE_SOURCES} ${CORE_SOURCES}
${PATHMATCHER_SOURCES} ${PATHMATCHER_SOURCES}
${NET_SOURCES} ${NET_SOURCES}
${LAUNCH_SOURCES} ${LAUNCH_SOURCES}
${UPDATE_SOURCES} ${UPDATE_SOURCES}
${NOTIFICATIONS_SOURCES} ${NOTIFICATIONS_SOURCES}
${NEWS_SOURCES} ${NEWS_SOURCES}
${STATUS_SOURCES} ${STATUS_SOURCES}
${MINECRAFT_SOURCES} ${MINECRAFT_SOURCES}
${SCREENSHOTS_SOURCES} ${SCREENSHOTS_SOURCES}
${TASKS_SOURCES} ${TASKS_SOURCES}
${SETTINGS_SOURCES} ${SETTINGS_SOURCES}
${JAVA_SOURCES} ${JAVA_SOURCES}
${TRANSLATIONS_SOURCES} ${TRANSLATIONS_SOURCES}
${TOOLS_SOURCES} ${TOOLS_SOURCES}
${WONKO_SOURCES} ${META_SOURCES}
${ICONS_SOURCES} ${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
) )
message(STATUS "FOO! ${LOGIC_SOURCES}")
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES}) add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1) set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_logic) generate_export_header(MultiMC_logic)
# Link # Link
target_link_libraries(MultiMC_logic xz-embedded unpack200 systeminfo ${QUAZIP_LIBRARIES} ${NBT_NAME} ${ZLIB_LIBRARIES}) target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent) target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
add_dependencies(MultiMC_logic QuaZIP)
# Mark and export headers # Mark and export headers
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}") target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
# Install it
install(
TARGETS MultiMC_logic
RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -27,453 +27,453 @@ namespace Commandline
// commandline splitter // commandline splitter
QStringList splitArgs(QString args) QStringList splitArgs(QString args)
{ {
QStringList argv; QStringList argv;
QString current; QString current;
bool escape = false; bool escape = false;
QChar inquotes; QChar inquotes;
for (int i = 0; i < args.length(); i++) for (int i = 0; i < args.length(); i++)
{ {
QChar cchar = args.at(i); QChar cchar = args.at(i);
// \ escaped // \ escaped
if (escape) if (escape)
{ {
current += cchar; current += cchar;
escape = false; escape = false;
// in "quotes" // in "quotes"
} }
else if (!inquotes.isNull()) else if (!inquotes.isNull())
{ {
if (cchar == 0x5C) if (cchar == '\\')
escape = true; escape = true;
else if (cchar == inquotes) else if (cchar == inquotes)
inquotes = 0; inquotes = 0;
else else
current += cchar; current += cchar;
// otherwise // otherwise
} }
else else
{ {
if (cchar == 0x20) if (cchar == ' ')
{ {
if (!current.isEmpty()) if (!current.isEmpty())
{ {
argv << current; argv << current;
current.clear(); current.clear();
} }
} }
else if (cchar == 0x22 || cchar == 0x27) else if (cchar == '"' || cchar == '\'')
inquotes = cchar; inquotes = cchar;
else else
current += cchar; current += cchar;
} }
} }
if (!current.isEmpty()) if (!current.isEmpty())
argv << current; argv << current;
return argv; return argv;
} }
Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle)
{ {
m_flagStyle = flagStyle; m_flagStyle = flagStyle;
m_argStyle = argStyle; m_argStyle = argStyle;
} }
// styles setter/getter // styles setter/getter
void Parser::setArgumentStyle(ArgumentStyle::Enum style) void Parser::setArgumentStyle(ArgumentStyle::Enum style)
{ {
m_argStyle = style; m_argStyle = style;
} }
ArgumentStyle::Enum Parser::argumentStyle() ArgumentStyle::Enum Parser::argumentStyle()
{ {
return m_argStyle; return m_argStyle;
} }
void Parser::setFlagStyle(FlagStyle::Enum style) void Parser::setFlagStyle(FlagStyle::Enum style)
{ {
m_flagStyle = style; m_flagStyle = style;
} }
FlagStyle::Enum Parser::flagStyle() FlagStyle::Enum Parser::flagStyle()
{ {
return m_flagStyle; return m_flagStyle;
} }
// setup methods // setup methods
void Parser::addSwitch(QString name, bool def) void Parser::addSwitch(QString name, bool def)
{ {
if (m_params.contains(name)) if (m_params.contains(name))
throw "Name not unique"; throw "Name not unique";
OptionDef *param = new OptionDef; OptionDef *param = new OptionDef;
param->type = otSwitch; param->type = otSwitch;
param->name = name; param->name = name;
param->metavar = QString("<%1>").arg(name); param->metavar = QString("<%1>").arg(name);
param->def = def; param->def = def;
m_options[name] = param; m_options[name] = param;
m_params[name] = (CommonDef *)param; m_params[name] = (CommonDef *)param;
m_optionList.append(param); m_optionList.append(param);
} }
void Parser::addOption(QString name, QVariant def) void Parser::addOption(QString name, QVariant def)
{ {
if (m_params.contains(name)) if (m_params.contains(name))
throw "Name not unique"; throw "Name not unique";
OptionDef *param = new OptionDef; OptionDef *param = new OptionDef;
param->type = otOption; param->type = otOption;
param->name = name; param->name = name;
param->metavar = QString("<%1>").arg(name); param->metavar = QString("<%1>").arg(name);
param->def = def; param->def = def;
m_options[name] = param; m_options[name] = param;
m_params[name] = (CommonDef *)param; m_params[name] = (CommonDef *)param;
m_optionList.append(param); m_optionList.append(param);
} }
void Parser::addArgument(QString name, bool required, QVariant def) void Parser::addArgument(QString name, bool required, QVariant def)
{ {
if (m_params.contains(name)) if (m_params.contains(name))
throw "Name not unique"; throw "Name not unique";
PositionalDef *param = new PositionalDef; PositionalDef *param = new PositionalDef;
param->name = name; param->name = name;
param->def = def; param->def = def;
param->required = required; param->required = required;
param->metavar = name; param->metavar = name;
m_positionals.append(param); m_positionals.append(param);
m_params[name] = (CommonDef *)param; m_params[name] = (CommonDef *)param;
} }
void Parser::addDocumentation(QString name, QString doc, QString metavar) void Parser::addDocumentation(QString name, QString doc, QString metavar)
{ {
if (!m_params.contains(name)) if (!m_params.contains(name))
throw "Name does not exist"; throw "Name does not exist";
CommonDef *param = m_params[name]; CommonDef *param = m_params[name];
param->doc = doc; param->doc = doc;
if (!metavar.isNull()) if (!metavar.isNull())
param->metavar = metavar; param->metavar = metavar;
} }
void Parser::addShortOpt(QString name, QChar flag) void Parser::addShortOpt(QString name, QChar flag)
{ {
if (!m_params.contains(name)) if (!m_params.contains(name))
throw "Name does not exist"; throw "Name does not exist";
if (!m_options.contains(name)) if (!m_options.contains(name))
throw "Name is not an Option or Swtich"; throw "Name is not an Option or Swtich";
OptionDef *param = m_options[name]; OptionDef *param = m_options[name];
m_flags[flag] = param; m_flags[flag] = param;
param->flag = flag; param->flag = flag;
} }
// help methods // help methods
QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags)
{ {
QStringList help; QStringList help;
help << compileUsage(progName, useFlags) << "\r\n"; help << compileUsage(progName, useFlags) << "\r\n";
// positionals // positionals
if (!m_positionals.isEmpty()) if (!m_positionals.isEmpty())
{ {
help << "\r\n"; help << "\r\n";
help << "Positional arguments:\r\n"; help << "Positional arguments:\r\n";
QListIterator<PositionalDef *> it2(m_positionals); QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext()) while (it2.hasNext())
{ {
PositionalDef *param = it2.next(); PositionalDef *param = it2.next();
help << " " << param->metavar; help << " " << param->metavar;
help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); help << " " << QString(helpIndent - param->metavar.length() - 1, ' ');
help << param->doc << "\r\n"; help << param->doc << "\r\n";
} }
} }
// Options // Options
if (!m_optionList.isEmpty()) if (!m_optionList.isEmpty())
{ {
help << "\r\n"; help << "\r\n";
QString optPrefix, flagPrefix; QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix); getPrefix(optPrefix, flagPrefix);
help << "Options & Switches:\r\n"; help << "Options & Switches:\r\n";
QListIterator<OptionDef *> it(m_optionList); QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext()) while (it.hasNext())
{ {
OptionDef *option = it.next(); OptionDef *option = it.next();
help << " "; help << " ";
int nameLength = optPrefix.length() + option->name.length(); int nameLength = optPrefix.length() + option->name.length();
if (!option->flag.isNull()) if (!option->flag.isNull())
{ {
nameLength += 3 + flagPrefix.length(); nameLength += 3 + flagPrefix.length();
help << flagPrefix << option->flag << ", "; help << flagPrefix << option->flag << ", ";
} }
help << optPrefix << option->name; help << optPrefix << option->name;
if (option->type == otOption) if (option->type == otOption)
{ {
QString arg = QString("%1%2").arg( QString arg = QString("%1%2").arg(
((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar);
nameLength += arg.length(); nameLength += arg.length();
help << arg; help << arg;
} }
help << " " << QString(helpIndent - nameLength - 1, ' '); help << " " << QString(helpIndent - nameLength - 1, ' ');
help << option->doc << "\r\n"; help << option->doc << "\r\n";
} }
} }
return help.join(""); return help.join("");
} }
QString Parser::compileUsage(QString progName, bool useFlags) QString Parser::compileUsage(QString progName, bool useFlags)
{ {
QStringList usage; QStringList usage;
usage << "Usage: " << progName; usage << "Usage: " << progName;
QString optPrefix, flagPrefix; QString optPrefix, flagPrefix;
getPrefix(optPrefix, flagPrefix); getPrefix(optPrefix, flagPrefix);
// options // options
QListIterator<OptionDef *> it(m_optionList); QListIterator<OptionDef *> it(m_optionList);
while (it.hasNext()) while (it.hasNext())
{ {
OptionDef *option = it.next(); OptionDef *option = it.next();
usage << " ["; usage << " [";
if (!option->flag.isNull() && useFlags) if (!option->flag.isNull() && useFlags)
usage << flagPrefix << option->flag; usage << flagPrefix << option->flag;
else else
usage << optPrefix << option->name; usage << optPrefix << option->name;
if (option->type == otOption) if (option->type == otOption)
usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar;
usage << "]"; usage << "]";
} }
// arguments // arguments
QListIterator<PositionalDef *> it2(m_positionals); QListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext()) while (it2.hasNext())
{ {
PositionalDef *param = it2.next(); PositionalDef *param = it2.next();
usage << " " << (param->required ? "<" : "["); usage << " " << (param->required ? "<" : "[");
usage << param->metavar; usage << param->metavar;
usage << (param->required ? ">" : "]"); usage << (param->required ? ">" : "]");
} }
return usage.join(""); return usage.join("");
} }
// parsing // parsing
QHash<QString, QVariant> Parser::parse(QStringList argv) QHash<QString, QVariant> Parser::parse(QStringList argv)
{ {
QHash<QString, QVariant> map; QHash<QString, QVariant> map;
QStringListIterator it(argv); QStringListIterator it(argv);
QString programName = it.next(); QString programName = it.next();
QString optionPrefix; QString optionPrefix;
QString flagPrefix; QString flagPrefix;
QListIterator<PositionalDef *> positionals(m_positionals); QListIterator<PositionalDef *> positionals(m_positionals);
QStringList expecting; QStringList expecting;
getPrefix(optionPrefix, flagPrefix); getPrefix(optionPrefix, flagPrefix);
while (it.hasNext()) while (it.hasNext())
{ {
QString arg = it.next(); QString arg = it.next();
if (!expecting.isEmpty()) if (!expecting.isEmpty())
// we were expecting an argument // we were expecting an argument
{ {
QString name = expecting.first(); QString name = expecting.first();
/* /*
if (map.contains(name)) if (map.contains(name))
throw ParsingError( throw ParsingError(
QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); QString("Option %2%1 was given multiple times").arg(name, optionPrefix));
*/ */
map[name] = QVariant(arg); map[name] = QVariant(arg);
expecting.removeFirst(); expecting.removeFirst();
continue; continue;
} }
if (arg.startsWith(optionPrefix)) if (arg.startsWith(optionPrefix))
// we have an option // we have an option
{ {
// qDebug("Found option %s", qPrintable(arg)); // qDebug("Found option %s", qPrintable(arg));
QString name = arg.mid(optionPrefix.length()); QString name = arg.mid(optionPrefix.length());
QString equals; QString equals;
if ((m_argStyle == ArgumentStyle::Equals || if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) && m_argStyle == ArgumentStyle::SpaceAndEquals) &&
name.contains("=")) name.contains("="))
{ {
int i = name.indexOf("="); int i = name.indexOf("=");
equals = name.mid(i + 1); equals = name.mid(i + 1);
name = name.left(i); name = name.left(i);
} }
if (m_options.contains(name)) if (m_options.contains(name))
{ {
/* /*
if (map.contains(name)) if (map.contains(name))
throw ParsingError(QString("Option %2%1 was given multiple times") throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(name, optionPrefix)); .arg(name, optionPrefix));
*/ */
OptionDef *option = m_options[name]; OptionDef *option = m_options[name];
if (option->type == otSwitch) if (option->type == otSwitch)
map[name] = true; map[name] = true;
else // if (option->type == otOption) else // if (option->type == otOption)
{ {
if (m_argStyle == ArgumentStyle::Space) if (m_argStyle == ArgumentStyle::Space)
expecting.append(name); expecting.append(name);
else if (!equals.isNull()) else if (!equals.isNull())
map[name] = equals; map[name] = equals;
else if (m_argStyle == ArgumentStyle::SpaceAndEquals) else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(name); expecting.append(name);
else else
throw ParsingError(QString("Option %2%1 reqires an argument.") throw ParsingError(QString("Option %2%1 reqires an argument.")
.arg(name, optionPrefix)); .arg(name, optionPrefix));
} }
continue; continue;
} }
throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix));
} }
if (arg.startsWith(flagPrefix)) if (arg.startsWith(flagPrefix))
// we have (a) flag(s) // we have (a) flag(s)
{ {
// qDebug("Found flags %s", qPrintable(arg)); // qDebug("Found flags %s", qPrintable(arg));
QString flags = arg.mid(flagPrefix.length()); QString flags = arg.mid(flagPrefix.length());
QString equals; QString equals;
if ((m_argStyle == ArgumentStyle::Equals || if ((m_argStyle == ArgumentStyle::Equals ||
m_argStyle == ArgumentStyle::SpaceAndEquals) && m_argStyle == ArgumentStyle::SpaceAndEquals) &&
flags.contains("=")) flags.contains("="))
{ {
int i = flags.indexOf("="); int i = flags.indexOf("=");
equals = flags.mid(i + 1); equals = flags.mid(i + 1);
flags = flags.left(i); flags = flags.left(i);
} }
for (int i = 0; i < flags.length(); i++) for (int i = 0; i < flags.length(); i++)
{ {
QChar flag = flags.at(i); QChar flag = flags.at(i);
if (!m_flags.contains(flag)) if (!m_flags.contains(flag))
throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix));
OptionDef *option = m_flags[flag]; OptionDef *option = m_flags[flag];
/* /*
if (map.contains(option->name)) if (map.contains(option->name))
throw ParsingError(QString("Option %2%1 was given multiple times") throw ParsingError(QString("Option %2%1 was given multiple times")
.arg(option->name, optionPrefix)); .arg(option->name, optionPrefix));
*/ */
if (option->type == otSwitch) if (option->type == otSwitch)
map[option->name] = true; map[option->name] = true;
else // if (option->type == otOption) else // if (option->type == otOption)
{ {
if (m_argStyle == ArgumentStyle::Space) if (m_argStyle == ArgumentStyle::Space)
expecting.append(option->name); expecting.append(option->name);
else if (!equals.isNull()) else if (!equals.isNull())
if (i == flags.length() - 1) if (i == flags.length() - 1)
map[option->name] = equals; map[option->name] = equals;
else else
throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " throw ParsingError(QString("Flag %4%2 of Argument-requiring Option "
"%1 not last flag in %4%3") "%1 not last flag in %4%3")
.arg(option->name, flag, flags, flagPrefix)); .arg(option->name, flag, flags, flagPrefix));
else if (m_argStyle == ArgumentStyle::SpaceAndEquals) else if (m_argStyle == ArgumentStyle::SpaceAndEquals)
expecting.append(option->name); expecting.append(option->name);
else else
throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)")
.arg(option->name, flag, flagPrefix)); .arg(option->name, flag, flagPrefix));
} }
} }
continue; continue;
} }
// must be a positional argument // must be a positional argument
if (!positionals.hasNext()) if (!positionals.hasNext())
throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); throw ParsingError(QString("Don't know what to do with '%1'").arg(arg));
PositionalDef *param = positionals.next(); PositionalDef *param = positionals.next();
map[param->name] = arg; map[param->name] = arg;
} }
// check if we're missing something // check if we're missing something
if (!expecting.isEmpty()) if (!expecting.isEmpty())
throw ParsingError(QString("Was still expecting arguments for %2%1").arg( throw ParsingError(QString("Was still expecting arguments for %2%1").arg(
expecting.join(QString(", ") + optionPrefix), optionPrefix)); expecting.join(QString(", ") + optionPrefix), optionPrefix));
while (positionals.hasNext()) while (positionals.hasNext())
{ {
PositionalDef *param = positionals.next(); PositionalDef *param = positionals.next();
if (param->required) if (param->required)
throw ParsingError( throw ParsingError(
QString("Missing required positional argument '%1'").arg(param->name)); QString("Missing required positional argument '%1'").arg(param->name));
else else
map[param->name] = param->def; map[param->name] = param->def;
} }
// fill out gaps // fill out gaps
QListIterator<OptionDef *> iter(m_optionList); QListIterator<OptionDef *> iter(m_optionList);
while (iter.hasNext()) while (iter.hasNext())
{ {
OptionDef *option = iter.next(); OptionDef *option = iter.next();
if (!map.contains(option->name)) if (!map.contains(option->name))
map[option->name] = option->def; map[option->name] = option->def;
} }
return map; return map;
} }
// clear defs // clear defs
void Parser::clear() void Parser::clear()
{ {
m_flags.clear(); m_flags.clear();
m_params.clear(); m_params.clear();
m_options.clear(); m_options.clear();
QMutableListIterator<OptionDef *> it(m_optionList); QMutableListIterator<OptionDef *> it(m_optionList);
while (it.hasNext()) while (it.hasNext())
{ {
OptionDef *option = it.next(); OptionDef *option = it.next();
it.remove(); it.remove();
delete option; delete option;
} }
QMutableListIterator<PositionalDef *> it2(m_positionals); QMutableListIterator<PositionalDef *> it2(m_positionals);
while (it2.hasNext()) while (it2.hasNext())
{ {
PositionalDef *arg = it2.next(); PositionalDef *arg = it2.next();
it2.remove(); it2.remove();
delete arg; delete arg;
} }
} }
// Destructor // Destructor
Parser::~Parser() Parser::~Parser()
{ {
clear(); clear();
} }
// getPrefix // getPrefix
void Parser::getPrefix(QString &opt, QString &flag) void Parser::getPrefix(QString &opt, QString &flag)
{ {
if (m_flagStyle == FlagStyle::Windows) if (m_flagStyle == FlagStyle::Windows)
opt = flag = "/"; opt = flag = "/";
else if (m_flagStyle == FlagStyle::Unix) else if (m_flagStyle == FlagStyle::Unix)
opt = flag = "-"; opt = flag = "-";
// else if (m_flagStyle == FlagStyle::GNU) // else if (m_flagStyle == FlagStyle::GNU)
else else
{ {
opt = "--"; opt = "--";
flag = "-"; flag = "-";
} }
} }
// ParsingError // ParsingError

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -51,13 +51,13 @@ namespace FlagStyle
{ {
enum Enum enum Enum
{ {
GNU, /**< --option and -o (GNU Style) */ GNU, /**< --option and -o (GNU Style) */
Unix, /**< -option and -o (Unix Style) */ Unix, /**< -option and -o (Unix Style) */
Windows, /**< /option and /o (Windows Style) */ Windows, /**< /option and /o (Windows Style) */
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
Default = Windows Default = Windows
#else #else
Default = GNU Default = GNU
#endif #endif
}; };
} }
@@ -69,13 +69,13 @@ namespace ArgumentStyle
{ {
enum Enum enum Enum
{ {
Space, /**< --option=value */ Space, /**< --option=value */
Equals, /**< --option value */ Equals, /**< --option value */
SpaceAndEquals, /**< --option[= ]value */ SpaceAndEquals, /**< --option[= ]value */
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
Default = Equals Default = Equals
#else #else
Default = SpaceAndEquals Default = SpaceAndEquals
#endif #endif
}; };
} }
@@ -86,7 +86,7 @@ enum Enum
class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
{ {
public: public:
ParsingError(const QString &what); ParsingError(const QString &what);
}; };
/** /**
@@ -95,158 +95,158 @@ public:
class MULTIMC_LOGIC_EXPORT Parser class MULTIMC_LOGIC_EXPORT Parser
{ {
public: public:
/** /**
* @brief Parser constructor * @brief Parser constructor
* @param flagStyle the FlagStyle to use in this Parser * @param flagStyle the FlagStyle to use in this Parser
* @param argStyle the ArgumentStyle to use in this Parser * @param argStyle the ArgumentStyle to use in this Parser
*/ */
Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, Parser(FlagStyle::Enum flagStyle = FlagStyle::Default,
ArgumentStyle::Enum argStyle = ArgumentStyle::Default); ArgumentStyle::Enum argStyle = ArgumentStyle::Default);
/** /**
* @brief set the flag style * @brief set the flag style
* @param style * @param style
*/ */
void setFlagStyle(FlagStyle::Enum style); void setFlagStyle(FlagStyle::Enum style);
/** /**
* @brief get the flag style * @brief get the flag style
* @return * @return
*/ */
FlagStyle::Enum flagStyle(); FlagStyle::Enum flagStyle();
/** /**
* @brief set the argument style * @brief set the argument style
* @param style * @param style
*/ */
void setArgumentStyle(ArgumentStyle::Enum style); void setArgumentStyle(ArgumentStyle::Enum style);
/** /**
* @brief get the argument style * @brief get the argument style
* @return * @return
*/ */
ArgumentStyle::Enum argumentStyle(); ArgumentStyle::Enum argumentStyle();
/** /**
* @brief define a boolean switch * @brief define a boolean switch
* @param name the parameter name * @param name the parameter name
* @param def the default value * @param def the default value
*/ */
void addSwitch(QString name, bool def = false); void addSwitch(QString name, bool def = false);
/** /**
* @brief define an option that takes an additional argument * @brief define an option that takes an additional argument
* @param name the parameter name * @param name the parameter name
* @param def the default value * @param def the default value
*/ */
void addOption(QString name, QVariant def = QVariant()); void addOption(QString name, QVariant def = QVariant());
/** /**
* @brief define a positional argument * @brief define a positional argument
* @param name the parameter name * @param name the parameter name
* @param required wether this argument is required * @param required wether this argument is required
* @param def the default value * @param def the default value
*/ */
void addArgument(QString name, bool required = true, QVariant def = QVariant()); void addArgument(QString name, bool required = true, QVariant def = QVariant());
/** /**
* @brief adds a flag to an existing parameter * @brief adds a flag to an existing parameter
* @param name the (existing) parameter name * @param name the (existing) parameter name
* @param flag the flag character * @param flag the flag character
* @see addSwitch addArgument addOption * @see addSwitch addArgument addOption
* Note: any one parameter can only have one flag * Note: any one parameter can only have one flag
*/ */
void addShortOpt(QString name, QChar flag); void addShortOpt(QString name, QChar flag);
/** /**
* @brief adds documentation to a Parameter * @brief adds documentation to a Parameter
* @param name the parameter name * @param name the parameter name
* @param metavar a string to be displayed as placeholder for the value * @param metavar a string to be displayed as placeholder for the value
* @param doc a QString containing the documentation * @param doc a QString containing the documentation
* Note: on positional arguments, metavar replaces the name as displayed. * Note: on positional arguments, metavar replaces the name as displayed.
* on options , metavar replaces the value placeholder * on options , metavar replaces the value placeholder
*/ */
void addDocumentation(QString name, QString doc, QString metavar = QString()); void addDocumentation(QString name, QString doc, QString metavar = QString());
/** /**
* @brief generate a help message * @brief generate a help message
* @param progName the program name to use in the help message * @param progName the program name to use in the help message
* @param helpIndent how much the parameter documentation should be indented * @param helpIndent how much the parameter documentation should be indented
* @param flagsInUsage whether we should use flags instead of options in the usage * @param flagsInUsage whether we should use flags instead of options in the usage
* @return a help message * @return a help message
*/ */
QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true);
/** /**
* @brief generate a short usage message * @brief generate a short usage message
* @param progName the program name to use in the usage message * @param progName the program name to use in the usage message
* @param useFlags whether we should use flags instead of options * @param useFlags whether we should use flags instead of options
* @return a usage message * @return a usage message
*/ */
QString compileUsage(QString progName, bool useFlags = true); QString compileUsage(QString progName, bool useFlags = true);
/** /**
* @brief parse * @brief parse
* @param argv a QStringList containing the program ARGV * @param argv a QStringList containing the program ARGV
* @return a QHash mapping argument names to their values * @return a QHash mapping argument names to their values
*/ */
QHash<QString, QVariant> parse(QStringList argv); QHash<QString, QVariant> parse(QStringList argv);
/** /**
* @brief clear all definitions * @brief clear all definitions
*/ */
void clear(); void clear();
~Parser(); ~Parser();
private: private:
FlagStyle::Enum m_flagStyle; FlagStyle::Enum m_flagStyle;
ArgumentStyle::Enum m_argStyle; ArgumentStyle::Enum m_argStyle;
enum OptionType enum OptionType
{ {
otSwitch, otSwitch,
otOption otOption
}; };
// Important: the common part MUST BE COMMON ON ALL THREE structs // Important: the common part MUST BE COMMON ON ALL THREE structs
struct CommonDef struct CommonDef
{ {
QString name; QString name;
QString doc; QString doc;
QString metavar; QString metavar;
QVariant def; QVariant def;
}; };
struct OptionDef struct OptionDef
{ {
// common // common
QString name; QString name;
QString doc; QString doc;
QString metavar; QString metavar;
QVariant def; QVariant def;
// option // option
OptionType type; OptionType type;
QChar flag; QChar flag;
}; };
struct PositionalDef struct PositionalDef
{ {
// common // common
QString name; QString name;
QString doc; QString doc;
QString metavar; QString metavar;
QVariant def; QVariant def;
// positional // positional
bool required; bool required;
}; };
QHash<QString, OptionDef *> m_options; QHash<QString, OptionDef *> m_options;
QHash<QChar, OptionDef *> m_flags; QHash<QChar, OptionDef *> m_flags;
QHash<QString, CommonDef *> m_params; QHash<QString, CommonDef *> m_params;
QList<PositionalDef *> m_positionals; QList<PositionalDef *> m_positionals;
QList<OptionDef *> m_optionList; QList<OptionDef *> m_optionList;
void getPrefix(QString &opt, QString &flag); void getPrefix(QString &opt, QString &flag);
}; };
} }

View File

@@ -4,32 +4,32 @@ template <typename T>
class DefaultVariable class DefaultVariable
{ {
public: public:
DefaultVariable(const T & value) DefaultVariable(const T & value)
{ {
defaultValue = value; defaultValue = value;
} }
DefaultVariable<T> & operator =(const T & value) DefaultVariable<T> & operator =(const T & value)
{ {
currentValue = value; currentValue = value;
is_default = currentValue == defaultValue; is_default = currentValue == defaultValue;
is_explicit = true; is_explicit = true;
return *this; return *this;
} }
operator const T &() const operator const T &() const
{ {
return is_default ? defaultValue : currentValue; return is_default ? defaultValue : currentValue;
} }
bool isDefault() const bool isDefault() const
{ {
return is_default; return is_default;
} }
bool isExplicit() const bool isExplicit() const
{ {
return is_explicit; return is_explicit;
} }
private: private:
T currentValue; T currentValue;
T defaultValue; T defaultValue;
bool is_default = true; bool is_default = true;
bool is_explicit = false; bool is_explicit = false;
}; };

View File

@@ -3,23 +3,24 @@
#include "BaseVersion.h" #include "BaseVersion.h"
#include "BaseVersionList.h" #include "BaseVersionList.h"
#include <QDir> #include <QDir>
#include <QCoreApplication>
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QDebug> #include <QDebug>
#include "tasks/Task.h" #include "tasks/Task.h"
#include "wonko/WonkoIndex.h" #include "meta/Index.h"
#include "FileSystem.h"
#include <QDebug> #include <QDebug>
class Env::Private struct Env::Private
{ {
public: QNetworkAccessManager m_qnam;
QNetworkAccessManager m_qnam; shared_qobject_ptr<HttpMetaCache> m_metacache;
shared_qobject_ptr<HttpMetaCache> m_metacache; std::shared_ptr<IIconList> m_iconlist;
std::shared_ptr<IIconList> m_iconlist; shared_qobject_ptr<Meta::Index> m_metadataIndex;
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists; QString m_jarsPath;
shared_qobject_ptr<WonkoIndex> m_wonkoIndex; QSet<QString> m_features;
QString m_wonkoRootUrl;
}; };
static Env * instance; static Env * instance;
@@ -30,175 +31,179 @@ static Env * instance;
Env::Env() Env::Env()
{ {
d = new Private(); d = new Private();
} }
Env::~Env() Env::~Env()
{ {
delete d; delete d;
} }
Env& Env::Env::getInstance() Env& Env::Env::getInstance()
{ {
if(!instance) if(!instance)
{ {
instance = new Env(); instance = new Env();
} }
return *instance; return *instance;
} }
void Env::dispose() void Env::dispose()
{ {
delete instance; delete instance;
instance = nullptr; instance = nullptr;
} }
shared_qobject_ptr< HttpMetaCache > Env::metacache() shared_qobject_ptr< HttpMetaCache > Env::metacache()
{ {
return d->m_metacache; return d->m_metacache;
} }
QNetworkAccessManager& Env::qnam() const QNetworkAccessManager& Env::qnam() const
{ {
return d->m_qnam; return d->m_qnam;
} }
std::shared_ptr<IIconList> Env::icons() std::shared_ptr<IIconList> Env::icons()
{ {
return d->m_iconlist; return d->m_iconlist;
} }
void Env::registerIconList(std::shared_ptr<IIconList> iconlist) void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
{ {
d->m_iconlist = iconlist; d->m_iconlist = iconlist;
} }
BaseVersionPtr Env::getVersion(QString component, QString version) shared_qobject_ptr<Meta::Index> Env::metadataIndex()
{ {
auto list = getVersionList(component); if (!d->m_metadataIndex)
if(!list) {
{ d->m_metadataIndex.reset(new Meta::Index());
return nullptr; }
} return d->m_metadataIndex;
return list->findVersion(version);
}
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
{
auto iter = d->m_versionLists.find(component);
if(iter != d->m_versionLists.end())
{
return *iter;
}
//return std::make_shared<NullVersionList>();
return nullptr;
}
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
{
d->m_versionLists[name] = vlist;
}
shared_qobject_ptr<WonkoIndex> Env::wonkoIndex()
{
if (!d->m_wonkoIndex)
{
d->m_wonkoIndex.reset(new WonkoIndex());
}
return d->m_wonkoIndex;
} }
void Env::initHttpMetaCache() void Env::initHttpMetaCache()
{ {
auto &m_metacache = d->m_metacache; auto &m_metacache = d->m_metacache;
m_metacache.reset(new HttpMetaCache("metacache")); m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath()); m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath()); m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
m_metacache->addBase("versions", QDir("versions").absolutePath()); m_metacache->addBase("versions", QDir("versions").absolutePath());
m_metacache->addBase("libraries", QDir("libraries").absolutePath()); m_metacache->addBase("libraries", QDir("libraries").absolutePath());
m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath()); m_metacache->addBase("minecraftforge", QDir("mods/minecraftforge").absolutePath());
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath()); m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath()); m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath()); m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath()); m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->Load(); m_metacache->addBase("meta", QDir("meta").absolutePath());
m_metacache->Load();
} }
void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password) void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password)
{ {
// Set the application proxy settings. // Set the application proxy settings.
if (proxyTypeStr == "SOCKS5") if (proxyTypeStr == "SOCKS5")
{ {
QNetworkProxy::setApplicationProxy( QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password)); QNetworkProxy(QNetworkProxy::Socks5Proxy, addr, port, user, password));
} }
else if (proxyTypeStr == "HTTP") else if (proxyTypeStr == "HTTP")
{ {
QNetworkProxy::setApplicationProxy( QNetworkProxy::setApplicationProxy(
QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password)); QNetworkProxy(QNetworkProxy::HttpProxy, addr, port, user, password));
} }
else if (proxyTypeStr == "None") else if (proxyTypeStr == "None")
{ {
// If we have no proxy set, set no proxy and return. // If we have no proxy set, set no proxy and return.
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy)); QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::NoProxy));
} }
else else
{ {
// If we have "Default" selected, set Qt to use the system proxy settings. // If we have "Default" selected, set Qt to use the system proxy settings.
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
} }
qDebug() << "Detecting proxy settings..."; qDebug() << "Detecting proxy settings...";
QNetworkProxy proxy = QNetworkProxy::applicationProxy(); QNetworkProxy proxy = QNetworkProxy::applicationProxy();
d->m_qnam.setProxy(proxy); d->m_qnam.setProxy(proxy);
QString proxyDesc; QString proxyDesc;
if (proxy.type() == QNetworkProxy::NoProxy) if (proxy.type() == QNetworkProxy::NoProxy)
{ {
qDebug() << "Using no proxy is an option!"; qDebug() << "Using no proxy is an option!";
return; return;
} }
switch (proxy.type()) switch (proxy.type())
{ {
case QNetworkProxy::DefaultProxy: case QNetworkProxy::DefaultProxy:
proxyDesc = "Default proxy: "; proxyDesc = "Default proxy: ";
break; break;
case QNetworkProxy::Socks5Proxy: case QNetworkProxy::Socks5Proxy:
proxyDesc = "Socks5 proxy: "; proxyDesc = "Socks5 proxy: ";
break; break;
case QNetworkProxy::HttpProxy: case QNetworkProxy::HttpProxy:
proxyDesc = "HTTP proxy: "; proxyDesc = "HTTP proxy: ";
break; break;
case QNetworkProxy::HttpCachingProxy: case QNetworkProxy::HttpCachingProxy:
proxyDesc = "HTTP caching: "; proxyDesc = "HTTP caching: ";
break; break;
case QNetworkProxy::FtpCachingProxy: case QNetworkProxy::FtpCachingProxy:
proxyDesc = "FTP caching: "; proxyDesc = "FTP caching: ";
break; break;
default: default:
proxyDesc = "DERP proxy: "; proxyDesc = "DERP proxy: ";
break; break;
} }
proxyDesc += QString("%3@%1:%2 pass %4") proxyDesc += QString("%3@%1:%2 pass %4")
.arg(proxy.hostName()) .arg(proxy.hostName())
.arg(proxy.port()) .arg(proxy.port())
.arg(proxy.user()) .arg(proxy.user())
.arg(proxy.password()); .arg(proxy.password());
qDebug() << proxyDesc; qDebug() << proxyDesc;
} }
QString Env::wonkoRootUrl() const QString Env::getJarsPath()
{ {
return d->m_wonkoRootUrl; if(d->m_jarsPath.isEmpty())
{
return FS::PathCombine(QCoreApplication::applicationDirPath(), "jars");
}
return d->m_jarsPath;
} }
void Env::setWonkoRootUrl(const QString& url) void Env::setJarsPath(const QString& path)
{ {
d->m_wonkoRootUrl = url; d->m_jarsPath = path;
} }
#include "Env.moc" void Env::enableFeature(const QString& featureName, bool state)
{
if(state)
{
d->m_features.insert(featureName);
}
else
{
d->m_features.remove(featureName);
}
}
bool Env::isFeatureEnabled(const QString& featureName) const
{
return d->m_features.contains(featureName);
}
void Env::getEnabledFeatures(QSet<QString>& features) const
{
features = d->m_features;
}
void Env::setEnabledFeatures(const QSet<QString>& features) const
{
d->m_features = features;
}

View File

@@ -13,51 +13,53 @@ class QNetworkAccessManager;
class HttpMetaCache; class HttpMetaCache;
class BaseVersionList; class BaseVersionList;
class BaseVersion; class BaseVersion;
class WonkoIndex;
namespace Meta
{
class Index;
}
#if defined(ENV) #if defined(ENV)
#undef ENV #undef ENV
#endif #endif
#define ENV (Env::getInstance()) #define ENV (Env::getInstance())
class MULTIMC_LOGIC_EXPORT Env class MULTIMC_LOGIC_EXPORT Env
{ {
friend class MultiMC; friend class MultiMC;
private: private:
class Private; struct Private;
Env(); Env();
~Env(); ~Env();
static void dispose(); static void dispose();
public: public:
static Env& getInstance(); static Env& getInstance();
QNetworkAccessManager &qnam() const; QNetworkAccessManager &qnam() const;
shared_qobject_ptr<HttpMetaCache> metacache(); shared_qobject_ptr<HttpMetaCache> metacache();
std::shared_ptr<IIconList> icons(); std::shared_ptr<IIconList> icons();
/// init the cache. FIXME: possible future hook point /// init the cache. FIXME: possible future hook point
void initHttpMetaCache(); void initHttpMetaCache();
/// Updates the application proxy settings from the settings object. /// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password); void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
/// get a version list by name void registerIconList(std::shared_ptr<IIconList> iconlist);
std::shared_ptr<BaseVersionList> getVersionList(QString component);
/// get a version by list name and version name shared_qobject_ptr<Meta::Index> metadataIndex();
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist); QString getJarsPath();
void setJarsPath(const QString & path);
void registerIconList(std::shared_ptr<IIconList> iconlist); bool isFeatureEnabled(const QString & featureName) const;
void enableFeature(const QString & featureName, bool state = true);
shared_qobject_ptr<WonkoIndex> wonkoIndex(); void getEnabledFeatures(QSet<QString> & features) const;
void setEnabledFeatures(const QSet<QString> & features) const;
QString wonkoRootUrl() const;
void setWonkoRootUrl(const QString &url);
protected: protected:
Private * d; Private * d;
}; };

View File

@@ -11,24 +11,24 @@
class MULTIMC_LOGIC_EXPORT Exception : public std::exception class MULTIMC_LOGIC_EXPORT Exception : public std::exception
{ {
public: public:
Exception(const QString &message) : std::exception(), m_message(message) Exception(const QString &message) : std::exception(), m_message(message)
{ {
qCritical() << "Exception:" << message; qCritical() << "Exception:" << message;
} }
Exception(const Exception &other) Exception(const Exception &other)
: std::exception(), m_message(other.cause()) : std::exception(), m_message(other.cause())
{ {
} }
virtual ~Exception() noexcept {} virtual ~Exception() noexcept {}
const char *what() const noexcept const char *what() const noexcept
{ {
return m_message.toLatin1().constData(); return m_message.toLatin1().constData();
} }
QString cause() const QString cause() const
{ {
return m_message; return m_message;
} }
private: private:
QString m_message; QString m_message;
}; };

View 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;
};

View File

@@ -3,263 +3,271 @@
#include "FileSystem.h" #include "FileSystem.h"
#include <QDir> #include <QDir>
#include <QFile>
#include <QSaveFile> #include <QSaveFile>
#include <QFileInfo> #include <QFileInfo>
#include <QDebug> #include <QDebug>
#include <QUrl> #include <QUrl>
#include <QStandardPaths> #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 { namespace FS {
void ensureExists(const QDir &dir) void ensureExists(const QDir &dir)
{ {
if (!QDir().mkpath(dir.absolutePath())) if (!QDir().mkpath(dir.absolutePath()))
{ {
throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + throw FileSystemException("Unable to create folder " + dir.dirName() + " (" +
dir.absolutePath() + ")"); dir.absolutePath() + ")");
} }
} }
void write(const QString &filename, const QByteArray &data) void write(const QString &filename, const QByteArray &data)
{ {
ensureExists(QFileInfo(filename).dir()); ensureExists(QFileInfo(filename).dir());
QSaveFile file(filename); QSaveFile file(filename);
if (!file.open(QSaveFile::WriteOnly)) if (!file.open(QSaveFile::WriteOnly))
{ {
throw FileSystemException("Couldn't open " + filename + " for writing: " + throw FileSystemException("Couldn't open " + filename + " for writing: " +
file.errorString()); file.errorString());
} }
if (data.size() != file.write(data)) if (data.size() != file.write(data))
{ {
throw FileSystemException("Error writing data to " + filename + ": " + throw FileSystemException("Error writing data to " + filename + ": " +
file.errorString()); file.errorString());
} }
if (!file.commit()) if (!file.commit())
{ {
throw FileSystemException("Error while committing data to " + filename + ": " + throw FileSystemException("Error while committing data to " + filename + ": " +
file.errorString()); file.errorString());
} }
} }
QByteArray read(const QString &filename) QByteArray read(const QString &filename)
{ {
QFile file(filename); QFile file(filename);
if (!file.open(QFile::ReadOnly)) if (!file.open(QFile::ReadOnly))
{ {
throw FileSystemException("Unable to open " + filename + " for reading: " + throw FileSystemException("Unable to open " + filename + " for reading: " +
file.errorString()); file.errorString());
} }
const qint64 size = file.size(); const qint64 size = file.size();
QByteArray data(int(size), 0); QByteArray data(int(size), 0);
const qint64 ret = file.read(data.data(), size); const qint64 ret = file.read(data.data(), size);
if (ret == -1 || ret != size) if (ret == -1 || ret != size)
{ {
throw FileSystemException("Error reading data from " + filename + ": " + throw FileSystemException("Error reading data from " + filename + ": " +
file.errorString()); file.errorString());
} }
return data; return data;
} }
bool updateTimestamp(const QString& filename) bool updateTimestamp(const QString& filename)
{ {
QFile file(filename); #ifdef Q_OS_WIN32
if (!file.exists()) std::wstring filename_utf_16 = filename.toStdWString();
{ return (_wutime64(filename_utf_16.c_str(), nullptr) == 0);
return false; #else
} QByteArray filenameBA = QFile::encodeName(filename);
if (!file.open(QIODevice::ReadWrite)) return (utime(filenameBA.data(), nullptr) == 0);
{ #endif
return false;
}
const quint64 size = file.size();
file.seek(size);
file.write( QByteArray(1, '0') );
file.resize(size);
return true;
} }
bool ensureFilePathExists(QString filenamepath) bool ensureFilePathExists(QString filenamepath)
{ {
QFileInfo a(filenamepath); QFileInfo a(filenamepath);
QDir dir; QDir dir;
QString ensuredPath = a.path(); QString ensuredPath = a.path();
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }
bool ensureFolderPathExists(QString foldernamepath) bool ensureFolderPathExists(QString foldernamepath)
{ {
QFileInfo a(foldernamepath); QFileInfo a(foldernamepath);
QDir dir; QDir dir;
QString ensuredPath = a.filePath(); QString ensuredPath = a.filePath();
bool success = dir.mkpath(ensuredPath); bool success = dir.mkpath(ensuredPath);
return success; return success;
} }
bool copy::operator()(const QString &offset) bool copy::operator()(const QString &offset)
{ {
//NOTE always deep copy on windows. the alternatives are too messy. //NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
m_followSymlinks = true; m_followSymlinks = true;
#endif #endif
auto src = PathCombine(m_src.absolutePath(), offset); auto src = PathCombine(m_src.absolutePath(), offset);
auto dst = PathCombine(m_dst.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset);
QFileInfo currentSrc(src); QFileInfo currentSrc(src);
if (!currentSrc.exists()) if (!currentSrc.exists())
return false; return false;
if(!m_followSymlinks && currentSrc.isSymLink()) if(!m_followSymlinks && currentSrc.isSymLink())
{ {
qDebug() << "creating symlink" << src << " - " << dst; qDebug() << "creating symlink" << src << " - " << dst;
if (!ensureFilePathExists(dst)) if (!ensureFilePathExists(dst))
{ {
qWarning() << "Cannot create path!"; qWarning() << "Cannot create path!";
return false; return false;
} }
return QFile::link(currentSrc.symLinkTarget(), dst); return QFile::link(currentSrc.symLinkTarget(), dst);
} }
else if(currentSrc.isFile()) else if(currentSrc.isFile())
{ {
qDebug() << "copying file" << src << " - " << dst; qDebug() << "copying file" << src << " - " << dst;
if (!ensureFilePathExists(dst)) if (!ensureFilePathExists(dst))
{ {
qWarning() << "Cannot create path!"; qWarning() << "Cannot create path!";
return false; return false;
} }
return QFile::copy(src, dst); return QFile::copy(src, dst);
} }
else if(currentSrc.isDir()) else if(currentSrc.isDir())
{ {
qDebug() << "recursing" << offset; qDebug() << "recursing" << offset;
if (!ensureFolderPathExists(dst)) if (!ensureFolderPathExists(dst))
{ {
qWarning() << "Cannot create path!"; qWarning() << "Cannot create path!";
return false; return false;
} }
QDir currentDir(src); QDir currentDir(src);
for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System))
{ {
auto inner_offset = PathCombine(offset, f); auto inner_offset = PathCombine(offset, f);
// ignore and skip stuff that matches the blacklist. // ignore and skip stuff that matches the blacklist.
if(m_blacklist && m_blacklist->matches(inner_offset)) if(m_blacklist && m_blacklist->matches(inner_offset))
{ {
continue; continue;
} }
if(!operator()(inner_offset)) if(!operator()(inner_offset))
{ {
qWarning() << "Failed to copy" << inner_offset; qWarning() << "Failed to copy" << inner_offset;
return false; return false;
} }
} }
} }
else else
{ {
qCritical() << "Copy ERROR: Unknown filesystem object:" << src; qCritical() << "Copy ERROR: Unknown filesystem object:" << src;
return false; return false;
} }
return true; return true;
} }
#if defined Q_OS_WIN32
#include <windows.h>
#include <string>
#endif
bool deletePath(QString path) bool deletePath(QString path)
{ {
bool OK = true; bool OK = true;
QDir dir(path); QDir dir(path);
if (!dir.exists()) if (!dir.exists())
{ {
return OK; return OK;
} }
auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
QDir::AllDirs | QDir::Files, QDir::AllDirs | QDir::Files,
QDir::DirsFirst); QDir::DirsFirst);
for(auto & info: allEntries) for(auto & info: allEntries)
{ {
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath());
auto wString = nativePath.toStdWString(); auto wString = nativePath.toStdWString();
DWORD dwAttrs = GetFileAttributesW(wString.c_str()); DWORD dwAttrs = GetFileAttributesW(wString.c_str());
// Windows: check for junctions, reparse points and other nasty things of that sort // Windows: check for junctions, reparse points and other nasty things of that sort
if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)
{ {
if (info.isFile()) if (info.isFile())
{ {
OK &= QFile::remove(info.absoluteFilePath()); OK &= QFile::remove(info.absoluteFilePath());
} }
else if (info.isDir()) else if (info.isDir())
{ {
OK &= dir.rmdir(info.absoluteFilePath()); OK &= dir.rmdir(info.absoluteFilePath());
} }
} }
#else #else
// We do not trust Qt with reparse points, but do trust it with unix symlinks. // We do not trust Qt with reparse points, but do trust it with unix symlinks.
if(info.isSymLink()) if(info.isSymLink())
{ {
OK &= QFile::remove(info.absoluteFilePath()); OK &= QFile::remove(info.absoluteFilePath());
} }
#endif #endif
else if (info.isDir()) else if (info.isDir())
{ {
OK &= deletePath(info.absoluteFilePath()); OK &= deletePath(info.absoluteFilePath());
} }
else if (info.isFile()) else if (info.isFile())
{ {
OK &= QFile::remove(info.absoluteFilePath()); OK &= QFile::remove(info.absoluteFilePath());
} }
else else
{ {
OK = false; OK = false;
qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath();
} }
} }
OK &= dir.rmdir(dir.absolutePath()); OK &= dir.rmdir(dir.absolutePath());
return OK; return OK;
} }
QString PathCombine(QString path1, QString path2) QString PathCombine(const QString & path1, const QString & path2)
{ {
if(!path1.size()) if(!path1.size())
return path2; return path2;
if(!path2.size()) if(!path2.size())
return path1; return path1;
return QDir::cleanPath(path1 + QDir::separator() + path2); return QDir::cleanPath(path1 + QDir::separator() + path2);
} }
QString PathCombine(QString path1, QString path2, QString path3) QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
{ {
return PathCombine(PathCombine(path1, path2), path3); return PathCombine(PathCombine(path1, path2), path3);
}
QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
{
return PathCombine(PathCombine(path1, path2, path3), path4);
} }
QString AbsolutePath(QString path) QString AbsolutePath(QString path)
{ {
return QFileInfo(path).absolutePath(); return QFileInfo(path).absolutePath();
} }
QString ResolveExecutable(QString path) QString ResolveExecutable(QString path)
{ {
if (path.isEmpty()) if (path.isEmpty())
{ {
return QString(); return QString();
} }
if(!path.contains('/')) if(!path.contains('/'))
{ {
path = QStandardPaths::findExecutable(path); path = QStandardPaths::findExecutable(path);
} }
QFileInfo pathInfo(path); QFileInfo pathInfo(path);
if(!pathInfo.exists() || !pathInfo.isExecutable()) if(!pathInfo.exists() || !pathInfo.isExecutable())
{ {
return QString(); return QString();
} }
return pathInfo.absoluteFilePath(); return pathInfo.absoluteFilePath();
} }
/** /**
@@ -270,187 +278,175 @@ QString ResolveExecutable(QString path)
*/ */
QString NormalizePath(QString path) QString NormalizePath(QString path)
{ {
QDir a = QDir::currentPath(); QDir a = QDir::currentPath();
QString currentAbsolute = a.absolutePath(); QString currentAbsolute = a.absolutePath();
QDir b(path); QDir b(path);
QString newAbsolute = b.absolutePath(); QString newAbsolute = b.absolutePath();
if (newAbsolute.startsWith(currentAbsolute)) if (newAbsolute.startsWith(currentAbsolute))
{ {
return a.relativeFilePath(newAbsolute); return a.relativeFilePath(newAbsolute);
} }
else else
{ {
return newAbsolute; return newAbsolute;
} }
} }
QString badFilenameChars = "\"\\/?<>:*|!"; QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{ {
for (int i = 0; i < string.length(); i++) for (int i = 0; i < string.length(); i++)
{ {
if (badFilenameChars.contains(string[i])) if (badFilenameChars.contains(string[i]))
{ {
string[i] = replaceWith; string[i] = replaceWith;
} }
} }
return string; return string;
} }
QString DirNameFromString(QString string, QString inDir) QString DirNameFromString(QString string, QString inDir)
{ {
int num = 0; int num = 0;
QString baseName = RemoveInvalidFilenameChars(string, '-'); QString baseName = RemoveInvalidFilenameChars(string, '-');
QString dirName; QString dirName;
do do
{ {
if(num == 0) if(num == 0)
{ {
dirName = baseName; dirName = baseName;
} }
else else
{ {
dirName = baseName + QString::number(num);; dirName = baseName + QString::number(num);;
} }
// If it's over 9000 // If it's over 9000
if (num > 9000) if (num > 9000)
return ""; return "";
num++; num++;
} while (QFileInfo(PathCombine(inDir, dirName)).exists()); } while (QFileInfo(PathCombine(inDir, dirName)).exists());
return dirName; return dirName;
} }
// Does the folder path contain any '!'? If yes, return true, otherwise false. // Does the folder path contain any '!'? If yes, return true, otherwise false.
// (This is a problem for Java) // (This is a problem for Java)
bool checkProblemticPathJava(QDir folder) bool checkProblemticPathJava(QDir folder)
{ {
QString pathfoldername = folder.absolutePath(); QString pathfoldername = folder.absolutePath();
return pathfoldername.contains("!", Qt::CaseInsensitive); return pathfoldername.contains("!", Qt::CaseInsensitive);
} }
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
// Win32 crap // Win32 crap
#if defined Q_OS_WIN #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; bool called_coinit = false;
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
{ {
HRESULT hres; HRESULT hres;
if (!called_coinit) if (!called_coinit)
{ {
hres = CoInitialize(NULL); hres = CoInitialize(NULL);
called_coinit = true; called_coinit = true;
if (!SUCCEEDED(hres)) if (!SUCCEEDED(hres))
{ {
qWarning("Failed to initialize COM. Error 0x%08X", hres); qWarning("Failed to initialize COM. Error 0x%08lX", hres);
return hres; return hres;
} }
} }
IShellLink *link; IShellLink *link;
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
(LPVOID *)&link); (LPVOID *)&link);
if (SUCCEEDED(hres)) if (SUCCEEDED(hres))
{ {
IPersistFile *persistFile; IPersistFile *persistFile;
link->SetPath(targetPath); link->SetPath(targetPath);
link->SetArguments(args); link->SetArguments(args);
hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
if (SUCCEEDED(hres)) if (SUCCEEDED(hres))
{ {
WCHAR wstr[MAX_PATH]; WCHAR wstr[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH);
hres = persistFile->Save(wstr, TRUE); hres = persistFile->Save(wstr, TRUE);
persistFile->Release(); persistFile->Release();
} }
link->Release(); link->Release();
} }
return hres; return hres;
} }
#endif #endif
QString getDesktopDir() QString getDesktopDir()
{ {
return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
} }
// Cross-platform Shortcut creation // Cross-platform Shortcut creation
bool createShortCut(QString location, QString dest, QStringList args, QString name, bool createShortCut(QString location, QString dest, QStringList args, QString name,
QString icon) QString icon)
{ {
#if defined Q_OS_LINUX #if defined Q_OS_LINUX
location = PathCombine(location, name + ".desktop"); location = PathCombine(location, name + ".desktop");
QFile f(location); QFile f(location);
f.open(QIODevice::WriteOnly | QIODevice::Text); f.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream stream(&f); QTextStream stream(&f);
QString argstring; QString argstring;
if (!args.empty()) if (!args.empty())
argstring = " '" + args.join("' '") + "'"; argstring = " '" + args.join("' '") + "'";
stream << "[Desktop Entry]" stream << "[Desktop Entry]"
<< "\n"; << "\n";
stream << "Type=Application" stream << "Type=Application"
<< "\n"; << "\n";
stream << "TryExec=" << dest.toLocal8Bit() << "\n"; stream << "TryExec=" << dest.toLocal8Bit() << "\n";
stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n"; stream << "Exec=" << dest.toLocal8Bit() << argstring.toLocal8Bit() << "\n";
stream << "Name=" << name.toLocal8Bit() << "\n"; stream << "Name=" << name.toLocal8Bit() << "\n";
stream << "Icon=" << icon.toLocal8Bit() << "\n"; stream << "Icon=" << icon.toLocal8Bit() << "\n";
stream.flush(); stream.flush();
f.close(); f.close();
f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup |
QFileDevice::ExeOther); QFileDevice::ExeOther);
return true; return true;
#elif defined Q_OS_WIN #elif defined Q_OS_WIN
// TODO: Fix // TODO: Fix
// QFile file(PathCombine(location, name + ".lnk")); // QFile file(PathCombine(location, name + ".lnk"));
// WCHAR *file_w; // WCHAR *file_w;
// WCHAR *dest_w; // WCHAR *dest_w;
// WCHAR *args_w; // WCHAR *args_w;
// file.fileName().toWCharArray(file_w); // file.fileName().toWCharArray(file_w);
// dest.toWCharArray(dest_w); // dest.toWCharArray(dest_w);
// QString argStr; // QString argStr;
// for (int i = 0; i < args.count(); i++) // for (int i = 0; i < args.count(); i++)
// { // {
// argStr.append(args[i]); // argStr.append(args[i]);
// argStr.append(" "); // argStr.append(" ");
// } // }
// argStr.toWCharArray(args_w); // argStr.toWCharArray(args_w);
// return SUCCEEDED(CreateLink(file_w, dest_w, args_w)); // return SUCCEEDED(CreateLink(file_w, dest_w, args_w));
return false; return false;
#else #else
qWarning("Desktop Shortcuts not supported on your platform!"); qWarning("Desktop Shortcuts not supported on your platform!");
return false; return false;
#endif #endif
} }
} }

View File

@@ -15,7 +15,7 @@ namespace FS
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
{ {
public: public:
FileSystemException(const QString &message) : Exception(message) {} FileSystemException(const QString &message) : Exception(message) {}
}; };
/** /**
@@ -48,34 +48,34 @@ MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
class MULTIMC_LOGIC_EXPORT copy class MULTIMC_LOGIC_EXPORT copy
{ {
public: public:
copy(const QString & src, const QString & dst) copy(const QString & src, const QString & dst)
{ {
m_src = src; m_src = src;
m_dst = dst; m_dst = dst;
} }
copy & followSymlinks(const bool follow) copy & followSymlinks(const bool follow)
{ {
m_followSymlinks = follow; m_followSymlinks = follow;
return *this; return *this;
} }
copy & blacklist(const IPathMatcher * filter) copy & blacklist(const IPathMatcher * filter)
{ {
m_blacklist = filter; m_blacklist = filter;
return *this; return *this;
} }
bool operator()() bool operator()()
{ {
return operator()(QString()); return operator()(QString());
} }
private: private:
bool operator()(const QString &offset); bool operator()(const QString &offset);
private: private:
bool m_followSymlinks = true; bool m_followSymlinks = true;
const IPathMatcher * m_blacklist = nullptr; const IPathMatcher * m_blacklist = nullptr;
QDir m_src; QDir m_src;
QDir m_dst; QDir m_dst;
}; };
/** /**
@@ -83,8 +83,9 @@ private:
*/ */
MULTIMC_LOGIC_EXPORT bool deletePath(QString path); MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2); MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3); MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path); MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);

View File

@@ -7,155 +7,155 @@
class FileSystemTest : public QObject class FileSystemTest : public QObject
{ {
Q_OBJECT Q_OBJECT
const QString bothSlash = "/foo/"; const QString bothSlash = "/foo/";
const QString trailingSlash = "foo/"; const QString trailingSlash = "foo/";
const QString leadingSlash = "/foo"; const QString leadingSlash = "/foo";
private private
slots: slots:
void test_pathCombine() void test_pathCombine()
{ {
QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash)); QCOMPARE(QString("/foo/foo"), FS::PathCombine(bothSlash, bothSlash));
QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash)); QCOMPARE(QString("foo/foo"), FS::PathCombine(trailingSlash, trailingSlash));
QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash)); QCOMPARE(QString("/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash));
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash)); QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(bothSlash, bothSlash, bothSlash));
QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash)); QCOMPARE(QString("foo/foo/foo"), FS::PathCombine(trailingSlash, trailingSlash, trailingSlash));
QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash)); QCOMPARE(QString("/foo/foo/foo"), FS::PathCombine(leadingSlash, leadingSlash, leadingSlash));
} }
void test_PathCombine1_data() void test_PathCombine1_data()
{ {
QTest::addColumn<QString>("result"); QTest::addColumn<QString>("result");
QTest::addColumn<QString>("path1"); QTest::addColumn<QString>("path1");
QTest::addColumn<QString>("path2"); QTest::addColumn<QString>("path2");
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl"; QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl"; QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc"; QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc";
QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl"; QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl";
QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl"; QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
#endif #endif
} }
void test_PathCombine1() void test_PathCombine1()
{ {
QFETCH(QString, result); QFETCH(QString, result);
QFETCH(QString, path1); QFETCH(QString, path1);
QFETCH(QString, path2); QFETCH(QString, path2);
QCOMPARE(FS::PathCombine(path1, path2), result); QCOMPARE(FS::PathCombine(path1, path2), result);
} }
void test_PathCombine2_data() void test_PathCombine2_data()
{ {
QTest::addColumn<QString>("result"); QTest::addColumn<QString>("result");
QTest::addColumn<QString>("path1"); QTest::addColumn<QString>("path1");
QTest::addColumn<QString>("path2"); QTest::addColumn<QString>("path2");
QTest::addColumn<QString>("path3"); QTest::addColumn<QString>("path3");
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl"; QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl"; QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl"; QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl"; QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl"; QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl";
QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl"; QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl"; QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
#endif #endif
} }
void test_PathCombine2() void test_PathCombine2()
{ {
QFETCH(QString, result); QFETCH(QString, result);
QFETCH(QString, path1); QFETCH(QString, path1);
QFETCH(QString, path2); QFETCH(QString, path2);
QFETCH(QString, path3); QFETCH(QString, path3);
QCOMPARE(FS::PathCombine(path1, path2, path3), result); QCOMPARE(FS::PathCombine(path1, path2, path3), result);
} }
void test_copy() void test_copy()
{ {
QString folder = QFINDTESTDATA("data/test_folder"); QString folder = QFINDTESTDATA("data/test_folder");
auto f = [&folder]() auto f = [&folder]()
{ {
QTemporaryDir tempDir; QTemporaryDir tempDir;
tempDir.setAutoRemove(true); tempDir.setAutoRemove(true);
qDebug() << "From:" << folder << "To:" << tempDir.path(); qDebug() << "From:" << folder << "To:" << tempDir.path();
QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder"));
qDebug() << tempDir.path(); qDebug() << tempDir.path();
qDebug() << target_dir.path(); qDebug() << target_dir.path();
FS::copy c(folder, target_dir.path()); FS::copy c(folder, target_dir.path());
c(); c();
for(auto entry: target_dir.entryList()) for(auto entry: target_dir.entryList())
{ {
qDebug() << entry; qDebug() << entry;
} }
QVERIFY(target_dir.entryList().contains("pack.mcmeta")); QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets")); QVERIFY(target_dir.entryList().contains("assets"));
}; };
// first try variant without trailing / // first try variant without trailing /
QVERIFY(!folder.endsWith('/')); QVERIFY(!folder.endsWith('/'));
f(); f();
// then variant with trailing / // then variant with trailing /
folder.append('/'); folder.append('/');
QVERIFY(folder.endsWith('/')); QVERIFY(folder.endsWith('/'));
f(); f();
} }
void test_getDesktop() void test_getDesktop()
{ {
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
} }
// this is only valid on linux // this is only valid on linux
// FIXME: implement on windows, OSX, then test. // FIXME: implement on windows, OSX, then test.
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
void test_createShortcut_data() void test_createShortcut_data()
{ {
QTest::addColumn<QString>("location"); QTest::addColumn<QString>("location");
QTest::addColumn<QString>("dest"); QTest::addColumn<QString>("dest");
QTest::addColumn<QStringList>("args"); QTest::addColumn<QStringList>("args");
QTest::addColumn<QString>("name"); QTest::addColumn<QString>("name");
QTest::addColumn<QString>("iconLocation"); QTest::addColumn<QString>("iconLocation");
QTest::addColumn<QByteArray>("result"); QTest::addColumn<QByteArray>("result");
QTest::newRow("unix") << QDir::currentPath() QTest::newRow("unix") << QDir::currentPath()
<< "asdfDest" << "asdfDest"
<< (QStringList() << "arg1" << "arg2") << (QStringList() << "arg1" << "arg2")
<< "asdf" << "asdf"
<< QString() << QString()
#if defined(Q_OS_LINUX) #if defined(Q_OS_LINUX)
<< MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix") << MULTIMC_GET_TEST_FILE("data/FileSystem-test_createShortcut-unix")
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
<< QByteArray() << QByteArray()
#endif #endif
; ;
} }
void test_createShortcut() void test_createShortcut()
{ {
QFETCH(QString, location); QFETCH(QString, location);
QFETCH(QString, dest); QFETCH(QString, dest);
QFETCH(QStringList, args); QFETCH(QStringList, args);
QFETCH(QString, name); QFETCH(QString, name);
QFETCH(QString, iconLocation); QFETCH(QString, iconLocation);
QFETCH(QByteArray, result); QFETCH(QByteArray, result);
QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation)); QVERIFY(FS::createShortCut(location, dest, args, name, iconLocation));
QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result)); QCOMPARE(QString::fromLocal8Bit(TestsInternal::readFile(location + QDir::separator() + name + ".desktop")), QString::fromLocal8Bit(result));
//QDir().remove(location); //QDir().remove(location);
} }
#endif #endif
}; };

31
api/logic/Filter.cpp Normal file
View 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
View 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 &regexp, bool invert);
virtual ~RegexpFilter();
bool accepts(const QString & value) override;
private:
QRegularExpression pattern;
bool invert = false;
};

View File

@@ -1,356 +0,0 @@
#include "FolderInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
#include "minecraft/onesix/OneSixInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
#include <QDir>
#include <QDirIterator>
#include <QFileSystemWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
const static int GROUP_FILE_FORMAT_VERSION = 1;
struct WatchLock
{
WatchLock(QFileSystemWatcher * watcher, const QString& instDir)
: m_watcher(watcher), m_instDir(instDir)
{
m_watcher->removePath(m_instDir);
}
~WatchLock()
{
m_watcher->addPath(m_instDir);
}
QFileSystemWatcher * m_watcher;
QString m_instDir;
};
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
: BaseInstanceProvider(settings)
{
m_instDir = instDir;
if (!QDir::current().exists(m_instDir))
{
QDir::current().mkpath(m_instDir);
}
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
m_watcher->addPath(m_instDir);
}
QList< InstanceId > FolderInstanceProvider::discoverInstances()
{
QList<InstanceId> out;
QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
QFileInfo dirInfo(subDir);
if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists())
continue;
// if it is a symlink, ignore it if it goes to the instance folder
if(dirInfo.isSymLink())
{
QFileInfo targetInfo(dirInfo.symLinkTarget());
QFileInfo instDirInfo(m_instDir);
if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath())
{
qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder";
continue;
}
}
auto id = dirInfo.fileName();
out.append(id);
qDebug() << "Found instance ID" << id;
}
return out;
}
InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
{
if(!m_groupsLoaded)
{
loadGroupList();
}
auto instanceRoot = FS::PathCombine(m_instDir, id);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(instanceRoot, "instance.cfg"));
InstancePtr inst;
instanceSettings->registerSetting("InstanceType", "Legacy");
QString inst_type = instanceSettings->get("InstanceType").toString();
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else if (inst_type == "Legacy")
{
inst.reset(new LegacyInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else
{
inst.reset(new NullInstance(m_globalSettings, instanceSettings, instanceRoot));
}
inst->init();
inst->setProvider(this);
auto iter = groupMap.find(id);
if (iter != groupMap.end())
{
inst->setGroupInitial((*iter));
}
connect(inst.get(), &BaseInstance::groupChanged, this, &FolderInstanceProvider::groupChanged);
qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
return inst;
}
#include "InstanceImportTask.h"
Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup);
}
#include "InstanceCreationTask.h"
Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
{
return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup);
}
#include "InstanceCopyTask.h"
Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
{
return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves);
}
void FolderInstanceProvider::saveGroupList()
{
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
for (auto iter = groupMap.begin(); iter != groupMap.end(); iter++)
{
QString id = iter.key();
QString group = iter.value();
if (group.isEmpty())
continue;
if (!reverseGroupMap.count(group))
{
QSet<QString> set;
set.insert(id);
reverseGroupMap[group] = set;
}
else
{
QSet<QString> &set = reverseGroupMap[group];
set.insert(id);
}
}
QJsonObject toplevel;
toplevel.insert("formatVersion", QJsonValue(QString("1")));
QJsonObject groupsArr;
for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++)
{
auto list = iter.value();
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
groupObj.insert("hidden", QJsonValue(QString("false")));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
}
groupObj.insert("instances", instanceArr);
groupsArr.insert(name, groupObj);
}
toplevel.insert("groups", groupsArr);
QJsonDocument doc(toplevel);
try
{
FS::write(groupFileName, doc.toJson());
}
catch(FS::FileSystemException & e)
{
qCritical() << "Failed to write instance group file :" << e.cause();
}
}
void FolderInstanceProvider::loadGroupList()
{
QSet<QString> groupSet;
QString groupFileName = m_instDir + "/instgroups.json";
// if there's no group file, fail
if (!QFileInfo(groupFileName).exists())
return;
QByteArray jsonData;
try
{
jsonData = FS::read(groupFileName);
}
catch (FS::FileSystemException & e)
{
qCritical() << "Failed to read instance group file :" << e.cause();
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
// if the json was bad, fail
if (error.error != QJsonParseError::NoError)
{
qCritical() << QString("Failed to parse instance group file: %1 at offset %2")
.arg(error.errorString(), QString::number(error.offset))
.toUtf8();
return;
}
// if the root of the json wasn't an object, fail
if (!jsonDoc.isObject())
{
qWarning() << "Invalid group file. Root entry should be an object.";
return;
}
QJsonObject rootObj = jsonDoc.object();
// Make sure the format version matches, otherwise fail.
if (rootObj.value("formatVersion").toVariant().toInt() != GROUP_FILE_FORMAT_VERSION)
return;
// Get the groups. if it's not an object, fail
if (!rootObj.value("groups").isObject())
{
qWarning() << "Invalid group list JSON: 'groups' should be an object.";
return;
}
groupMap.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++)
{
QString groupName = iter.key();
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
qWarning() << QString("Group '%1' in the group list should "
"be an object.")
.arg(groupName)
.toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
qWarning() << QString("Group '%1' in the group list is invalid. "
"It should contain an array "
"called 'instances'.")
.arg(groupName)
.toUtf8();
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end();
iter2++)
{
groupMap[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
emit groupsChanged(groupSet);
}
void FolderInstanceProvider::groupChanged()
{
// save the groups. save all of them.
auto instance = (BaseInstance *) QObject::sender();
auto id = instance->id();
groupMap[id] = instance->group();
emit groupsChanged({instance->group()});
saveGroupList();
}
void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
{
Q_UNUSED(path);
emit instancesChanged();
}
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
{
QString newInstDir = value.toString();
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
{
saveGroupList();
}
m_instDir = newInstDir;
m_groupsLoaded = false;
emit instancesChanged();
}
}
QString FolderInstanceProvider::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString();
QString relPath = FS::PathCombine("_MMC_TEMP/" , key);
QDir rootPath(m_instDir);
auto path = FS::PathCombine(m_instDir, relPath);
if(!rootPath.mkpath(relPath))
{
return QString();
}
return path;
}
bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
const QString& groupName)
{
if(!path.contains(keyPath))
{
qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath;
return false;
}
QDir dir;
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
if(!dir.rename(path, FS::PathCombine(m_instDir, instID)))
{
destroyStagingPath(keyPath);
return false;
}
groupMap[instID] = groupName;
emit groupsChanged({groupName});
emit instancesChanged();
}
saveGroupList();
return true;
}
bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
{
return FS::deletePath(keyPath);
}

View File

@@ -1,63 +0,0 @@
#pragma once
#include "BaseInstanceProvider.h"
#include <QMap>
class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT FolderInstanceProvider : public BaseInstanceProvider
{
Q_OBJECT
public:
FolderInstanceProvider(SettingsObjectPtr settings, const QString & instDir);
public:
/// used by InstanceList to @return a list of plausible IDs to probe for
QList<InstanceId> discoverInstances() override;
/// used by InstanceList to (re)load an instance with the given @id.
InstancePtr loadInstance(const InstanceId& id) override;
// create instance in this provider
Task * creationTask(BaseVersionPtr version, const QString &instName, const QString &instGroup, const QString &instIcon);
// copy instance to this provider
Task * copyTask(const InstancePtr &oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves);
// import zipped instance into this provider
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
*/
QString getStagedInstancePath() override;
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override;
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString & keyPath) override;
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
private slots:
void instanceDirContentsChanged(const QString &path);
void groupChanged();
private: /* methods */
void loadGroupList() override;
void saveGroupList() override;
private: /* data */
QString m_instDir;
QFileSystemWatcher * m_watcher;
QMap<QString, QString> groupMap;
bool m_groupsLoaded = false;
};

View File

@@ -4,112 +4,112 @@
bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes) bool GZip::unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes)
{ {
if (compressedBytes.size() == 0) if (compressedBytes.size() == 0)
{ {
uncompressedBytes = compressedBytes; uncompressedBytes = compressedBytes;
return true; return true;
} }
unsigned uncompLength = compressedBytes.size(); unsigned uncompLength = compressedBytes.size();
uncompressedBytes.clear(); uncompressedBytes.clear();
uncompressedBytes.resize(uncompLength); uncompressedBytes.resize(uncompLength);
z_stream strm; z_stream strm;
memset(&strm, 0, sizeof(strm)); memset(&strm, 0, sizeof(strm));
strm.next_in = (Bytef *)compressedBytes.data(); strm.next_in = (Bytef *)compressedBytes.data();
strm.avail_in = compressedBytes.size(); strm.avail_in = compressedBytes.size();
bool done = false; bool done = false;
if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK) if (inflateInit2(&strm, (16 + MAX_WBITS)) != Z_OK)
{ {
return false; return false;
} }
int err = Z_OK; int err = Z_OK;
while (!done) while (!done)
{ {
// If our output buffer is too small // If our output buffer is too small
if (strm.total_out >= uncompLength) if (strm.total_out >= uncompLength)
{ {
uncompressedBytes.resize(uncompLength * 2); uncompressedBytes.resize(uncompLength * 2);
uncompLength *= 2; uncompLength *= 2;
} }
strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out); strm.next_out = (Bytef *)(uncompressedBytes.data() + strm.total_out);
strm.avail_out = uncompLength - strm.total_out; strm.avail_out = uncompLength - strm.total_out;
// Inflate another chunk. // Inflate another chunk.
err = inflate(&strm, Z_SYNC_FLUSH); err = inflate(&strm, Z_SYNC_FLUSH);
if (err == Z_STREAM_END) if (err == Z_STREAM_END)
done = true; done = true;
else if (err != Z_OK) else if (err != Z_OK)
{ {
break; break;
} }
} }
if (inflateEnd(&strm) != Z_OK || !done) if (inflateEnd(&strm) != Z_OK || !done)
{ {
return false; return false;
} }
uncompressedBytes.resize(strm.total_out); uncompressedBytes.resize(strm.total_out);
return true; return true;
} }
bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes) bool GZip::zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes)
{ {
if (uncompressedBytes.size() == 0) if (uncompressedBytes.size() == 0)
{ {
compressedBytes = uncompressedBytes; compressedBytes = uncompressedBytes;
return true; return true;
} }
unsigned compLength = std::min(uncompressedBytes.size(), 16); unsigned compLength = std::min(uncompressedBytes.size(), 16);
compressedBytes.clear(); compressedBytes.clear();
compressedBytes.resize(compLength); compressedBytes.resize(compLength);
z_stream zs; z_stream zs;
memset(&zs, 0, sizeof(zs)); memset(&zs, 0, sizeof(zs));
if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK) if (deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (16 + MAX_WBITS), 8, Z_DEFAULT_STRATEGY) != Z_OK)
{ {
return false; return false;
} }
zs.next_in = (Bytef*)uncompressedBytes.data(); zs.next_in = (Bytef*)uncompressedBytes.data();
zs.avail_in = uncompressedBytes.size(); zs.avail_in = uncompressedBytes.size();
int ret; int ret;
compressedBytes.resize(uncompressedBytes.size()); compressedBytes.resize(uncompressedBytes.size());
unsigned offset = 0; unsigned offset = 0;
unsigned temp = 0; unsigned temp = 0;
do do
{ {
auto remaining = compressedBytes.size() - offset; auto remaining = compressedBytes.size() - offset;
if(remaining < 1) if(remaining < 1)
{ {
compressedBytes.resize(compressedBytes.size() * 2); compressedBytes.resize(compressedBytes.size() * 2);
} }
zs.next_out = (Bytef *) (compressedBytes.data() + offset); zs.next_out = (Bytef *) (compressedBytes.data() + offset);
temp = zs.avail_out = compressedBytes.size() - offset; temp = zs.avail_out = compressedBytes.size() - offset;
ret = deflate(&zs, Z_FINISH); ret = deflate(&zs, Z_FINISH);
offset += temp - zs.avail_out; offset += temp - zs.avail_out;
} while (ret == Z_OK); } while (ret == Z_OK);
compressedBytes.resize(offset); compressedBytes.resize(offset);
if (deflateEnd(&zs) != Z_OK) if (deflateEnd(&zs) != Z_OK)
{ {
return false; return false;
} }
if (ret != Z_STREAM_END) if (ret != Z_STREAM_END)
{ {
return false; return false;
} }
return true; return true;
} }

View File

@@ -6,7 +6,7 @@
class MULTIMC_LOGIC_EXPORT GZip class MULTIMC_LOGIC_EXPORT GZip
{ {
public: public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes); static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes); static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
}; };

View File

@@ -6,50 +6,50 @@
void fib(int &prev, int &cur) void fib(int &prev, int &cur)
{ {
auto ret = prev + cur; auto ret = prev + cur;
prev = cur; prev = cur;
cur = ret; cur = ret;
} }
class GZipTest : public QObject class GZipTest : public QObject
{ {
Q_OBJECT Q_OBJECT
private private
slots: slots:
void test_Through() void test_Through()
{ {
// test up to 10 MB // test up to 10 MB
static const int size = 10 * 1024 * 1024; static const int size = 10 * 1024 * 1024;
QByteArray random; QByteArray random;
QByteArray compressed; QByteArray compressed;
QByteArray decompressed; QByteArray decompressed;
std::default_random_engine eng((std::random_device())()); std::default_random_engine eng((std::random_device())());
std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max()); std::uniform_int_distribution<uint8_t> idis(0, std::numeric_limits<uint8_t>::max());
// initialize random buffer // initialize random buffer
for(int i = 0; i < size; i++) for(int i = 0; i < size; i++)
{ {
random.append((char)idis(eng)); random.append((char)idis(eng));
} }
// initialize fibonacci // initialize fibonacci
int prev = 1; int prev = 1;
int cur = 1; int cur = 1;
// test if fibonacci long random buffers pass through GZip // test if fibonacci long random buffers pass through GZip
do do
{ {
QByteArray copy = random; QByteArray copy = random;
copy.resize(cur); copy.resize(cur);
compressed.clear(); compressed.clear();
decompressed.clear(); decompressed.clear();
QVERIFY(GZip::zip(copy, compressed)); QVERIFY(GZip::zip(copy, compressed));
QVERIFY(GZip::unzip(compressed, decompressed)); QVERIFY(GZip::unzip(compressed, decompressed));
QCOMPARE(decompressed, copy); QCOMPARE(decompressed, copy);
fib(prev, cur); fib(prev, cur);
} while (cur < size); } while (cur < size);
} }
}; };
QTEST_GUILESS_MAIN(GZipTest) QTEST_GUILESS_MAIN(GZipTest)

View File

@@ -1,68 +1,56 @@
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "pathmatcher/RegexpMatcher.h" #include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves) InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
{ {
m_globalSettings = settings; m_origInstance = origInstance;
m_target = target;
m_origInstance = origInstance;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
if(!copySaves) if(!copySaves)
{ {
// FIXME: get this from the original instance type... // FIXME: get this from the original instance type...
auto matcherReal = new RegexpMatcher("[.]?minecraft/saves"); auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
matcherReal->caseSensitive(false); matcherReal->caseSensitive(false);
m_matcher.reset(matcherReal); m_matcher.reset(matcherReal);
} }
} }
void InstanceCopyTask::executeTask() void InstanceCopyTask::executeTask()
{ {
setStatus(tr("Copying instance %1").arg(m_origInstance->name())); setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
m_stagingPath = m_target->getStagedInstancePath();
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy); FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished); folderCopy.followSymlinks(false).blacklist(m_matcher.get());
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture); m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
} }
void InstanceCopyTask::copyFinished() void InstanceCopyTask::copyFinished()
{ {
auto successful = m_copyFuture.result(); auto successful = m_copyFuture.result();
if(!successful) if(!successful)
{ {
m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance folder copy failed."));
emitFailed(tr("Instance folder copy failed.")); return;
return; }
} // FIXME: shouldn't this be able to report errors?
// FIXME: shouldn't this be able to report errors? auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg")); instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->registerSetting("InstanceType", "Legacy");
// FIXME: and this too? errors??? InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
m_origInstance->copy(instanceSettings, m_stagingPath); inst->setName(m_instName);
inst->setIconKey(m_instIcon);
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); emitSucceeded();
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
m_target->commitStagedInstance(m_stagingPath, m_stagingPath, m_instName, m_instGroup);
emitSucceeded();
} }
void InstanceCopyTask::copyAborted() void InstanceCopyTask::copyAborted()
{ {
m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance folder copy has been aborted."));
emitFailed(tr("Instance folder copy has been aborted.")); return;
return;
} }

View File

@@ -9,34 +9,23 @@
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "InstanceTask.h"
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
class BaseInstanceProvider;
class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName, explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves);
const QString &instIcon, const QString &instGroup, bool copySaves);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
void copyFinished(); void copyFinished();
void copyAborted(); void copyAborted();
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings; InstancePtr m_origInstance;
BaseInstanceProvider * m_target = nullptr; QFuture<bool> m_copyFuture;
InstancePtr m_origInstance; QFutureWatcher<bool> m_copyFutureWatcher;
QString m_instName; std::unique_ptr<IPathMatcher> m_matcher;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher;
}; };

View File

@@ -1,46 +1,31 @@
#include "InstanceCreationTask.h" #include "InstanceCreationTask.h"
#include "BaseInstanceProvider.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "FileSystem.h" #include "FileSystem.h"
//FIXME: remove this //FIXME: remove this
#include "minecraft/MinecraftVersion.h" #include "minecraft/MinecraftInstance.h"
#include "minecraft/onesix/OneSixInstance.h" #include "minecraft/ComponentList.h"
InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version, InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
const QString& instName, const QString& instIcon, const QString& instGroup)
{ {
m_globalSettings = settings; m_version = version;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
m_version = version;
} }
void InstanceCreationTask::executeTask() void InstanceCreationTask::executeTask()
{ {
setStatus(tr("Creating instance from version %1").arg(m_version->name())); setStatus(tr("Creating instance from version %1").arg(m_version->name()));
auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version); {
if(!minecraftVersion) auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
{ instanceSettings->suspendSave();
emitFailed(tr("The supplied version is not a Minecraft version.")); instanceSettings->registerSetting("InstanceType", "Legacy");
return ; instanceSettings->set("InstanceType", "OneSix");
} MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
auto components = inst.getComponentList();
QString stagingPath = m_target->getStagedInstancePath(); components->buildingFromScratch();
QDir rootDir(stagingPath); components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
inst.setName(m_instName);
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg")); inst.setIconKey(m_instIcon);
instanceSettings->registerSetting("InstanceType", "Legacy"); instanceSettings->resumeSave();
}
auto mcVer = std::dynamic_pointer_cast<MinecraftVersion>(m_version); emitSucceeded();
instanceSettings->set("InstanceType", "OneSix");
InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath));
inst->setIntendedVersionId(m_version->descriptor());
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
inst->init();
m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
emitSucceeded();
} }

View File

@@ -6,26 +6,18 @@
#include <QUrl> #include <QUrl>
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "BaseVersion.h" #include "BaseVersion.h"
#include "InstanceTask.h"
class BaseInstanceProvider; class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public InstanceTask
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName, explicit InstanceCreationTask(BaseVersionPtr version);
const QString &instIcon, const QString &instGroup);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings; BaseVersionPtr m_version;
BaseInstanceProvider * m_target;
BaseVersionPtr m_version;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
}; };

View File

@@ -1,164 +1,411 @@
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h" #include "FileSystem.h"
#include "Env.h" #include "Env.h"
#include "MMCZip.h" #include "MMCZip.h"
#include "NullInstance.h" #include "NullInstance.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "icons/IIconList.h" #include "icons/IIconList.h"
#include "icons/IconUtils.h"
#include <QtConcurrentRun> #include <QtConcurrentRun>
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, // FIXME: this does not belong here, it's Minecraft/Flame specific
const QString &instName, const QString &instIcon, const QString &instGroup) #include "minecraft/MinecraftInstance.h"
#include "minecraft/ComponentList.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h"
#include "Json.h"
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
{ {
m_globalSettings = settings; m_sourceUrl = sourceUrl;
m_sourceUrl = sourceUrl;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
} }
void InstanceImportTask::executeTask() void InstanceImportTask::executeTask()
{ {
InstancePtr newInstance; InstancePtr newInstance;
if (m_sourceUrl.isLocalFile()) if (m_sourceUrl.isLocalFile())
{ {
m_archivePath = m_sourceUrl.toLocalFile(); m_archivePath = m_sourceUrl.toLocalFile();
extractAndTweak(); processZipPack();
} }
else else
{ {
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true; m_downloadRequired = true;
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = ENV.metacache()->resolveEntry("general", path); auto entry = ENV.metacache()->resolveEntry("general", path);
entry->setStale(true); entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"))); m_filesNetJob.reset(new NetJob(tr("Modpack download")));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath(); m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get(); auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
m_filesNetJob->start(); m_filesNetJob->start();
} }
} }
void InstanceImportTask::downloadSucceeded() void InstanceImportTask::downloadSucceeded()
{ {
extractAndTweak(); processZipPack();
m_filesNetJob.reset();
} }
void InstanceImportTask::downloadFailed(QString reason) void InstanceImportTask::downloadFailed(QString reason)
{ {
emitFailed(reason); emitFailed(reason);
m_filesNetJob.reset();
} }
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
{ {
setProgress(current / 2, total); setProgress(current / 2, total);
} }
static QFileInfo findRecursive(const QString &dir, const QString &name) void InstanceImportTask::processZipPack()
{ {
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast)) setStatus(tr("Extracting modpack"));
{ QDir extractDir(m_stagingPath);
if (info.isFile() && info.fileName() == name) qDebug() << "Attempting to create instance from" << m_archivePath;
{
return info;
}
else if (info.isDir())
{
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
if (res.isFile() && res.exists())
{
return res;
}
}
}
return QFileInfo();
}
void InstanceImportTask::extractAndTweak() // open the zip and find relevant files in it
{ m_packZip.reset(new QuaZip(m_archivePath));
setStatus(tr("Extracting modpack")); if (!m_packZip->open(QuaZip::mdUnzip))
m_stagingPath = m_target->getStagedInstancePath(); {
QDir extractDir(m_stagingPath); emitFailed(tr("Unable to open supplied modpack zip file."));
qDebug() << "Attempting to create instance from" << m_archivePath; return;
}
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath()); QStringList blacklist = {"instance.cfg", "manifest.json"};
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished); QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted); QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
m_extractFutureWatcher.setFuture(m_extractFuture); QString root;
if(!mmcFound.isNull())
{
// process as MultiMC instance/pack
qDebug() << "MultiMC:" << mmcFound;
root = mmcFound;
m_modpackType = ModpackType::MultiMC;
}
else if(!flameFound.isNull())
{
// process as Flame pack
qDebug() << "Flame:" << flameFound;
root = flameFound;
m_modpackType = ModpackType::Flame;
}
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
}
// make sure we extract just the pack
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture);
} }
void InstanceImportTask::extractFinished() void InstanceImportTask::extractFinished()
{ {
if (m_extractFuture.result().isEmpty()) m_packZip.reset();
{ if (m_extractFuture.result().isEmpty())
m_target->destroyStagingPath(m_stagingPath); {
emitFailed(tr("Failed to extract modpack")); emitFailed(tr("Failed to extract modpack"));
return; return;
} }
QDir extractDir(m_stagingPath); QDir extractDir(m_stagingPath);
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Archive does not contain instance.cfg"));
return;
}
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!! qDebug() << "Fixing permissions for extracted pack files...";
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath()); QDirIterator it(extractDir, QDirIterator::Subdirectories);
instanceSettings->registerSetting("InstanceType", "Legacy"); while (it.hasNext())
{
auto filepath = it.next();
QFileInfo file(filepath);
auto permissions = QFile::permissions(filepath);
auto origPermissions = permissions;
if(file.isDir())
{
// Folder +rwx for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
}
else
{
// File +rw for current user
permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
}
if(origPermissions != permissions)
{
if(!QFile::setPermissions(filepath, permissions))
{
logWarning(tr("Could not fix permissions for %1").arg(filepath));
}
else
{
qDebug() << "Fixed" << filepath;
}
}
}
QString actualDir = instanceCfgFile.absolutePath(); switch(m_modpackType)
NullInstance instance(m_globalSettings, instanceSettings, actualDir); {
case ModpackType::Flame:
// reset time played on import... because packs. processFlame();
instance.resetTimePlayed(); return;
case ModpackType::MultiMC:
// set a new nice name processMultiMC();
instance.setName(m_instName); return;
case ModpackType::Unknown:
// if the icon was specified by user, use that. otherwise pull icon from the pack emitFailed(tr("Archive does not contain a recognized modpack type."));
if (m_instIcon != "default") return;
{ }
instance.setIconKey(m_instIcon);
}
else
{
m_instIcon = instance.iconKey();
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
if (QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
if (iconList->iconFileExists(m_instIcon))
{
iconList->deleteIcon(m_instIcon);
}
iconList->installIcons({importIconPath});
}
}
if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup))
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Unable to commit instance"));
return;
}
emitSucceeded();
} }
void InstanceImportTask::extractAborted() void InstanceImportTask::extractAborted()
{ {
m_target->destroyStagingPath(m_stagingPath); emitFailed(tr("Instance import has been aborted."));
emitFailed(tr("Instance import has been aborted.")); return;
return; }
void InstanceImportTask::processFlame()
{
const static QMap<QString,QString> forgemap = {
{"1.2.5", "3.4.9.171"},
{"1.4.2", "6.0.1.355"},
{"1.4.7", "6.6.2.534"},
{"1.5.2", "7.8.1.737"}
};
Flame::Manifest pack;
try
{
QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
Flame::loadManifest(pack, configPath);
QFile::remove(configPath);
}
catch (const JSONValidationError &e)
{
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
if(!pack.overrides.isEmpty())
{
QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
if (QFile::exists(overridePath))
{
QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
if (!QFile::rename(overridePath, mcPath))
{
emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
return;
}
}
else
{
logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
}
}
QString forgeVersion;
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
if(id.startsWith("forge-"))
{
id.remove("forge-");
forgeVersion = id;
continue;
}
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
}
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto mcVersion = pack.minecraft.version;
// Hack to correct some 'special sauce'...
if(mcVersion.endsWith('.'))
{
mcVersion.remove(QRegExp("[.]+$"));
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
auto components = instance.getComponentList();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
{
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
if(forgeVersion == "recommended")
{
if(forgemap.contains(mcVersion))
{
forgeVersion = forgemap[mcVersion];
}
else
{
logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion));
}
}
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
if(pack.name.contains("Direwolf20"))
{
instance.setIconKey("steve");
}
else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast"))
{
instance.setIconKey("ftb_logo");
}
else
{
// default to something other than the MultiMC default to distinguish these
instance.setIconKey("flame");
}
}
QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
QFileInfo jarmodsInfo(jarmodsPath);
if(jarmodsInfo.isDir())
{
// install all the jar mods
qDebug() << "Found jarmods:";
QDir jarmodsDir(jarmodsPath);
QStringList jarMods;
for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files))
{
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
auto profile = instance.getComponentList();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
}
instance.setName(m_instName);
m_modIdResolver.reset(new Flame::FileResolvingTask(pack));
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]()
{
auto results = m_modIdResolver->getResults();
m_filesNetJob.reset(new NetJob(tr("Mod download")));
for(auto result: results.files)
{
QString filename = result.fileName;
if(!result.required)
{
filename += ".disabled";
}
auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
auto path = FS::PathCombine(m_stagingPath , relpath);
switch(result.type)
{
case Flame::File::Type::Folder:
{
logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
// fall-through intentional, we treat these as plain old mods and dump them wherever.
}
case Flame::File::Type::SingleFile:
case Flame::File::Type::Mod:
{
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
m_filesNetJob->addNetAction(dl);
break;
}
case Flame::File::Type::Modpack:
logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
break;
case Flame::File::Type::Cmod2:
case Flame::File::Type::Ctoc:
case Flame::File::Type::Unknown:
logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
break;
}
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
{
m_filesNetJob.reset();
emitFailed(reason);
});
connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
setStatus(tr("Downloading mods..."));
m_filesNetJob->start();
}
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
{
m_modIdResolver.reset();
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total)
{
setProgress(current, total);
});
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status)
{
setStatus(status);
});
m_modIdResolver->start();
}
void InstanceImportTask::processMultiMC()
{
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
// reset time played on import... because packs.
instance.resetTimePlayed();
// set a new nice name
instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
m_instIcon = instance.iconKey();
auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
if (!importIconPath.isNull() && QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
if (iconList->iconFileExists(m_instIcon))
{
iconList->deleteIcon(m_instIcon);
}
iconList->installIcons({importIconPath});
}
}
emitSucceeded();
} }

View File

@@ -1,47 +1,54 @@
#pragma once #pragma once
#include "tasks/Task.h" #include "InstanceTask.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "net/NetJob.h" #include "net/NetJob.h"
#include <QUrl> #include <QUrl>
#include <QFuture> #include <QFuture>
#include <QFutureWatcher> #include <QFutureWatcher>
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#include "QObjectPtr.h"
class BaseInstanceProvider; class QuaZip;
namespace Flame
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{ {
Q_OBJECT class FileResolvingTask;
}
class MULTIMC_LOGIC_EXPORT InstanceImportTask : public InstanceTask
{
Q_OBJECT
public: public:
explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName, explicit InstanceImportTask(const QUrl sourceUrl);
const QString &instIcon, const QString &instGroup);
protected: protected:
//! Entry point for tasks. //! Entry point for tasks.
virtual void executeTask() override; virtual void executeTask() override;
private: private:
void extractAndTweak(); void processZipPack();
void processMultiMC();
void processFlame();
private slots: private slots:
void downloadSucceeded(); void downloadSucceeded();
void downloadFailed(QString reason); void downloadFailed(QString reason);
void downloadProgressChanged(qint64 current, qint64 total); void downloadProgressChanged(qint64 current, qint64 total);
void extractFinished(); void extractFinished();
void extractAborted(); void extractAborted();
private: /* data */ private: /* data */
SettingsObjectPtr m_globalSettings; NetJobPtr m_filesNetJob;
NetJobPtr m_filesNetJob; shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl; QUrl m_sourceUrl;
BaseInstanceProvider * m_target; QString m_archivePath;
QString m_archivePath; bool m_downloadRequired = false;
bool m_downloadRequired = false; std::unique_ptr<QuaZip> m_packZip;
QString m_instName; QFuture<QStringList> m_extractFuture;
QString m_instIcon; QFutureWatcher<QStringList> m_extractFutureWatcher;
QString m_instGroup; enum class ModpackType{
QString m_stagingPath; Unknown,
QFuture<QStringList> m_extractFuture; MultiMC,
QFutureWatcher<QStringList> m_extractFutureWatcher; Flame
} m_modpackType = ModpackType::Unknown;
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,88 +21,146 @@
#include <QList> #include <QList>
#include "BaseInstance.h" #include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "QObjectPtr.h" #include "QObjectPtr.h"
class QFileSystemWatcher; class QFileSystemWatcher;
class BaseInstance; class InstanceTask;
class QDir; 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 class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0); explicit InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent = 0);
virtual ~InstanceList(); virtual ~InstanceList();
public: public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const; QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const override;
enum AdditionalRoles bool setData(const QModelIndex & index, const QVariant & value, int role) override;
{
GroupRole = Qt::UserRole,
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
};
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError
{
NoError = 0,
UnknownError
};
InstancePtr at(int i) const enum AdditionalRoles
{ {
return m_instances.at(i); GroupRole = Qt::UserRole,
} InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
};
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError
{
NoError = 0,
UnknownError
};
int count() const InstancePtr at(int i) const
{ {
return m_instances.count(); return m_instances.at(i);
} }
InstListError loadList(bool complete = false); int count() const
{
return m_instances.count();
}
/// Add an instance provider. Takes ownership of it. Should only be done before the first load. InstListError loadList();
void addInstanceProvider(BaseInstanceProvider * provider); void saveNow();
InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
void deleteGroup(const QString & name); InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
void deleteGroup(const GroupId & name);
void deleteInstance(const InstanceId & id);
// Wrap an instance creation task in some more task machinery and make it ready to be used
Task * wrapInstanceTask(InstanceTask * task);
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
*/
QString getStagedInstancePath();
/**
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName);
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
*/
bool destroyStagingPath(const QString & keyPath);
signals: signals:
void dataIsInvalid(); 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: private slots:
void propertiesChanged(BaseInstance *inst); void propertiesChanged(BaseInstance *inst);
void groupsPublished(QSet<QString>); void providerUpdated();
void providerUpdated(); void instanceDirContentsChanged(const QString &path);
private: private:
int getInstIndex(BaseInstance *inst) const; int getInstIndex(BaseInstance *inst) const;
void suspendWatch(); void suspendWatch();
void resumeWatch(); void resumeWatch();
void add(const QList<InstancePtr> &list); void add(const QList<InstancePtr> &list);
void loadGroupList();
void saveGroupList();
QList<InstanceId> discoverInstances();
InstancePtr loadInstance(const InstanceId& id);
protected: private:
int m_watchLevel = 0; int m_watchLevel = 0;
QSet<BaseInstanceProvider *> m_updatedProviders; bool m_dirty = false;
QString m_instDir; QList<InstancePtr> m_instances;
QList<InstancePtr> m_instances; QSet<QString> m_groups;
QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings; SettingsObjectPtr m_globalSettings;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers; QString m_instDir;
QFileSystemWatcher * m_watcher;
QMap<InstanceId, GroupId> m_groupMap;
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
}; };

View File

@@ -0,0 +1,9 @@
#include "InstanceTask.h"
InstanceTask::InstanceTask()
{
}
InstanceTask::~InstanceTask()
{
}

53
api/logic/InstanceTask.h Normal file
View 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;
};

View File

@@ -11,262 +11,262 @@ namespace Json
{ {
void write(const QJsonDocument &doc, const QString &filename) void write(const QJsonDocument &doc, const QString &filename)
{ {
FS::write(filename, doc.toJson()); FS::write(filename, doc.toJson());
} }
void write(const QJsonObject &object, const QString &filename) void write(const QJsonObject &object, const QString &filename)
{ {
write(QJsonDocument(object), filename); write(QJsonDocument(object), filename);
} }
void write(const QJsonArray &array, const QString &filename) void write(const QJsonArray &array, const QString &filename)
{ {
write(QJsonDocument(array), filename); write(QJsonDocument(array), filename);
} }
QByteArray toBinary(const QJsonObject &obj) QByteArray toBinary(const QJsonObject &obj)
{ {
return QJsonDocument(obj).toBinaryData(); return QJsonDocument(obj).toBinaryData();
} }
QByteArray toBinary(const QJsonArray &array) QByteArray toBinary(const QJsonArray &array)
{ {
return QJsonDocument(array).toBinaryData(); return QJsonDocument(array).toBinaryData();
} }
QByteArray toText(const QJsonObject &obj) QByteArray toText(const QJsonObject &obj)
{ {
return QJsonDocument(obj).toJson(QJsonDocument::Compact); return QJsonDocument(obj).toJson(QJsonDocument::Compact);
} }
QByteArray toText(const QJsonArray &array) QByteArray toText(const QJsonArray &array)
{ {
return QJsonDocument(array).toJson(QJsonDocument::Compact); return QJsonDocument(array).toJson(QJsonDocument::Compact);
} }
static bool isBinaryJson(const QByteArray &data) static bool isBinaryJson(const QByteArray &data)
{ {
decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag; decltype(QJsonDocument::BinaryFormatTag) tag = QJsonDocument::BinaryFormatTag;
return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0; return memcmp(data.constData(), &tag, sizeof(QJsonDocument::BinaryFormatTag)) == 0;
} }
QJsonDocument requireDocument(const QByteArray &data, const QString &what) QJsonDocument requireDocument(const QByteArray &data, const QString &what)
{ {
if (isBinaryJson(data)) if (isBinaryJson(data))
{ {
QJsonDocument doc = QJsonDocument::fromBinaryData(data); QJsonDocument doc = QJsonDocument::fromBinaryData(data);
if (doc.isNull()) if (doc.isNull())
{ {
throw JsonException(what + ": Invalid JSON (binary JSON detected)"); throw JsonException(what + ": Invalid JSON (binary JSON detected)");
} }
return doc; return doc;
} }
else else
{ {
QJsonParseError error; QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error); QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) if (error.error != QJsonParseError::NoError)
{ {
throw JsonException(what + ": Error parsing JSON: " + error.errorString()); throw JsonException(what + ": Error parsing JSON: " + error.errorString());
} }
return doc; return doc;
} }
} }
QJsonDocument requireDocument(const QString &filename, const QString &what) QJsonDocument requireDocument(const QString &filename, const QString &what)
{ {
return requireDocument(FS::read(filename), what); return requireDocument(FS::read(filename), what);
} }
QJsonObject requireObject(const QJsonDocument &doc, const QString &what) QJsonObject requireObject(const QJsonDocument &doc, const QString &what)
{ {
if (!doc.isObject()) if (!doc.isObject())
{ {
throw JsonException(what + " is not an object"); throw JsonException(what + " is not an object");
} }
return doc.object(); return doc.object();
} }
QJsonArray requireArray(const QJsonDocument &doc, const QString &what) QJsonArray requireArray(const QJsonDocument &doc, const QString &what)
{ {
if (!doc.isArray()) if (!doc.isArray())
{ {
throw JsonException(what + " is not an array"); throw JsonException(what + " is not an array");
} }
return doc.array(); return doc.array();
} }
void writeString(QJsonObject &to, const QString &key, const QString &value) void writeString(QJsonObject &to, const QString &key, const QString &value)
{ {
if (!value.isEmpty()) if (!value.isEmpty())
{ {
to.insert(key, value); to.insert(key, value);
} }
} }
void writeStringList(QJsonObject &to, const QString &key, const QStringList &values) void writeStringList(QJsonObject &to, const QString &key, const QStringList &values)
{ {
if (!values.isEmpty()) if (!values.isEmpty())
{ {
QJsonArray array; QJsonArray array;
for(auto value: values) for(auto value: values)
{ {
array.append(value); array.append(value);
} }
to.insert(key, array); to.insert(key, array);
} }
} }
template<> template<>
QJsonValue toJson<QUrl>(const QUrl &url) QJsonValue toJson<QUrl>(const QUrl &url)
{ {
return QJsonValue(url.toString(QUrl::FullyEncoded)); return QJsonValue(url.toString(QUrl::FullyEncoded));
} }
template<> template<>
QJsonValue toJson<QByteArray>(const QByteArray &data) QJsonValue toJson<QByteArray>(const QByteArray &data)
{ {
return QJsonValue(QString::fromLatin1(data.toHex())); return QJsonValue(QString::fromLatin1(data.toHex()));
} }
template<> template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime) QJsonValue toJson<QDateTime>(const QDateTime &datetime)
{ {
return QJsonValue(datetime.toString(Qt::ISODate)); return QJsonValue(datetime.toString(Qt::ISODate));
} }
template<> template<>
QJsonValue toJson<QDir>(const QDir &dir) QJsonValue toJson<QDir>(const QDir &dir)
{ {
return QDir::current().relativeFilePath(dir.absolutePath()); return QDir::current().relativeFilePath(dir.absolutePath());
} }
template<> template<>
QJsonValue toJson<QUuid>(const QUuid &uuid) QJsonValue toJson<QUuid>(const QUuid &uuid)
{ {
return uuid.toString(); return uuid.toString();
} }
template<> template<>
QJsonValue toJson<QVariant>(const QVariant &variant) QJsonValue toJson<QVariant>(const QVariant &variant)
{ {
return QJsonValue::fromVariant(variant); return QJsonValue::fromVariant(variant);
} }
template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what) template<> QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what)
{ {
const QString string = ensureIsType<QString>(value, what); const QString string = ensureIsType<QString>(value, what);
// ensure that the string can be safely cast to Latin1 // ensure that the string can be safely cast to Latin1
if (string != QString::fromLatin1(string.toLatin1())) if (string != QString::fromLatin1(string.toLatin1()))
{ {
throw JsonException(what + " is not encodable as Latin1"); throw JsonException(what + " is not encodable as Latin1");
} }
return QByteArray::fromHex(string.toLatin1()); return QByteArray::fromHex(string.toLatin1());
} }
template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what) template<> QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what)
{ {
if (!value.isArray()) if (!value.isArray())
{ {
throw JsonException(what + " is not an array"); throw JsonException(what + " is not an array");
} }
return value.toArray(); return value.toArray();
} }
template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what) template<> QString requireIsType<QString>(const QJsonValue &value, const QString &what)
{ {
if (!value.isString()) if (!value.isString())
{ {
throw JsonException(what + " is not a string"); throw JsonException(what + " is not a string");
} }
return value.toString(); return value.toString();
} }
template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what) template<> bool requireIsType<bool>(const QJsonValue &value, const QString &what)
{ {
if (!value.isBool()) if (!value.isBool())
{ {
throw JsonException(what + " is not a bool"); throw JsonException(what + " is not a bool");
} }
return value.toBool(); return value.toBool();
} }
template<> double requireIsType<double>(const QJsonValue &value, const QString &what) template<> double requireIsType<double>(const QJsonValue &value, const QString &what)
{ {
if (!value.isDouble()) if (!value.isDouble())
{ {
throw JsonException(what + " is not a double"); throw JsonException(what + " is not a double");
} }
return value.toDouble(); return value.toDouble();
} }
template<> int requireIsType<int>(const QJsonValue &value, const QString &what) template<> int requireIsType<int>(const QJsonValue &value, const QString &what)
{ {
const double doubl = requireIsType<double>(value, what); const double doubl = requireIsType<double>(value, what);
if (fmod(doubl, 1) != 0) if (fmod(doubl, 1) != 0)
{ {
throw JsonException(what + " is not an integer"); throw JsonException(what + " is not an integer");
} }
return int(doubl); return int(doubl);
} }
template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what) template<> QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what)
{ {
const QString string = requireIsType<QString>(value, what); const QString string = requireIsType<QString>(value, what);
const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate); const QDateTime datetime = QDateTime::fromString(string, Qt::ISODate);
if (!datetime.isValid()) if (!datetime.isValid())
{ {
throw JsonException(what + " is not a ISO formatted date/time value"); throw JsonException(what + " is not a ISO formatted date/time value");
} }
return datetime; return datetime;
} }
template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what) template<> QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what)
{ {
const QString string = ensureIsType<QString>(value, what); const QString string = ensureIsType<QString>(value, what);
if (string.isEmpty()) if (string.isEmpty())
{ {
return QUrl(); return QUrl();
} }
const QUrl url = QUrl(string, QUrl::StrictMode); const QUrl url = QUrl(string, QUrl::StrictMode);
if (!url.isValid()) if (!url.isValid())
{ {
throw JsonException(what + " is not a correctly formatted URL"); throw JsonException(what + " is not a correctly formatted URL");
} }
return url; return url;
} }
template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what) template<> QDir requireIsType<QDir>(const QJsonValue &value, const QString &what)
{ {
const QString string = requireIsType<QString>(value, what); const QString string = requireIsType<QString>(value, what);
// FIXME: does not handle invalid characters! // FIXME: does not handle invalid characters!
return QDir::current().absoluteFilePath(string); return QDir::current().absoluteFilePath(string);
} }
template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what) template<> QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what)
{ {
const QString string = requireIsType<QString>(value, what); const QString string = requireIsType<QString>(value, what);
const QUuid uuid = QUuid(string); const QUuid uuid = QUuid(string);
if (uuid.toString() != string) // converts back => valid if (uuid.toString() != string) // converts back => valid
{ {
throw JsonException(what + " is not a valid UUID"); throw JsonException(what + " is not a valid UUID");
} }
return uuid; return uuid;
} }
template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what) template<> QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what)
{ {
if (!value.isObject()) if (!value.isObject())
{ {
throw JsonException(what + " is not an object"); throw JsonException(what + " is not an object");
} }
return value.toObject(); return value.toObject();
} }
template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what) template<> QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what)
{ {
if (value.isNull() || value.isUndefined()) if (value.isNull() || value.isUndefined())
{ {
throw JsonException(what + " is null or undefined"); throw JsonException(what + " is null or undefined");
} }
return value.toVariant(); return value.toVariant();
} }
template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what) template<> QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what)
{ {
if (value.isNull() || value.isUndefined()) if (value.isNull() || value.isUndefined())
{ {
throw JsonException(what + " is null or undefined"); throw JsonException(what + " is null or undefined");
} }
return value; return value;
} }
} }

View File

@@ -19,7 +19,7 @@ namespace Json
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
{ {
public: public:
JsonException(const QString &message) : Exception(message) {} JsonException(const QString &message) : Exception(message) {}
}; };
/// @throw FileSystemException /// @throw FileSystemException
@@ -51,7 +51,7 @@ void writeStringList(QJsonObject & to, const QString &key, const QStringList &va
template<typename T> template<typename T>
QJsonValue toJson(const T &t) QJsonValue toJson(const T &t)
{ {
return QJsonValue(t); return QJsonValue(t);
} }
template<> template<>
QJsonValue toJson<QUrl>(const QUrl &url); QJsonValue toJson<QUrl>(const QUrl &url);
@@ -69,12 +69,12 @@ QJsonValue toJson<QVariant>(const QVariant &variant);
template<typename T> template<typename T>
QJsonArray toJsonArray(const QList<T> &container) QJsonArray toJsonArray(const QList<T> &container)
{ {
QJsonArray array; QJsonArray array;
for (const T item : container) for (const T item : container)
{ {
array.append(toJson<T>(item)); array.append(toJson<T>(item));
} }
return array; return array;
} }
////////////////// READING //////////////////// ////////////////// READING ////////////////////
@@ -115,119 +115,119 @@ template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value
template <typename T> template <typename T>
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value") T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{ {
if (value.isUndefined() || value.isNull()) if (value.isUndefined() || value.isNull())
{ {
return default_; return default_;
} }
try try
{ {
return requireIsType<T>(value, what); return requireIsType<T>(value, what);
} }
catch (JsonException &) catch (const JsonException &)
{ {
return default_; return default_;
} }
} }
/// @throw JsonException /// @throw JsonException
template <typename T> template <typename T>
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{ {
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) if (!parent.contains(key))
{ {
throw JsonException(localWhat + "s parent does not contain " + localWhat); throw JsonException(localWhat + "s parent does not contain " + localWhat);
} }
return requireIsType<T>(parent.value(key), localWhat); return requireIsType<T>(parent.value(key), localWhat);
} }
template <typename T> template <typename T>
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__") T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{ {
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) if (!parent.contains(key))
{ {
return default_; return default_;
} }
return ensureIsType<T>(parent.value(key), default_, localWhat); return ensureIsType<T>(parent.value(key), default_, localWhat);
} }
template <typename T> template <typename T>
QVector<T> requireIsArrayOf(const QJsonDocument &doc) QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{ {
const QJsonArray array = requireArray(doc); const QJsonArray array = requireArray(doc);
QVector<T> out; QVector<T> out;
for (const QJsonValue val : array) for (const QJsonValue val : array)
{ {
out.append(requireIsType<T>(val, "Document")); out.append(requireIsType<T>(val, "Document"));
} }
return out; return out;
} }
template <typename T> template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value") QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{ {
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what); const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out; QVector<T> out;
for (const QJsonValue val : array) for (const QJsonValue val : array)
{ {
out.append(requireIsType<T>(val, what)); out.append(requireIsType<T>(val, what));
} }
return out; return out;
} }
template <typename T> template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value") QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{ {
if (value.isUndefined()) if (value.isUndefined())
{ {
return default_; return default_;
} }
return ensureIsArrayOf<T>(value, what); return ensureIsArrayOf<T>(value, what);
} }
/// @throw JsonException /// @throw JsonException
template <typename T> template <typename T>
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{ {
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) if (!parent.contains(key))
{ {
throw JsonException(localWhat + "s parent does not contain " + localWhat); throw JsonException(localWhat + "s parent does not contain " + localWhat);
} }
return ensureIsArrayOf<T>(parent.value(key), localWhat); return ensureIsArrayOf<T>(parent.value(key), localWhat);
} }
template <typename T> template <typename T>
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key, QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__") const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
{ {
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\''); const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key)) if (!parent.contains(key))
{ {
return default_; return default_;
} }
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat); return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
} }
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers // this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \ #define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \ inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
{ \ { \
return requireIsType<TYPE>(value, what); \ return requireIsType<TYPE>(value, what); \
} \ } \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \ inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \ { \
return ensureIsType<TYPE>(value, default_, what); \ return ensureIsType<TYPE>(value, default_, what); \
} \ } \
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \ inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
{ \ { \
return requireIsType<TYPE>(parent, key, what); \ return requireIsType<TYPE>(parent, key, what); \
} \ } \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \ inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \ { \
return ensureIsType<TYPE>(parent, key, default_, what); \ return ensureIsType<TYPE>(parent, key, default_, what); \
} }
JSON_HELPERFUNCTIONS(Array, QJsonArray) JSON_HELPERFUNCTIONS(Array, QJsonArray)
JSON_HELPERFUNCTIONS(Object, QJsonObject) JSON_HELPERFUNCTIONS(Object, QJsonObject)

View File

@@ -4,157 +4,157 @@
LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
{ {
// QProcess has a strange interface... let's map a lot of those into a few. // QProcess has a strange interface... let's map a lot of those into a few.
connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut); connect(this, &QProcess::readyReadStandardOutput, this, &LoggedProcess::on_stdOut);
connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr); connect(this, &QProcess::readyReadStandardError, this, &LoggedProcess::on_stdErr);
connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus))); connect(this, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(on_exit(int,QProcess::ExitStatus)));
connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError))); connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(on_error(QProcess::ProcessError)));
connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange); connect(this, &QProcess::stateChanged, this, &LoggedProcess::on_stateChange);
} }
LoggedProcess::~LoggedProcess() LoggedProcess::~LoggedProcess()
{ {
if(m_is_detachable) if(m_is_detachable)
{ {
setProcessState(QProcess::NotRunning); setProcessState(QProcess::NotRunning);
} }
} }
QStringList reprocess(const QByteArray & data, QString & leftover) QStringList reprocess(const QByteArray & data, QString & leftover)
{ {
QString str = leftover + QString::fromLocal8Bit(data); QString str = leftover + QString::fromLocal8Bit(data);
str.remove('\r'); str.remove('\r');
QStringList lines = str.split("\n"); QStringList lines = str.split("\n");
leftover = lines.takeLast(); leftover = lines.takeLast();
return lines; return lines;
} }
void LoggedProcess::on_stdErr() void LoggedProcess::on_stdErr()
{ {
auto lines = reprocess(readAllStandardError(), m_err_leftover); auto lines = reprocess(readAllStandardError(), m_err_leftover);
emit log(lines, MessageLevel::StdErr); emit log(lines, MessageLevel::StdErr);
} }
void LoggedProcess::on_stdOut() void LoggedProcess::on_stdOut()
{ {
auto lines = reprocess(readAllStandardOutput(), m_out_leftover); auto lines = reprocess(readAllStandardOutput(), m_out_leftover);
emit log(lines, MessageLevel::StdOut); emit log(lines, MessageLevel::StdOut);
} }
void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status) void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status)
{ {
// save the exit code // save the exit code
m_exit_code = exit_code; m_exit_code = exit_code;
// Flush console window // Flush console window
if (!m_err_leftover.isEmpty()) if (!m_err_leftover.isEmpty())
{ {
emit log({m_err_leftover}, MessageLevel::StdErr); emit log({m_err_leftover}, MessageLevel::StdErr);
m_err_leftover.clear(); m_err_leftover.clear();
} }
if (!m_out_leftover.isEmpty()) if (!m_out_leftover.isEmpty())
{ {
emit log({m_err_leftover}, MessageLevel::StdOut); emit log({m_err_leftover}, MessageLevel::StdOut);
m_out_leftover.clear(); m_out_leftover.clear();
} }
// based on state, send signals // based on state, send signals
if (!m_is_aborting) if (!m_is_aborting)
{ {
if (status == QProcess::NormalExit) if (status == QProcess::NormalExit)
{ {
//: Message displayed on instance exit //: Message displayed on instance exit
emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC); emit log({tr("Process exited with code %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Finished); changeState(LoggedProcess::Finished);
} }
else else
{ {
//: Message displayed on instance crashed //: Message displayed on instance crashed
if(exit_code == -1) if(exit_code == -1)
emit log({tr("Process crashed.")}, MessageLevel::MultiMC); emit log({tr("Process crashed.")}, MessageLevel::MultiMC);
else else
emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC); emit log({tr("Process crashed with exitcode %1.").arg(exit_code)}, MessageLevel::MultiMC);
changeState(LoggedProcess::Crashed); changeState(LoggedProcess::Crashed);
} }
} }
else else
{ {
//: Message displayed after the instance exits due to kill request //: Message displayed after the instance exits due to kill request
emit log({tr("Process was killed by user.")}, MessageLevel::Error); emit log({tr("Process was killed by user.")}, MessageLevel::Error);
changeState(LoggedProcess::Aborted); changeState(LoggedProcess::Aborted);
} }
} }
void LoggedProcess::on_error(QProcess::ProcessError error) void LoggedProcess::on_error(QProcess::ProcessError error)
{ {
switch(error) switch(error)
{ {
case QProcess::FailedToStart: case QProcess::FailedToStart:
{ {
emit log({tr("The process failed to start.")}, MessageLevel::Fatal); emit log({tr("The process failed to start.")}, MessageLevel::Fatal);
changeState(LoggedProcess::FailedToStart); changeState(LoggedProcess::FailedToStart);
break; break;
} }
// we'll just ignore those... never needed them // we'll just ignore those... never needed them
case QProcess::Crashed: case QProcess::Crashed:
case QProcess::ReadError: case QProcess::ReadError:
case QProcess::Timedout: case QProcess::Timedout:
case QProcess::UnknownError: case QProcess::UnknownError:
case QProcess::WriteError: case QProcess::WriteError:
break; break;
} }
} }
void LoggedProcess::kill() void LoggedProcess::kill()
{ {
m_is_aborting = true; m_is_aborting = true;
QProcess::kill(); QProcess::kill();
} }
int LoggedProcess::exitCode() const int LoggedProcess::exitCode() const
{ {
return m_exit_code; return m_exit_code;
} }
void LoggedProcess::changeState(LoggedProcess::State state) void LoggedProcess::changeState(LoggedProcess::State state)
{ {
if(state == m_state) if(state == m_state)
return; return;
m_state = state; m_state = state;
emit stateChanged(m_state); emit stateChanged(m_state);
} }
LoggedProcess::State LoggedProcess::state() const LoggedProcess::State LoggedProcess::state() const
{ {
return m_state; return m_state;
} }
void LoggedProcess::on_stateChange(QProcess::ProcessState state) void LoggedProcess::on_stateChange(QProcess::ProcessState state)
{ {
switch(state) switch(state)
{ {
case QProcess::NotRunning: case QProcess::NotRunning:
break; // let's not - there are too many that handle this already. break; // let's not - there are too many that handle this already.
case QProcess::Starting: case QProcess::Starting:
{ {
if(m_state != LoggedProcess::NotRunning) if(m_state != LoggedProcess::NotRunning)
{ {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting; qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Starting;
} }
changeState(LoggedProcess::Starting); changeState(LoggedProcess::Starting);
return; return;
} }
case QProcess::Running: case QProcess::Running:
{ {
if(m_state != LoggedProcess::Starting) if(m_state != LoggedProcess::Starting)
{ {
qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running; qWarning() << "Wrong state change for process from state" << m_state << "to" << (int) LoggedProcess::Running;
} }
changeState(LoggedProcess::Running); changeState(LoggedProcess::Running);
return; return;
} }
} }
} }
#if defined Q_OS_WIN32 #if defined Q_OS_WIN32
@@ -172,5 +172,5 @@ qint64 LoggedProcess::processId() const
void LoggedProcess::setDetachable(bool detachable) void LoggedProcess::setDetachable(bool detachable)
{ {
m_is_detachable = detachable; m_is_detachable = detachable;
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,54 +27,54 @@ class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
{ {
Q_OBJECT Q_OBJECT
public: public:
enum State enum State
{ {
NotRunning, NotRunning,
Starting, Starting,
FailedToStart, FailedToStart,
Running, Running,
Finished, Finished,
Crashed, Crashed,
Aborted Aborted
}; };
public: public:
explicit LoggedProcess(QObject* parent = 0); explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess(); virtual ~LoggedProcess();
State state() const; State state() const;
int exitCode() const; int exitCode() const;
qint64 processId() const; qint64 processId() const;
void setDetachable(bool detachable); void setDetachable(bool detachable);
signals: signals:
void log(QStringList lines, MessageLevel::Enum level); void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state); void stateChanged(LoggedProcess::State state);
public slots: public slots:
/** /**
* @brief kill the process - equivalent to kill -9 * @brief kill the process - equivalent to kill -9
*/ */
void kill(); void kill();
private slots: private slots:
void on_stdErr(); void on_stdErr();
void on_stdOut(); void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status); void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error); void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState); void on_stateChange(QProcess::ProcessState);
private: private:
void changeState(LoggedProcess::State state); void changeState(LoggedProcess::State state);
private: private:
QString m_err_leftover; QString m_err_leftover;
QString m_out_leftover; QString m_out_leftover;
bool m_killed = false; bool m_killed = false;
State m_state = NotRunning; State m_state = NotRunning;
int m_exit_code = 0; int m_exit_code = 0;
bool m_is_aborting = false; bool m_is_aborting = false;
bool m_is_detachable = false; bool m_is_detachable = false;
}; };

View File

@@ -3,74 +3,74 @@
/// TAKEN FROM Qt, because it doesn't expose it intelligently /// TAKEN FROM Qt, because it doesn't expose it intelligently
static inline QChar getNextChar(const QString &s, int location) static inline QChar getNextChar(const QString &s, int location)
{ {
return (location < s.length()) ? s.at(location) : QChar(); return (location < s.length()) ? s.at(location) : QChar();
} }
/// TAKEN FROM Qt, because it doesn't expose it intelligently /// TAKEN FROM Qt, because it doesn't expose it intelligently
int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
{ {
for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
{ {
// skip spaces, tabs and 0's // skip spaces, tabs and 0's
QChar c1 = getNextChar(s1, l1); QChar c1 = getNextChar(s1, l1);
while (c1.isSpace()) while (c1.isSpace())
c1 = getNextChar(s1, ++l1); c1 = getNextChar(s1, ++l1);
QChar c2 = getNextChar(s2, l2); QChar c2 = getNextChar(s2, l2);
while (c2.isSpace()) while (c2.isSpace())
c2 = getNextChar(s2, ++l2); c2 = getNextChar(s2, ++l2);
if (c1.isDigit() && c2.isDigit()) if (c1.isDigit() && c2.isDigit())
{ {
while (c1.digitValue() == 0) while (c1.digitValue() == 0)
c1 = getNextChar(s1, ++l1); c1 = getNextChar(s1, ++l1);
while (c2.digitValue() == 0) while (c2.digitValue() == 0)
c2 = getNextChar(s2, ++l2); c2 = getNextChar(s2, ++l2);
int lookAheadLocation1 = l1; int lookAheadLocation1 = l1;
int lookAheadLocation2 = l2; int lookAheadLocation2 = l2;
int currentReturnValue = 0; int currentReturnValue = 0;
// find the last digit, setting currentReturnValue as we go if it isn't equal // find the last digit, setting currentReturnValue as we go if it isn't equal
for (QChar lookAhead1 = c1, lookAhead2 = c2; for (QChar lookAhead1 = c1, lookAhead2 = c2;
(lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
{ {
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
if (!is1ADigit && !is2ADigit) if (!is1ADigit && !is2ADigit)
break; break;
if (!is1ADigit) if (!is1ADigit)
return -1; return -1;
if (!is2ADigit) if (!is2ADigit)
return 1; return 1;
if (currentReturnValue == 0) if (currentReturnValue == 0)
{ {
if (lookAhead1 < lookAhead2) if (lookAhead1 < lookAhead2)
{ {
currentReturnValue = -1; currentReturnValue = -1;
} }
else if (lookAhead1 > lookAhead2) else if (lookAhead1 > lookAhead2)
{ {
currentReturnValue = 1; currentReturnValue = 1;
} }
} }
} }
if (currentReturnValue != 0) if (currentReturnValue != 0)
return currentReturnValue; return currentReturnValue;
} }
if (cs == Qt::CaseInsensitive) if (cs == Qt::CaseInsensitive)
{ {
if (!c1.isLower()) if (!c1.isLower())
c1 = c1.toLower(); c1 = c1.toLower();
if (!c2.isLower()) if (!c2.isLower())
c2 = c2.toLower(); c2 = c2.toLower();
} }
int r = QString::localeAwareCompare(c1, c2); int r = QString::localeAwareCompare(c1, c2);
if (r < 0) if (r < 0)
return -1; return -1;
if (r > 0) if (r > 0)
return 1; return 1;
} }
// The two strings are the same (02 == 2) so fall back to the normal sort // The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs); return QString::compare(s1, s2, cs);
} }

View File

@@ -6,5 +6,5 @@
namespace Strings namespace Strings
{ {
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs); int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
} }

View File

@@ -1,491 +1,255 @@
/* /* Copyright 2013-2019 MultiMC Contributors
Copyright (C) 2010 Roberto Pompermaier *
Copyright (C) 2005-2014 Sergey A. Tachenov * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Parts of this file were part of QuaZIP. * You may obtain a copy of the License at
*
QuaZIP is free software: you can redistribute it and/or modify * http://www.apache.org/licenses/LICENSE-2.0
it under the terms of the GNU Lesser General Public License as published by *
the Free Software Foundation, either version 2.1 of the License, or * Unless required by applicable law or agreed to in writing, software
(at your option) any later version. * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
QuaZIP is distributed in the hope that it will be useful, * See the License for the specific language governing permissions and
but WITHOUT ANY WARRANTY; without even the implied warranty of * limitations under the License.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with QuaZIP. If not, see <http://www.gnu.org/licenses/>.
See COPYING file for the full LGPL text.
Original ZIP package is copyrighted by Gilles Vollant and contributors,
see quazip/(un)MMCZip.h files for details. Basically it's the zlib license.
*/
#include <quazip.h> #include <quazip.h>
#include <JlCompress.h>
#include <quazipdir.h> #include <quazipdir.h>
#include <quazipfile.h>
#include <JlCompress.h>
#include "MMCZip.h" #include "MMCZip.h"
#include "FileSystem.h" #include "FileSystem.h"
#include <QDebug> #include <QDebug>
bool copyData(QIODevice &inFile, QIODevice &outFile) // ours
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, const JlCompress::FilterFunction filter)
{ {
while (!inFile.atEnd()) QuaZip modZip(from.filePath());
{ modZip.open(QuaZip::mdUnzip);
char buf[4096];
qint64 readLen = inFile.read(buf, 4096); QuaZipFile fileInsideMod(&modZip);
if (readLen <= 0) QuaZipFile zipOutFile(into);
return false; for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
if (outFile.write(buf, readLen) != readLen) {
return false; QString filename = modZip.getCurrentFileName();
} if (filter && !filter(filename))
return true; {
} qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
QStringList MMCZip::extractDir(QString fileCompressed, QString dir) continue;
{ }
return JlCompress::extractDir(fileCompressed, dir); if (contained.contains(filename))
} {
qDebug() << "Skipping already contained file " << filename << " from "
bool compressFile(QuaZip *zip, QString fileName, QString fileDest) << from.fileName();
{ continue;
if (!zip) }
{ contained.insert(filename);
return false;
} if (!fileInsideMod.open(QIODevice::ReadOnly))
if (zip->getMode() != QuaZip::mdCreate && zip->getMode() != QuaZip::mdAppend && {
zip->getMode() != QuaZip::mdAdd) qCritical() << "Failed to open " << filename << " from " << from.fileName();
{ return false;
return false; }
}
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
QFile inFile;
inFile.setFileName(fileName); if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
if (!inFile.open(QIODevice::ReadOnly)) {
{ qCritical() << "Failed to open " << filename << " in the jar";
return false; fileInsideMod.close();
} return false;
}
QuaZipFile outFile(zip); if (!JlCompress::copyData(fileInsideMod, zipOutFile))
if (!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName()))) {
{ zipOutFile.close();
return false; fileInsideMod.close();
} qCritical() << "Failed to copy data of " << filename << " into the jar";
return false;
if (!copyData(inFile, outFile) || outFile.getZipError() != UNZ_OK) }
{ zipOutFile.close();
return false; fileInsideMod.close();
} }
return true;
outFile.close();
if (outFile.getZipError() != UNZ_OK)
{
return false;
}
inFile.close();
return true;
}
bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QString>& added, QString prefix, const SeparatorPrefixTree <'/'> * blacklist)
{
if (!zip) return false;
if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd)
{
return false;
}
QDir directory(dir);
if (!directory.exists())
{
return false;
}
QDir origDirectory(origDir);
if (dir != origDir)
{
QString internalDirName = origDirectory.relativeFilePath(dir);
if(!blacklist || !blacklist->covers(internalDirName))
{
QuaZipFile dirZipFile(zip);
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
{
return false;
}
dirZipFile.close();
}
}
QFileInfoList files = directory.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
for (auto file: files)
{
if(!file.isDir())
{
continue;
}
if(!compressSubDir(zip,file.absoluteFilePath(),origDir, added, prefix, blacklist))
{
return false;
}
}
files = directory.entryInfoList(QDir::Files);
for (auto file: files)
{
if(!file.isFile())
{
continue;
}
if(file.absoluteFilePath()==zip->getZipName())
{
continue;
}
QString filename = origDirectory.relativeFilePath(file.absoluteFilePath());
if(blacklist && blacklist->covers(filename))
{
continue;
}
if(prefix.size())
{
filename = FS::PathCombine(prefix, filename);
}
added.insert(filename);
if (!compressFile(zip,file.absoluteFilePath(),filename))
{
return false;
}
}
return true;
}
bool MMCZip::mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
std::function<bool(QString)> filter)
{
QuaZip modZip(from.filePath());
modZip.open(QuaZip::mdUnzip);
QuaZipFile fileInsideMod(&modZip);
QuaZipFile zipOutFile(into);
for (bool more = modZip.goToFirstFile(); more; more = modZip.goToNextFile())
{
QString filename = modZip.getCurrentFileName();
if (!filter(filename))
{
qDebug() << "Skipping file " << filename << " from "
<< from.fileName() << " - filtered";
continue;
}
if (contained.contains(filename))
{
qDebug() << "Skipping already contained file " << filename << " from "
<< from.fileName();
continue;
}
contained.insert(filename);
if (!fileInsideMod.open(QIODevice::ReadOnly))
{
qCritical() << "Failed to open " << filename << " from " << from.fileName();
return false;
}
QuaZipNewInfo info_out(fileInsideMod.getActualFileName());
if (!zipOutFile.open(QIODevice::WriteOnly, info_out))
{
qCritical() << "Failed to open " << filename << " in the jar";
fileInsideMod.close();
return false;
}
if (!copyData(fileInsideMod, zipOutFile))
{
zipOutFile.close();
fileInsideMod.close();
qCritical() << "Failed to copy data of " << filename << " into the jar";
return false;
}
zipOutFile.close();
fileInsideMod.close();
}
return true;
} }
// ours
bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods) bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods)
{ {
QuaZip zipOut(targetJarPath); QuaZip zipOut(targetJarPath);
if (!zipOut.open(QuaZip::mdCreate)) if (!zipOut.open(QuaZip::mdCreate))
{ {
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to open the minecraft.jar for modding"; qCritical() << "Failed to open the minecraft.jar for modding";
return false; return false;
} }
// Files already added to the jar. // Files already added to the jar.
// These files will be skipped. // These files will be skipped.
QSet<QString> addedFiles; QSet<QString> addedFiles;
// Modify the jar // Modify the jar
QListIterator<Mod> i(mods); QListIterator<Mod> i(mods);
i.toBack(); i.toBack();
while (i.hasPrevious()) while (i.hasPrevious())
{ {
const Mod &mod = i.previous(); const Mod &mod = i.previous();
// do not merge disabled mods. // do not merge disabled mods.
if (!mod.enabled()) if (!mod.enabled())
continue; continue;
if (mod.type() == Mod::MOD_ZIPFILE) if (mod.type() == Mod::MOD_ZIPFILE)
{ {
if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles, noFilter)) if (!mergeZipFiles(&zipOut, mod.filename(), addedFiles))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
return false; return false;
} }
} }
else if (mod.type() == Mod::MOD_SINGLEFILE) else if (mod.type() == Mod::MOD_SINGLEFILE)
{ {
auto filename = mod.filename(); // FIXME: buggy - does not work with addedFiles
if (!compressFile(&zipOut, filename.absoluteFilePath(), auto filename = mod.filename();
filename.fileName())) if (!JlCompress::compressFile(&zipOut, filename.absoluteFilePath(), filename.fileName()))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
return false; return false;
} }
addedFiles.insert(filename.fileName()); addedFiles.insert(filename.fileName());
} }
else if (mod.type() == Mod::MOD_FOLDER) else if (mod.type() == Mod::MOD_FOLDER)
{ {
auto filename = mod.filename(); // FIXME: buggy - does not work with addedFiles
QString what_to_zip = filename.absoluteFilePath(); auto filename = mod.filename();
QDir dir(what_to_zip); QString what_to_zip = filename.absoluteFilePath();
dir.cdUp(); QDir dir(what_to_zip);
QString parent_dir = dir.absolutePath(); dir.cdUp();
if (!compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles)) QString parent_dir = dir.absolutePath();
{ if (!JlCompress::compressSubDir(&zipOut, what_to_zip, parent_dir, addedFiles))
zipOut.close(); {
QFile::remove(targetJarPath); zipOut.close();
qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar."; QFile::remove(targetJarPath);
return false; qCritical() << "Failed to add" << mod.filename().fileName() << "to the jar.";
} return false;
qDebug() << "Adding folder " << filename.fileName() << " from " }
<< filename.absoluteFilePath(); qDebug() << "Adding folder " << filename.fileName() << " from "
} << filename.absoluteFilePath();
} }
else
{
// Make sure we do not continue launching when something is missing or undefined...
zipOut.close();
QFile::remove(targetJarPath);
qCritical() << "Failed to add unknown mod type" << mod.filename().fileName() << "to the jar.";
return false;
}
}
if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, metaInfFilter)) if (!mergeZipFiles(&zipOut, QFileInfo(sourceJarPath), addedFiles, [](const QString key){return !key.contains("META-INF");}))
{ {
zipOut.close(); zipOut.close();
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to insert minecraft.jar contents."; qCritical() << "Failed to insert minecraft.jar contents.";
return false; return false;
} }
// Recompress the jar // Recompress the jar
zipOut.close(); zipOut.close();
if (zipOut.getZipError() != 0) if (zipOut.getZipError() != 0)
{ {
QFile::remove(targetJarPath); QFile::remove(targetJarPath);
qCritical() << "Failed to finalize minecraft.jar!"; qCritical() << "Failed to finalize minecraft.jar!";
return false; return false;
} }
return true; return true;
} }
bool MMCZip::noFilter(QString) // ours
QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
{ {
return true; QuaZipDir rootDir(zip, root);
} for(auto fileName: rootDir.entryList(QDir::Files))
{
bool MMCZip::metaInfFilter(QString key) if(fileName == what)
{ return root;
if(key.contains("META-INF")) }
{ for(auto fileName: rootDir.entryList(QDir::Dirs))
return false; {
} QString result = findFolderOfFileInZip(zip, what, root + fileName);
return true; if(!result.isEmpty())
} {
return result;
bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const SeparatorPrefixTree <'/'> * blacklist) }
{ }
QuaZip zip(zipFile); return QString();
QDir().mkpath(QFileInfo(zipFile).absolutePath());
if(!zip.open(QuaZip::mdCreate))
{
QFile::remove(zipFile);
return false;
}
QSet<QString> added;
if (!compressSubDir(&zip, dir, dir, added, prefix, blacklist))
{
QFile::remove(zipFile);
return false;
}
zip.close();
if(zip.getZipError()!=0)
{
QFile::remove(zipFile);
return false;
}
return true;
}
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
return root;
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
QString result = findFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
}
}
return QString();
} }
// ours
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root) bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{ {
QuaZipDir rootDir(zip, root); QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files)) for(auto fileName: rootDir.entryList(QDir::Files))
{ {
if(fileName == what) if(fileName == what)
{ {
result.append(root); result.append(root);
return true; return true;
} }
} }
for(auto fileName: rootDir.entryList(QDir::Dirs)) for(auto fileName: rootDir.entryList(QDir::Dirs))
{ {
findFilesInZip(zip, what, result, root + fileName); findFilesInZip(zip, what, result, root + fileName);
} }
return !result.isEmpty(); return !result.isEmpty();
} }
bool removeFile(QStringList listFile)
{
bool ret = true;
for (int i = 0; i < listFile.count(); i++)
{
ret &= QFile::remove(listFile.at(i));
}
return ret;
}
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
{
if(!zip)
return false;
if (zip->getMode() != QuaZip::mdUnzip)
return false;
if (!fileName.isEmpty())
zip->setCurrentFile(fileName);
QuaZipFile inFile(zip);
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
return false;
// Controllo esistenza cartella file risultato
QDir curDir;
if (fileDest.endsWith('/'))
{
if (!curDir.mkpath(fileDest))
{
return false;
}
}
else
{
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
{
return false;
}
}
QuaZipFileInfo64 info;
if (!zip->getCurrentFileInfo(&info))
return false;
QFile::Permissions srcPerm = info.getPermissions();
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
{
if (srcPerm != 0)
{
QFile(fileDest).setPermissions(srcPerm);
}
return true;
}
QFile outFile;
outFile.setFileName(fileDest);
if (!outFile.open(QIODevice::WriteOnly))
return false;
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
{
outFile.close();
removeFile(QStringList(fileDest));
return false;
}
outFile.close();
inFile.close();
if (inFile.getZipError() != UNZ_OK)
{
removeFile(QStringList(fileDest));
return false;
}
if (srcPerm != 0)
{
outFile.setPermissions(srcPerm);
}
return true;
}
// ours
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{ {
QDir directory(target); QDir directory(target);
QStringList extracted; QStringList extracted;
if (!zip->goToFirstFile()) qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
{ if (!zip->goToFirstFile())
return QStringList(); {
} qWarning() << "Failed to seek to first file in zip";
do return QStringList();
{ }
QString name = zip->getCurrentFileName(); do
if(!name.startsWith(subdir)) {
{ QString name = zip->getCurrentFileName();
continue; if(!name.startsWith(subdir))
} {
name.remove(0, subdir.size()); continue;
QString absFilePath = directory.absoluteFilePath(name); }
if(name.isEmpty()) name.remove(0, subdir.size());
{ QString absFilePath = directory.absoluteFilePath(name);
absFilePath += "/"; if(name.isEmpty())
} {
if (!extractFile(zip, "", absFilePath)) absFilePath += "/";
{ }
removeFile(extracted); if (!JlCompress::extractFile(zip, "", absFilePath))
return QStringList(); {
} qWarning() << "Failed to extract file" << name << "to" << absFilePath;
extracted.append(absFilePath); JlCompress::removeFile(extracted);
} while (zip->goToNextFile()); return QStringList();
return extracted; }
extracted.append(absFilePath);
qDebug() << "Extracted file" << name;
} while (zip->goToNextFile());
return extracted;
}
// ours
QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
{
return {};
}
return MMCZip::extractSubDir(&zip, "", dir);
} }

View File

@@ -1,88 +1,71 @@
/* Copyright 2013-2019 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once #pragma once
#include <QString> #include <QString>
#include <QFileInfo> #include <QFileInfo>
#include <QSet> #include <QSet>
#include "minecraft/Mod.h" #include "minecraft/Mod.h"
#include "SeparatorPrefixTree.h"
#include <functional> #include <functional>
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class QuaZip; #include <JlCompress.h>
namespace MMCZip namespace MMCZip
{ {
/** /**
* Compress a subdirectory. * Merge two zip files, using a filter function
* \param parentZip Opened zip containing the parent directory.
* \param dir The full path to the directory to pack.
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
* \param recursive Whether to pack sub-directories as well or only files.
* \return true if success, false otherwise.
*/ */
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added, bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained,
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr); const JlCompress::FilterFunction filter = nullptr);
/** /**
* Compress a whole directory. * take a source jar, add mods to it, resulting in target jar
* \param fileCompressed The name of the archive. */
* \param dir The directory to compress. bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
* \param recursive Whether to pack the subdirectories as well, or just regular files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/// filter function for @mergeZipFiles - passthrough /**
bool MULTIMC_LOGIC_EXPORT noFilter(QString key); * Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
/// filter function for @mergeZipFiles - ignores METAINF /**
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key); * Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/** /**
* Merge two zip files, using a filter function * Extract a subdirectory from an archive
*/ */
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter); QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
/** /**
* take a source jar, add mods to it, resulting in target jar * Extract a whole archive.
*/ *
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods); * \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if
* left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
/**
* Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a single file to a destination
*
* \return true if it succeeds
*/
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
/**
* Extract a subdirectory from an archive
*/
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
} }

View File

@@ -2,35 +2,35 @@
MessageLevel::Enum MessageLevel::getLevel(const QString& levelName) MessageLevel::Enum MessageLevel::getLevel(const QString& levelName)
{ {
if (levelName == "MultiMC") if (levelName == "MultiMC")
return MessageLevel::MultiMC; return MessageLevel::MultiMC;
else if (levelName == "Debug") else if (levelName == "Debug")
return MessageLevel::Debug; return MessageLevel::Debug;
else if (levelName == "Info") else if (levelName == "Info")
return MessageLevel::Info; return MessageLevel::Info;
else if (levelName == "Message") else if (levelName == "Message")
return MessageLevel::Message; return MessageLevel::Message;
else if (levelName == "Warning") else if (levelName == "Warning")
return MessageLevel::Warning; return MessageLevel::Warning;
else if (levelName == "Error") else if (levelName == "Error")
return MessageLevel::Error; return MessageLevel::Error;
else if (levelName == "Fatal") else if (levelName == "Fatal")
return MessageLevel::Fatal; return MessageLevel::Fatal;
// Skip PrePost, it's not exposed to !![]! // Skip PrePost, it's not exposed to !![]!
// Also skip StdErr and StdOut // Also skip StdErr and StdOut
else else
return MessageLevel::Unknown; return MessageLevel::Unknown;
} }
MessageLevel::Enum MessageLevel::fromLine(QString &line) MessageLevel::Enum MessageLevel::fromLine(QString &line)
{ {
// Level prefix // Level prefix
int endmark = line.indexOf("]!"); int endmark = line.indexOf("]!");
if (line.startsWith("!![") && endmark != -1) if (line.startsWith("!![") && endmark != -1)
{ {
auto level = MessageLevel::getLevel(line.left(endmark).mid(3)); auto level = MessageLevel::getLevel(line.left(endmark).mid(3));
line = line.mid(endmark + 2); line = line.mid(endmark + 2);
return level; return level;
} }
return MessageLevel::Unknown; return MessageLevel::Unknown;
} }

View File

@@ -10,16 +10,16 @@ namespace MessageLevel
{ {
enum Enum enum Enum
{ {
Unknown, /**< No idea what this is or where it came from */ Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */ StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */ StdErr, /**< Undetermined stdout messages */
MultiMC, /**< MultiMC Messages */ MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */ Debug, /**< Debug Messages */
Info, /**< Info Messages */ Info, /**< Info Messages */
Message, /**< Standard Messages */ Message, /**< Standard Messages */
Warning, /**< Warnings */ Warning, /**< Warnings */
Error, /**< Errors */ Error, /**< Errors */
Fatal, /**< Fatal Errors */ Fatal, /**< Fatal Errors */
}; };
MessageLevel::Enum getLevel(const QString &levelName); MessageLevel::Enum getLevel(const QString &levelName);

View File

@@ -1,93 +1,76 @@
#pragma once #pragma once
#include "BaseInstance.h" #include "BaseInstance.h"
#include "launch/LaunchTask.h"
class NullInstance: public BaseInstance class NullInstance: public BaseInstance
{ {
Q_OBJECT
public: public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir) NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir) :BaseInstance(globalSettings, settings, rootDir)
{ {
setVersionBroken(true); setVersionBroken(true);
} }
virtual ~NullInstance() {}; virtual ~NullInstance() {};
virtual bool setIntendedVersionId(QString) override void saveNow() override
{ {
return false; }
} QString getStatusbarDescription() override
virtual QString currentVersionId() const override {
{ return tr("Unknown instance type");
return "Null"; };
}; QSet< QString > traits() const override
virtual QString intendedVersionId() const override {
{ return {};
return "Null"; };
}; QString instanceConfigFolder() const override
virtual void init() override {
{ return instanceRoot();
}; };
virtual QString getStatusbarDescription() override shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
{ {
return tr("Unknown instance type"); return nullptr;
}; }
virtual bool shouldUpdate() const override shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
{ {
return false; return nullptr;
}; }
virtual QSet< QString > traits() override QProcessEnvironment createEnvironment() override
{ {
return {}; return QProcessEnvironment();
}; }
virtual QString instanceConfigFolder() const override QMap<QString, QString> getVariables() const override
{ {
return instanceRoot(); return QMap<QString, QString>();
}; }
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override IPathMatcher::Ptr getLogFileMatcher() override
{ {
return nullptr; return nullptr;
} }
virtual shared_qobject_ptr< Task > createUpdateTask() override QString getLogFileRoot() override
{ {
return nullptr; return instanceRoot();
} }
virtual std::shared_ptr<Task> createJarModdingTask() override QString typeName() const override
{ {
return nullptr; return "Null";
} }
virtual void setShouldUpdate(bool) override bool canExport() const override
{ {
}; return false;
virtual std::shared_ptr< BaseVersionList > versionList() const override }
{ bool canEdit() const override
return nullptr; {
}; return false;
virtual QProcessEnvironment createEnvironment() override }
{ bool canLaunch() const override
return QProcessEnvironment(); {
} return false;
virtual QMap<QString, QString> getVariables() const override }
{ QStringList verboseDescription(AuthSessionPtr session) override
return QMap<QString, QString>(); {
} QStringList out;
virtual IPathMatcher::Ptr getLogFileMatcher() override out << "Null instance - placeholder.";
{ return out;
return nullptr; }
}
virtual QString getLogFileRoot() override
{
return instanceRoot();
}
virtual QString typeName() const override
{
return "Null";
}
bool canExport() const override
{
return false;
}
QStringList verboseDescription(AuthSessionPtr session) override
{
QStringList out;
out << "Null instance - placeholder.";
return out;
}
}; };

View File

@@ -0,0 +1,49 @@
#pragma once
#include "multimc_logic_export.h"
enum class ProblemSeverity
{
None,
Warning,
Error
};
struct PatchProblem
{
ProblemSeverity m_severity;
QString m_description;
};
class MULTIMC_LOGIC_EXPORT ProblemProvider
{
public:
virtual ~ProblemProvider() {};
virtual const QList<PatchProblem> getProblems() const = 0;
virtual ProblemSeverity getProblemSeverity() const = 0;
};
class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
{
public:
const QList<PatchProblem> getProblems() const override
{
return m_problems;
}
ProblemSeverity getProblemSeverity() const override
{
return m_problemSeverity;
}
virtual void addProblem(ProblemSeverity severity, const QString &description)
{
if(severity > m_problemSeverity)
{
m_problemSeverity = severity;
}
m_problems.append({severity, description});
}
private:
QList<PatchProblem> m_problems;
ProblemSeverity m_problemSeverity = ProblemSeverity::None;
};

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <functional>
#include <memory> #include <memory>
#include <QObject> #include <QObject>
@@ -7,10 +8,10 @@ namespace details
{ {
struct DeleteQObjectLater struct DeleteQObjectLater
{ {
void operator()(QObject *obj) const void operator()(QObject *obj) const
{ {
obj->deleteLater(); obj->deleteLater();
} }
}; };
} }
/** /**
@@ -27,56 +28,56 @@ template <typename T>
class shared_qobject_ptr class shared_qobject_ptr
{ {
public: public:
shared_qobject_ptr(){} shared_qobject_ptr(){}
shared_qobject_ptr(T * wrap) shared_qobject_ptr(T * wrap)
{ {
reset(wrap); reset(wrap);
} }
shared_qobject_ptr(const shared_qobject_ptr<T>& other) shared_qobject_ptr(const shared_qobject_ptr<T>& other)
{ {
m_ptr = other.m_ptr; m_ptr = other.m_ptr;
} }
template<typename Derived> template<typename Derived>
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other) shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
{ {
m_ptr = other.unwrap(); m_ptr = other.unwrap();
} }
public: public:
void reset(T * wrap) void reset(T * wrap)
{ {
using namespace std::placeholders; using namespace std::placeholders;
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
} }
void reset(const shared_qobject_ptr<T> &other) void reset(const shared_qobject_ptr<T> &other)
{ {
m_ptr = other.m_ptr; m_ptr = other.m_ptr;
} }
void reset() void reset()
{ {
m_ptr.reset(); m_ptr.reset();
} }
T * get() const T * get() const
{ {
return m_ptr.get(); return m_ptr.get();
} }
T * operator->() const T * operator->() const
{ {
return m_ptr.get(); return m_ptr.get();
} }
T & operator*() const T & operator*() const
{ {
return *m_ptr.get(); return *m_ptr.get();
} }
operator bool() const operator bool() const
{ {
return m_ptr.get() != nullptr; return m_ptr.get() != nullptr;
} }
const std::shared_ptr <T> unwrap() const const std::shared_ptr <T> unwrap() const
{ {
return m_ptr; return m_ptr;
} }
private: private:
std::shared_ptr <T> m_ptr; std::shared_ptr <T> m_ptr;
}; };

View File

@@ -1,60 +1,63 @@
#pragma once #pragma once
#include <QWriteLocker>
#include <QReadLocker>
template <typename K, typename V> template <typename K, typename V>
class RWStorage class RWStorage
{ {
public: public:
void add(K key, V value) void add(K key, V value)
{ {
QWriteLocker l(&lock); QWriteLocker l(&lock);
cache[key] = value; cache[key] = value;
stale_entries.remove(key); stale_entries.remove(key);
} }
V get(K key) V get(K key)
{ {
QReadLocker l(&lock); QReadLocker l(&lock);
if(cache.contains(key)) if(cache.contains(key))
{ {
return cache[key]; return cache[key];
} }
else return V(); else return V();
} }
bool get(K key, V& value) bool get(K key, V& value)
{ {
QReadLocker l(&lock); QReadLocker l(&lock);
if(cache.contains(key)) if(cache.contains(key))
{ {
value = cache[key]; value = cache[key];
return true; return true;
} }
else return false; else return false;
} }
bool has(K key) bool has(K key)
{ {
QReadLocker l(&lock); QReadLocker l(&lock);
return cache.contains(key); return cache.contains(key);
} }
bool stale(K key) bool stale(K key)
{ {
QReadLocker l(&lock); QReadLocker l(&lock);
if(!cache.contains(key)) if(!cache.contains(key))
return true; return true;
return stale_entries.contains(key); return stale_entries.contains(key);
} }
void setStale(K key) void setStale(K key)
{ {
QReadLocker l(&lock); QWriteLocker l(&lock);
if(cache.contains(key)) if(cache.contains(key))
{ {
stale_entries.insert(key); stale_entries.insert(key);
} }
} }
void clear() void clear()
{ {
QWriteLocker l(&lock); QWriteLocker l(&lock);
cache.clear(); cache.clear();
} stale_entries.clear();
}
private: private:
QReadWriteLock lock; QReadWriteLock lock;
QMap<K, V> cache; QMap<K, V> cache;
QSet<K> stale_entries; QSet<K> stale_entries;
}; };

View File

@@ -4,108 +4,108 @@
#include <QDebug> #include <QDebug>
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent) RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent)
: QObject(parent), m_watcher(new QFileSystemWatcher(this)) : QObject(parent), m_watcher(new QFileSystemWatcher(this))
{ {
connect(m_watcher, &QFileSystemWatcher::fileChanged, this, connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
&RecursiveFileSystemWatcher::fileChange); &RecursiveFileSystemWatcher::fileChange);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, connect(m_watcher, &QFileSystemWatcher::directoryChanged, this,
&RecursiveFileSystemWatcher::directoryChange); &RecursiveFileSystemWatcher::directoryChange);
} }
void RecursiveFileSystemWatcher::setRootDir(const QDir &root) void RecursiveFileSystemWatcher::setRootDir(const QDir &root)
{ {
bool wasEnabled = m_isEnabled; bool wasEnabled = m_isEnabled;
disable(); disable();
m_root = root; m_root = root;
setFiles(scanRecursive(m_root)); setFiles(scanRecursive(m_root));
if (wasEnabled) if (wasEnabled)
{ {
enable(); enable();
} }
} }
void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles) void RecursiveFileSystemWatcher::setWatchFiles(const bool watchFiles)
{ {
bool wasEnabled = m_isEnabled; bool wasEnabled = m_isEnabled;
disable(); disable();
m_watchFiles = watchFiles; m_watchFiles = watchFiles;
if (wasEnabled) if (wasEnabled)
{ {
enable(); enable();
} }
} }
void RecursiveFileSystemWatcher::enable() void RecursiveFileSystemWatcher::enable()
{ {
if (m_isEnabled) if (m_isEnabled)
{ {
return; return;
} }
Q_ASSERT(m_root != QDir::root()); Q_ASSERT(m_root != QDir::root());
addFilesToWatcherRecursive(m_root); addFilesToWatcherRecursive(m_root);
m_isEnabled = true; m_isEnabled = true;
} }
void RecursiveFileSystemWatcher::disable() void RecursiveFileSystemWatcher::disable()
{ {
if (!m_isEnabled) if (!m_isEnabled)
{ {
return; return;
} }
m_isEnabled = false; m_isEnabled = false;
m_watcher->removePaths(m_watcher->files()); m_watcher->removePaths(m_watcher->files());
m_watcher->removePaths(m_watcher->directories()); m_watcher->removePaths(m_watcher->directories());
} }
void RecursiveFileSystemWatcher::setFiles(const QStringList &files) void RecursiveFileSystemWatcher::setFiles(const QStringList &files)
{ {
if (files != m_files) if (files != m_files)
{ {
m_files = files; m_files = files;
emit filesChanged(); emit filesChanged();
} }
} }
void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir) void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir)
{ {
m_watcher->addPath(dir.absolutePath()); m_watcher->addPath(dir.absolutePath());
for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) for (const QString &directory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
{ {
addFilesToWatcherRecursive(dir.absoluteFilePath(directory)); addFilesToWatcherRecursive(dir.absoluteFilePath(directory));
} }
if (m_watchFiles) if (m_watchFiles)
{ {
for (const QFileInfo &info : dir.entryInfoList(QDir::Files)) for (const QFileInfo &info : dir.entryInfoList(QDir::Files))
{ {
m_watcher->addPath(info.absoluteFilePath()); m_watcher->addPath(info.absoluteFilePath());
} }
} }
} }
QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory) QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory)
{ {
QStringList ret; QStringList ret;
if(!m_matcher) if(!m_matcher)
{ {
return {}; return {};
} }
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden)) for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden))
{ {
ret.append(scanRecursive(directory.absoluteFilePath(dir))); ret.append(scanRecursive(directory.absoluteFilePath(dir)));
} }
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden)) for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
{ {
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file)); auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
if (m_matcher->matches(relPath)) if (m_matcher->matches(relPath))
{ {
ret.append(relPath); ret.append(relPath);
} }
} }
return ret; return ret;
} }
void RecursiveFileSystemWatcher::fileChange(const QString &path) void RecursiveFileSystemWatcher::fileChange(const QString &path)
{ {
emit fileChanged(path); emit fileChanged(path);
} }
void RecursiveFileSystemWatcher::directoryChange(const QString &path) void RecursiveFileSystemWatcher::directoryChange(const QString &path)
{ {
setFiles(scanRecursive(m_root)); setFiles(scanRecursive(m_root));
} }

View File

@@ -8,56 +8,56 @@
class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
RecursiveFileSystemWatcher(QObject *parent); RecursiveFileSystemWatcher(QObject *parent);
void setRootDir(const QDir &root); void setRootDir(const QDir &root);
QDir rootDir() const QDir rootDir() const
{ {
return m_root; return m_root;
} }
// WARNING: setting this to true may be bad for performance // WARNING: setting this to true may be bad for performance
void setWatchFiles(const bool watchFiles); void setWatchFiles(const bool watchFiles);
bool watchFiles() const bool watchFiles() const
{ {
return m_watchFiles; return m_watchFiles;
} }
void setMatcher(IPathMatcher::Ptr matcher) void setMatcher(IPathMatcher::Ptr matcher)
{ {
m_matcher = matcher; m_matcher = matcher;
} }
QStringList files() const QStringList files() const
{ {
return m_files; return m_files;
} }
signals: signals:
void filesChanged(); void filesChanged();
void fileChanged(const QString &path); void fileChanged(const QString &path);
public slots: public slots:
void enable(); void enable();
void disable(); void disable();
private: private:
QDir m_root; QDir m_root;
bool m_watchFiles = false; bool m_watchFiles = false;
bool m_isEnabled = false; bool m_isEnabled = false;
IPathMatcher::Ptr m_matcher; IPathMatcher::Ptr m_matcher;
QFileSystemWatcher *m_watcher; QFileSystemWatcher *m_watcher;
QStringList m_files; QStringList m_files;
void setFiles(const QStringList &files); void setFiles(const QStringList &files);
void addFilesToWatcherRecursive(const QDir &dir); void addFilesToWatcherRecursive(const QDir &dir);
QStringList scanRecursive(const QDir &dir); QStringList scanRecursive(const QDir &dir);
private slots: private slots:
void fileChange(const QString &path); void fileChange(const QString &path);
void directoryChange(const QString &path); void directoryChange(const QString &path);
}; };

View File

@@ -7,292 +7,292 @@ template <char Tseparator>
class SeparatorPrefixTree class SeparatorPrefixTree
{ {
public: public:
SeparatorPrefixTree(QStringList paths) SeparatorPrefixTree(QStringList paths)
{ {
insert(paths); insert(paths);
} }
SeparatorPrefixTree(bool contained = false) SeparatorPrefixTree(bool contained = false)
{ {
m_contained = contained; m_contained = contained;
} }
void insert(QStringList paths) void insert(QStringList paths)
{ {
for(auto &path: paths) for(auto &path: paths)
{ {
insert(path); insert(path);
} }
} }
/// insert an exact path into the tree /// insert an exact path into the tree
SeparatorPrefixTree & insert(QString path) SeparatorPrefixTree & insert(QString path)
{ {
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
children[path] = SeparatorPrefixTree(true); children[path] = SeparatorPrefixTree(true);
return children[path]; return children[path];
} }
else else
{ {
auto prefix = path.left(sepIndex); auto prefix = path.left(sepIndex);
if(!children.contains(prefix)) if(!children.contains(prefix))
{ {
children[prefix] = SeparatorPrefixTree(false); children[prefix] = SeparatorPrefixTree(false);
} }
return children[prefix].insert(path.mid(sepIndex + 1)); return children[prefix].insert(path.mid(sepIndex + 1));
} }
} }
/// is the path fully contained in the tree? /// is the path fully contained in the tree?
bool contains(QString path) const bool contains(QString path) const
{ {
auto node = find(path); auto node = find(path);
return node != nullptr; return node != nullptr;
} }
/// does the tree cover a path? That means the prefix of the path is contained in the tree /// does the tree cover a path? That means the prefix of the path is contained in the tree
bool covers(QString path) const bool covers(QString path) const
{ {
// if we found some valid node, it's good enough. the tree covers the path // if we found some valid node, it's good enough. the tree covers the path
if(m_contained) if(m_contained)
{ {
return true; return true;
} }
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
auto found = children.find(path); auto found = children.find(path);
if(found == children.end()) if(found == children.end())
{ {
return false; return false;
} }
return (*found).covers(QString()); return (*found).covers(QString());
} }
else else
{ {
auto prefix = path.left(sepIndex); auto prefix = path.left(sepIndex);
auto found = children.find(prefix); auto found = children.find(prefix);
if(found == children.end()) if(found == children.end())
{ {
return false; return false;
} }
return (*found).covers(path.mid(sepIndex + 1)); return (*found).covers(path.mid(sepIndex + 1));
} }
} }
/// return the contained path that covers the path specified /// return the contained path that covers the path specified
QString cover(QString path) const QString cover(QString path) const
{ {
// if we found some valid node, it's good enough. the tree covers the path // if we found some valid node, it's good enough. the tree covers the path
if(m_contained) if(m_contained)
{ {
return QString(""); return QString("");
} }
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
auto found = children.find(path); auto found = children.find(path);
if(found == children.end()) if(found == children.end())
{ {
return QString(); return QString();
} }
auto nested = (*found).cover(QString()); auto nested = (*found).cover(QString());
if(nested.isNull()) if(nested.isNull())
{ {
return nested; return nested;
} }
if(nested.isEmpty()) if(nested.isEmpty())
return path; return path;
return path + Tseparator + nested; return path + Tseparator + nested;
} }
else else
{ {
auto prefix = path.left(sepIndex); auto prefix = path.left(sepIndex);
auto found = children.find(prefix); auto found = children.find(prefix);
if(found == children.end()) if(found == children.end())
{ {
return QString(); return QString();
} }
auto nested = (*found).cover(path.mid(sepIndex + 1)); auto nested = (*found).cover(path.mid(sepIndex + 1));
if(nested.isNull()) if(nested.isNull())
{ {
return nested; return nested;
} }
if(nested.isEmpty()) if(nested.isEmpty())
return prefix; return prefix;
return prefix + Tseparator + nested; return prefix + Tseparator + nested;
} }
} }
/// Does the path-specified node exist in the tree? It does not have to be contained. /// Does the path-specified node exist in the tree? It does not have to be contained.
bool exists(QString path) const bool exists(QString path) const
{ {
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
auto found = children.find(path); auto found = children.find(path);
if(found == children.end()) if(found == children.end())
{ {
return false; return false;
} }
return true; return true;
} }
else else
{ {
auto prefix = path.left(sepIndex); auto prefix = path.left(sepIndex);
auto found = children.find(prefix); auto found = children.find(prefix);
if(found == children.end()) if(found == children.end())
{ {
return false; return false;
} }
return (*found).exists(path.mid(sepIndex + 1)); return (*found).exists(path.mid(sepIndex + 1));
} }
} }
/// find a node in the tree by name /// find a node in the tree by name
const SeparatorPrefixTree * find(QString path) const const SeparatorPrefixTree * find(QString path) const
{ {
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
auto found = children.find(path); auto found = children.find(path);
if(found == children.end()) if(found == children.end())
{ {
return nullptr; return nullptr;
} }
return &(*found); return &(*found);
} }
else else
{ {
auto prefix = path.left(sepIndex); auto prefix = path.left(sepIndex);
auto found = children.find(prefix); auto found = children.find(prefix);
if(found == children.end()) if(found == children.end())
{ {
return nullptr; return nullptr;
} }
return (*found).find(path.mid(sepIndex + 1)); return (*found).find(path.mid(sepIndex + 1));
} }
} }
/// is this a leaf node? /// is this a leaf node?
bool leaf() const bool leaf() const
{ {
return children.isEmpty(); return children.isEmpty();
} }
/// is this node actually contained in the tree, or is it purely structural? /// is this node actually contained in the tree, or is it purely structural?
bool contained() const bool contained() const
{ {
return m_contained; return m_contained;
} }
/// Remove a path from the tree /// Remove a path from the tree
bool remove(QString path) bool remove(QString path)
{ {
return removeInternal(path) != Failed; return removeInternal(path) != Failed;
} }
/// Clear all children of this node tree node /// Clear all children of this node tree node
void clear() void clear()
{ {
children.clear(); children.clear();
} }
QStringList toStringList() const QStringList toStringList() const
{ {
QStringList collected; QStringList collected;
// collecting these is more expensive. // collecting these is more expensive.
auto iter = children.begin(); auto iter = children.begin();
while(iter != children.end()) while(iter != children.end())
{ {
QStringList list = iter.value().toStringList(); QStringList list = iter.value().toStringList();
for(int i = 0; i < list.size(); i++) for(int i = 0; i < list.size(); i++)
{ {
list[i] = iter.key() + Tseparator + list[i]; list[i] = iter.key() + Tseparator + list[i];
} }
collected.append(list); collected.append(list);
if((*iter).m_contained) if((*iter).m_contained)
{ {
collected.append(iter.key()); collected.append(iter.key());
} }
iter++; iter++;
} }
return collected; return collected;
} }
private: private:
enum Removal enum Removal
{ {
Failed, Failed,
Succeeded, Succeeded,
HasChildren HasChildren
}; };
Removal removeInternal(QString path = QString()) Removal removeInternal(QString path = QString())
{ {
if(path.isEmpty()) if(path.isEmpty())
{ {
if(!m_contained) if(!m_contained)
{ {
// remove all children - we are removing a prefix // remove all children - we are removing a prefix
clear(); clear();
return Succeeded; return Succeeded;
} }
m_contained = false; m_contained = false;
if(children.size()) if(children.size())
{ {
return HasChildren; return HasChildren;
} }
return Succeeded; return Succeeded;
} }
Removal remStatus = Failed; Removal remStatus = Failed;
QString childToRemove; QString childToRemove;
auto sepIndex = path.indexOf(Tseparator); auto sepIndex = path.indexOf(Tseparator);
if(sepIndex == -1) if(sepIndex == -1)
{ {
childToRemove = path; childToRemove = path;
auto found = children.find(childToRemove); auto found = children.find(childToRemove);
if(found == children.end()) if(found == children.end())
{ {
return Failed; return Failed;
} }
remStatus = (*found).removeInternal(); remStatus = (*found).removeInternal();
} }
else else
{ {
childToRemove = path.left(sepIndex); childToRemove = path.left(sepIndex);
auto found = children.find(childToRemove); auto found = children.find(childToRemove);
if(found == children.end()) if(found == children.end())
{ {
return Failed; return Failed;
} }
remStatus = (*found).removeInternal(path.mid(sepIndex + 1)); remStatus = (*found).removeInternal(path.mid(sepIndex + 1));
} }
switch (remStatus) switch (remStatus)
{ {
case Failed: case Failed:
case HasChildren: case HasChildren:
{ {
return remStatus; return remStatus;
} }
case Succeeded: case Succeeded:
{ {
children.remove(childToRemove); children.remove(childToRemove);
if(m_contained) if(m_contained)
{ {
return HasChildren; return HasChildren;
} }
if(children.size()) if(children.size())
{ {
return HasChildren; return HasChildren;
} }
return Succeeded; return Succeeded;
} }
} }
return Failed; return Failed;
} }
private: private:
QMap<QString,SeparatorPrefixTree<Tseparator>> children; QMap<QString,SeparatorPrefixTree<Tseparator>> children;
bool m_contained = false; bool m_contained = false;
}; };

View File

@@ -12,27 +12,27 @@ class Usable;
*/ */
class Usable class Usable
{ {
friend class UseLock; friend class UseLock;
public: public:
std::size_t useCount() std::size_t useCount()
{ {
return m_useCount; return m_useCount;
} }
bool isInUse() bool isInUse()
{ {
return m_useCount > 0; return m_useCount > 0;
} }
protected: protected:
virtual void decrementUses() virtual void decrementUses()
{ {
m_useCount--; m_useCount--;
} }
virtual void incrementUses() virtual void incrementUses()
{ {
m_useCount++; m_useCount++;
} }
private: private:
std::size_t m_useCount = 0; std::size_t m_useCount = 0;
}; };
/** /**
@@ -43,16 +43,16 @@ private:
class UseLock class UseLock
{ {
public: public:
UseLock(std::shared_ptr<Usable> usable) UseLock(std::shared_ptr<Usable> usable)
: m_usable(usable) : m_usable(usable)
{ {
// this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate. // this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate.
m_usable->incrementUses(); m_usable->incrementUses();
} }
~UseLock() ~UseLock()
{ {
m_usable->decrementUses(); m_usable->decrementUses();
} }
private: private:
std::shared_ptr<Usable> m_usable; std::shared_ptr<Usable> m_usable;
}; };

View File

@@ -7,134 +7,79 @@
Version::Version(const QString &str) : m_string(str) Version::Version(const QString &str) : m_string(str)
{ {
parse(); parse();
} }
bool Version::operator<(const Version &other) const bool Version::operator<(const Version &other) const
{ {
const int size = qMax(m_sections.size(), other.m_sections.size()); const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
{ {
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 = const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2) if (sec1 != sec2)
{ {
return sec1 < sec2; return sec1 < sec2;
} }
} }
return false; return false;
} }
bool Version::operator<=(const Version &other) const bool Version::operator<=(const Version &other) const
{ {
return *this < other || *this == other; return *this < other || *this == other;
} }
bool Version::operator>(const Version &other) const bool Version::operator>(const Version &other) const
{ {
const int size = qMax(m_sections.size(), other.m_sections.size()); const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
{ {
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 = const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2) if (sec1 != sec2)
{ {
return sec1 > sec2; return sec1 > sec2;
} }
} }
return false; return false;
} }
bool Version::operator>=(const Version &other) const bool Version::operator>=(const Version &other) const
{ {
return *this > other || *this == other; return *this > other || *this == other;
} }
bool Version::operator==(const Version &other) const bool Version::operator==(const Version &other) const
{ {
const int size = qMax(m_sections.size(), other.m_sections.size()); const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i) for (int i = 0; i < size; ++i)
{ {
const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i); const Section sec1 = (i >= m_sections.size()) ? Section("0") : m_sections.at(i);
const Section sec2 = const Section sec2 =
(i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i); (i >= other.m_sections.size()) ? Section("0") : other.m_sections.at(i);
if (sec1 != sec2) if (sec1 != sec2)
{ {
return false; return false;
} }
} }
return true; return true;
} }
bool Version::operator!=(const Version &other) const bool Version::operator!=(const Version &other) const
{ {
return !operator==(other); return !operator==(other);
} }
void Version::parse() void Version::parse()
{ {
m_sections.clear(); m_sections.clear();
QStringList parts = m_string.split('.'); // FIXME: this is bad. versions can contain a lot more separators...
QStringList parts = m_string.split('.');
for (const auto part : parts) for (const auto part : parts)
{ {
m_sections.append(Section(part)); m_sections.append(Section(part));
} }
}
bool versionIsInInterval(const QString &version, const QString &interval)
{
return versionIsInInterval(Version(version), interval);
}
bool versionIsInInterval(const Version &version, const QString &interval)
{
if (interval.isEmpty() || version.toString() == interval)
{
return true;
}
// Interval notation is used
QRegularExpression exp(
"(?<start>[\\[\\]\\(\\)])(?<bottom>.*?)(,(?<top>.*?))?(?<end>[\\[\\]\\(\\)]),?");
QRegularExpressionMatch match = exp.match(interval);
if (match.hasMatch())
{
const QChar start = match.captured("start").at(0);
const QChar end = match.captured("end").at(0);
const QString bottom = match.captured("bottom");
const QString top = match.captured("top");
// check if in range (bottom)
if (!bottom.isEmpty())
{
const auto bottomVersion = Version(bottom);
if ((start == '[') && !(version >= bottomVersion))
{
return false;
}
else if ((start == '(') && !(version > bottomVersion))
{
return false;
}
}
// check if in range (top)
if (!top.isEmpty())
{
const auto topVersion = Version(top);
if ((end == ']') && !(version <= topVersion))
{
return false;
}
else if ((end == ')') && !(version < topVersion))
{
return false;
}
}
return true;
}
return false;
} }

View File

@@ -7,104 +7,101 @@
class QUrl; class QUrl;
struct MULTIMC_LOGIC_EXPORT Version class MULTIMC_LOGIC_EXPORT Version
{ {
Version(const QString &str); public:
Version() {} Version(const QString &str);
Version() {}
bool operator<(const Version &other) const; bool operator<(const Version &other) const;
bool operator<=(const Version &other) const; bool operator<=(const Version &other) const;
bool operator>(const Version &other) const; bool operator>(const Version &other) const;
bool operator>=(const Version &other) const; bool operator>=(const Version &other) const;
bool operator==(const Version &other) const; bool operator==(const Version &other) const;
bool operator!=(const Version &other) const; bool operator!=(const Version &other) const;
QString toString() const QString toString() const
{ {
return m_string; return m_string;
} }
private: private:
QString m_string; QString m_string;
struct Section struct Section
{ {
explicit Section(const QString &fullString) explicit Section(const QString &fullString)
{ {
m_fullString = fullString; m_fullString = fullString;
int cutoff = m_fullString.size(); int cutoff = m_fullString.size();
for(int i = 0; i < m_fullString.size(); i++) for(int i = 0; i < m_fullString.size(); i++)
{ {
if(!m_fullString[i].isDigit()) if(!m_fullString[i].isDigit())
{ {
cutoff = i; cutoff = i;
break; break;
} }
} }
auto numPart = m_fullString.leftRef(cutoff); auto numPart = m_fullString.leftRef(cutoff);
if(numPart.size()) if(numPart.size())
{ {
numValid = true; numValid = true;
m_numPart = numPart.toInt(); m_numPart = numPart.toInt();
} }
auto stringPart = m_fullString.midRef(cutoff); auto stringPart = m_fullString.midRef(cutoff);
if(stringPart.size()) if(stringPart.size())
{ {
m_stringPart = stringPart.toString(); m_stringPart = stringPart.toString();
} }
} }
explicit Section() {} explicit Section() {}
bool numValid = false; bool numValid = false;
int m_numPart = 0; int m_numPart = 0;
QString m_stringPart; QString m_stringPart;
QString m_fullString; QString m_fullString;
inline bool operator!=(const Section &other) const inline bool operator!=(const Section &other) const
{ {
if(numValid && other.numValid) if(numValid && other.numValid)
{ {
return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart; return m_numPart != other.m_numPart || m_stringPart != other.m_stringPart;
} }
else else
{ {
return m_fullString != other.m_fullString; return m_fullString != other.m_fullString;
} }
} }
inline bool operator<(const Section &other) const inline bool operator<(const Section &other) const
{ {
if(numValid && other.numValid) if(numValid && other.numValid)
{ {
if(m_numPart < other.m_numPart) if(m_numPart < other.m_numPart)
return true; return true;
if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart) if(m_numPart == other.m_numPart && m_stringPart < other.m_stringPart)
return true; return true;
return false; return false;
} }
else else
{ {
return m_fullString < other.m_fullString; return m_fullString < other.m_fullString;
} }
} }
inline bool operator>(const Section &other) const inline bool operator>(const Section &other) const
{ {
if(numValid && other.numValid) if(numValid && other.numValid)
{ {
if(m_numPart > other.m_numPart) if(m_numPart > other.m_numPart)
return true; return true;
if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart) if(m_numPart == other.m_numPart && m_stringPart > other.m_stringPart)
return true; return true;
return false; return false;
} }
else else
{ {
return m_fullString > other.m_fullString; return m_fullString > other.m_fullString;
} }
} }
}; };
QList<Section> m_sections; QList<Section> m_sections;
void parse(); void parse();
}; };
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
MULTIMC_LOGIC_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,102 +20,64 @@
class ModUtilsTest : public QObject class ModUtilsTest : public QObject
{ {
Q_OBJECT Q_OBJECT
void setupVersions() void setupVersions()
{ {
QTest::addColumn<QString>("first"); QTest::addColumn<QString>("first");
QTest::addColumn<QString>("second"); QTest::addColumn<QString>("second");
QTest::addColumn<bool>("lessThan"); QTest::addColumn<bool>("lessThan");
QTest::addColumn<bool>("equal"); QTest::addColumn<bool>("equal");
QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true; QTest::newRow("equal, explicit") << "1.2.0" << "1.2.0" << false << true;
QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true; QTest::newRow("equal, implicit 1") << "1.2" << "1.2.0" << false << true;
QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true; QTest::newRow("equal, implicit 2") << "1.2.0" << "1.2" << false << true;
QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true; QTest::newRow("equal, two-digit") << "1.42" << "1.42" << false << true;
QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false; QTest::newRow("lessThan, explicit 1") << "1.2.0" << "1.2.1" << true << false;
QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false; QTest::newRow("lessThan, explicit 2") << "1.2.0" << "1.3.0" << true << false;
QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false; QTest::newRow("lessThan, explicit 3") << "1.2.0" << "2.2.0" << true << false;
QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false; QTest::newRow("lessThan, implicit 1") << "1.2" << "1.2.1" << true << false;
QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false; QTest::newRow("lessThan, implicit 2") << "1.2" << "1.3.0" << true << false;
QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false; QTest::newRow("lessThan, implicit 3") << "1.2" << "2.2.0" << true << false;
QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false; QTest::newRow("lessThan, two-digit") << "1.41" << "1.42" << true << false;
QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false; QTest::newRow("greaterThan, explicit 1") << "1.2.1" << "1.2.0" << false << false;
QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false; QTest::newRow("greaterThan, explicit 2") << "1.3.0" << "1.2.0" << false << false;
QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false; QTest::newRow("greaterThan, explicit 3") << "2.2.0" << "1.2.0" << false << false;
QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false; QTest::newRow("greaterThan, implicit 1") << "1.2.1" << "1.2" << false << false;
QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false; QTest::newRow("greaterThan, implicit 2") << "1.3.0" << "1.2" << false << false;
QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false; QTest::newRow("greaterThan, implicit 3") << "2.2.0" << "1.2" << false << false;
QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false; QTest::newRow("greaterThan, two-digit") << "1.42" << "1.41" << false << false;
} }
private slots: private slots:
void initTestCase() void initTestCase()
{ {
} }
void cleanupTestCase() void cleanupTestCase()
{ {
} }
void test_versionIsInInterval_data() void test_versionCompare_data()
{ {
QTest::addColumn<QString>("version"); setupVersions();
QTest::addColumn<QString>("interval"); }
QTest::addColumn<bool>("result"); void test_versionCompare()
{
QFETCH(QString, first);
QFETCH(QString, second);
QFETCH(bool, lessThan);
QFETCH(bool, equal);
QTest::newRow("empty, true") << "1.2.3" << "" << true; const auto v1 = Version(first);
QTest::newRow("one version, true") << "1.2.3" << "1.2.3" << true; const auto v2 = Version(second);
QTest::newRow("one version, false") << "1.2.3" << "1.2.2" << false;
QTest::newRow("one version inclusive <-> infinity, true") << "1.2.3" << "[1.2.3,)" << true; QCOMPARE(v1 < v2, lessThan);
QTest::newRow("one version exclusive <-> infinity, true") << "1.2.3" << "(1.2.2,)" << true; QCOMPARE(v1 > v2, !lessThan && !equal);
QTest::newRow("one version inclusive <-> infitity, false") << "1.2.3" << "[1.2.4,)" << false; QCOMPARE(v1 == v2, equal);
QTest::newRow("one version exclusive <-> infinity, false") << "1.2.3" << "(1.2.3,)" << false; }
QTest::newRow("infinity <-> one version inclusive, true") << "1.2.3" << "(,1.2.3]" << true;
QTest::newRow("infinity <-> one version exclusive, true") << "1.2.3" << "(,1.2.4)" << true;
QTest::newRow("infinity <-> one version inclusive, false") << "1.2.3" << "(,1.2.2]" << false;
QTest::newRow("infinity <-> one version exclusive, false") << "1.2.3" << "(,1.2.3)" << false;
QTest::newRow("inclusive <-> inclusive, true") << "1.2.3" << "[1.2.2,1.2.3]" << true;
QTest::newRow("inclusive <-> exclusive, true") << "1.2.3" << "[1.2.3,1.2.4)" << true;
QTest::newRow("exclusive <-> inclusive, true") << "1.2.3" << "(1.2.2,1.2.3]" << true;
QTest::newRow("exclusive <-> exclusive, true") << "1.2.3" << "(1.2.2,1.2.4)" << true;
QTest::newRow("inclusive <-> inclusive, false") << "1.2.3" << "[1.0.0,1.2.2]" << false;
QTest::newRow("inclusive <-> exclusive, false") << "1.2.3" << "[1.0.0,1.2.3)" << false;
QTest::newRow("exclusive <-> inclusive, false") << "1.2.3" << "(1.2.3,2.0.0]" << false;
QTest::newRow("exclusive <-> exclusive, false") << "1.2.3" << "(1.0.0,1.2.3)" << false;
}
void test_versionIsInInterval()
{
QFETCH(QString, version);
QFETCH(QString, interval);
QFETCH(bool, result);
QCOMPARE(versionIsInInterval(version, interval), result);
}
void test_versionCompare_data()
{
setupVersions();
}
void test_versionCompare()
{
QFETCH(QString, first);
QFETCH(QString, second);
QFETCH(bool, lessThan);
QFETCH(bool, equal);
const auto v1 = Version(first);
const auto v2 = Version(second);
QCOMPARE(v1 < v2, lessThan);
QCOMPARE(v1 > v2, !lessThan && !equal);
QCOMPARE(v1 == v2, equal);
}
}; };
QTEST_GUILESS_MAIN(ModUtilsTest) QTEST_GUILESS_MAIN(ModUtilsTest)

20
api/logic/WatchLock.h Normal file
View 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;
};

View File

@@ -6,20 +6,21 @@
enum IconType : unsigned enum IconType : unsigned
{ {
Builtin, Builtin,
Transient, Transient,
FileBased, FileBased,
ICONS_TOTAL, ICONS_TOTAL,
ToBeDeleted ToBeDeleted
}; };
class MULTIMC_LOGIC_EXPORT IIconList class MULTIMC_LOGIC_EXPORT IIconList
{ {
public: public:
virtual ~IIconList(); virtual ~IIconList();
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0; virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
virtual bool deleteIcon(const QString &key) = 0; virtual bool deleteIcon(const QString &key) = 0;
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0; virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0; virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0; virtual void installIcons(const QStringList &iconFiles) = 0;
virtual void installIcon(const QString &file, const QString &name) = 0;
}; };

View 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;
}
}

View 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();
}

View File

@@ -1,4 +1,5 @@
#include "JavaChecker.h" #include "JavaChecker.h"
#include "JavaUtils.h"
#include <FileSystem.h> #include <FileSystem.h>
#include <Commandline.h> #include <Commandline.h>
#include <QFile> #include <QFile>
@@ -7,154 +8,157 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include "Env.h"
JavaChecker::JavaChecker(QObject *parent) : QObject(parent) JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
{ {
} }
void JavaChecker::performCheck() void JavaChecker::performCheck()
{ {
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar"); QString checkerJar = FS::PathCombine(ENV.getJarsPath(), "JavaCheck.jar");
QStringList args; QStringList args;
process.reset(new QProcess()); process.reset(new QProcess());
if(m_args.size()) if(m_args.size())
{ {
auto extraArgs = Commandline::splitArgs(m_args); auto extraArgs = Commandline::splitArgs(m_args);
args.append(extraArgs); args.append(extraArgs);
} }
if(m_minMem != 0) if(m_minMem != 0)
{ {
args << QString("-Xms%1m").arg(m_minMem); args << QString("-Xms%1m").arg(m_minMem);
} }
if(m_maxMem != 0) if(m_maxMem != 0)
{ {
args << QString("-Xmx%1m").arg(m_maxMem); args << QString("-Xmx%1m").arg(m_maxMem);
} }
if(m_permGen != 64) if(m_permGen != 64)
{ {
args << QString("-XX:PermSize=%1m").arg(m_permGen); args << QString("-XX:PermSize=%1m").arg(m_permGen);
} }
args.append({"-jar", checkerJar}); args.append({"-jar", checkerJar});
process->setArguments(args); process->setArguments(args);
process->setProgram(m_path); process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels); process->setProcessChannelMode(QProcess::SeparateChannels);
qDebug() << "Running java checker: " + m_path + args.join(" ");; process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");;
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus))); connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError))); connect(process.get(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady())); connect(process.get(), SIGNAL(readyReadStandardOutput()), this, SLOT(stdoutReady()));
connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady())); connect(process.get(), SIGNAL(readyReadStandardError()), this, SLOT(stderrReady()));
connect(&killTimer, SIGNAL(timeout()), SLOT(timeout())); connect(&killTimer, SIGNAL(timeout()), SLOT(timeout()));
killTimer.setSingleShot(true); killTimer.setSingleShot(true);
killTimer.start(15000); killTimer.start(15000);
process->start(); process->start();
} }
void JavaChecker::stdoutReady() void JavaChecker::stdoutReady()
{ {
QByteArray data = process->readAllStandardOutput(); QByteArray data = process->readAllStandardOutput();
QString added = QString::fromLocal8Bit(data); QString added = QString::fromLocal8Bit(data);
added.remove('\r'); added.remove('\r');
m_stdout += added; m_stdout += added;
} }
void JavaChecker::stderrReady() void JavaChecker::stderrReady()
{ {
QByteArray data = process->readAllStandardError(); QByteArray data = process->readAllStandardError();
QString added = QString::fromLocal8Bit(data); QString added = QString::fromLocal8Bit(data);
added.remove('\r'); added.remove('\r');
m_stderr += added; m_stderr += added;
} }
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status) void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
{ {
killTimer.stop(); killTimer.stop();
QProcessPtr _process; QProcessPtr _process = process;
_process.swap(process); process.reset();
JavaCheckResult result; JavaCheckResult result;
{ {
result.path = m_path; result.path = m_path;
result.id = m_id; result.id = m_id;
} }
result.errorLog = m_stderr; result.errorLog = m_stderr;
result.outLog = m_stdout; result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout; qDebug() << "STDOUT" << m_stdout;
qWarning() << "STDERR" << m_stderr; qWarning() << "STDERR" << m_stderr;
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode; qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
if (status == QProcess::CrashExit || exitcode == 1) if (status == QProcess::CrashExit || exitcode == 1)
{ {
result.validity = JavaCheckResult::Validity::Errored; result.validity = JavaCheckResult::Validity::Errored;
emit checkFinished(result); emit checkFinished(result);
return; return;
} }
bool success = true; bool success = true;
QMap<QString, QString> results; QMap<QString, QString> results;
QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts); QStringList lines = m_stdout.split("\n", QString::SkipEmptyParts);
for(QString line : lines) for(QString line : lines)
{ {
line = line.trimmed(); line = line.trimmed();
auto parts = line.split('=', QString::SkipEmptyParts); auto parts = line.split('=', QString::SkipEmptyParts);
if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty()) if(parts.size() != 2 || parts[0].isEmpty() || parts[1].isEmpty())
{ {
success = false; success = false;
} }
else else
{ {
results.insert(parts[0], parts[1]); results.insert(parts[0], parts[1]);
} }
} }
if(!results.contains("os.arch") || !results.contains("java.version") || !success) if(!results.contains("os.arch") || !results.contains("java.version") || !success)
{ {
result.validity = JavaCheckResult::Validity::ReturnedInvalidData; result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
emit checkFinished(result); emit checkFinished(result);
return; return;
} }
auto os_arch = results["os.arch"]; auto os_arch = results["os.arch"];
auto java_version = results["java.version"]; auto java_version = results["java.version"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64"; bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
result.validity = JavaCheckResult::Validity::Valid; result.validity = JavaCheckResult::Validity::Valid;
result.is_64bit = is_64; result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32"; result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch; result.realPlatform = os_arch;
result.javaVersion = java_version; result.javaVersion = java_version;
qDebug() << "Java checker succeeded."; qDebug() << "Java checker succeeded.";
emit checkFinished(result); emit checkFinished(result);
} }
void JavaChecker::error(QProcess::ProcessError err) void JavaChecker::error(QProcess::ProcessError err)
{ {
if(err == QProcess::FailedToStart) if(err == QProcess::FailedToStart)
{ {
killTimer.stop(); killTimer.stop();
qDebug() << "Java checker has failed to start."; qDebug() << "Java checker has failed to start.";
JavaCheckResult result; JavaCheckResult result;
{ {
result.path = m_path; result.path = m_path;
result.id = m_id; result.id = m_id;
} }
emit checkFinished(result); emit checkFinished(result);
return; return;
} }
} }
void JavaChecker::timeout() void JavaChecker::timeout()
{ {
// NO MERCY. NO ABUSE. // NO MERCY. NO ABUSE.
if(process) if(process)
{ {
qDebug() << "Java checker has been killed by timeout."; qDebug() << "Java checker has been killed by timeout.";
process->kill(); process->kill();
} }
} }

View File

@@ -3,6 +3,8 @@
#include <QTimer> #include <QTimer>
#include <memory> #include <memory>
#include "QObjectPtr.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include "JavaVersion.h" #include "JavaVersion.h"
@@ -11,50 +13,50 @@ class JavaChecker;
struct MULTIMC_LOGIC_EXPORT JavaCheckResult struct MULTIMC_LOGIC_EXPORT JavaCheckResult
{ {
QString path; QString path;
QString mojangPlatform; QString mojangPlatform;
QString realPlatform; QString realPlatform;
JavaVersion javaVersion; JavaVersion javaVersion;
QString outLog; QString outLog;
QString errorLog; QString errorLog;
bool is_64bit = false; bool is_64bit = false;
int id; int id;
enum class Validity enum class Validity
{ {
Errored, Errored,
ReturnedInvalidData, ReturnedInvalidData,
Valid Valid
} validity = Validity::Errored; } validity = Validity::Errored;
}; };
typedef std::shared_ptr<QProcess> QProcessPtr; typedef shared_qobject_ptr<QProcess> QProcessPtr;
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr; typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaChecker(QObject *parent = 0); explicit JavaChecker(QObject *parent = 0);
void performCheck(); void performCheck();
QString m_path; QString m_path;
QString m_args; QString m_args;
int m_id = 0; int m_id = 0;
int m_minMem = 0; int m_minMem = 0;
int m_maxMem = 0; int m_maxMem = 0;
int m_permGen = 64; int m_permGen = 64;
signals: signals:
void checkFinished(JavaCheckResult result); void checkFinished(JavaCheckResult result);
private: private:
QProcessPtr process; QProcessPtr process;
QTimer killTimer; QTimer killTimer;
QString m_stdout; QString m_stdout;
QString m_stderr; QString m_stderr;
public public
slots: slots:
void timeout(); void timeout();
void finished(int exitcode, QProcess::ExitStatus); void finished(int exitcode, QProcess::ExitStatus);
void error(QProcess::ProcessError); void error(QProcess::ProcessError);
void stdoutReady(); void stdoutReady();
void stderrReady(); void stderrReady();
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,27 +19,26 @@
void JavaCheckerJob::partFinished(JavaCheckResult result) void JavaCheckerJob::partFinished(JavaCheckResult result)
{ {
num_finished++; num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/" qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
<< javacheckers.size(); << javacheckers.size();
emit progress(num_finished, javacheckers.size()); setProgress(num_finished, javacheckers.size());
javaresults.replace(result.id, result); javaresults.replace(result.id, result);
if (num_finished == javacheckers.size()) if (num_finished == javacheckers.size())
{ {
emit finished(javaresults); emitSucceeded();
} }
} }
void JavaCheckerJob::executeTask() void JavaCheckerJob::executeTask()
{ {
qDebug() << m_job_name.toLocal8Bit() << " started."; qDebug() << m_job_name.toLocal8Bit() << " started.";
m_running = true; for (auto iter : javacheckers)
for (auto iter : javacheckers) {
{ javaresults.append(JavaCheckResult());
javaresults.append(JavaCheckResult()); connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
connect(iter.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); iter->performCheck();
iter->performCheck(); }
}
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,65 +20,42 @@
#include "tasks/Task.h" #include "tasks/Task.h"
class JavaCheckerJob; class JavaCheckerJob;
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr; typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task class JavaCheckerJob : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {}; explicit JavaCheckerJob(QString job_name) : Task(), m_job_name(job_name) {};
virtual ~JavaCheckerJob() {};
bool addJavaCheckerAction(JavaCheckerPtr base) bool addJavaCheckerAction(JavaCheckerPtr base)
{ {
javacheckers.append(base); javacheckers.append(base);
total_progress++; // if this is already running, the action needs to be started right away!
// if this is already running, the action needs to be started right away! if (isRunning())
if (isRunning()) {
{ setProgress(num_finished, javacheckers.size());
setProgress(current_progress, total_progress); connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult))); base->performCheck();
}
base->performCheck(); return true;
} }
return true; QList<JavaCheckResult> getResults()
} {
return javaresults;
JavaCheckerPtr operator[](int index) }
{
return javacheckers[index];
}
;
JavaCheckerPtr first()
{
if (javacheckers.size())
return javacheckers[0];
return JavaCheckerPtr();
}
int size() const
{
return javacheckers.size();
}
virtual bool isRunning() const override
{
return m_running;
}
signals:
void started();
void finished(QList<JavaCheckResult>);
private slots: private slots:
void partFinished(JavaCheckResult result); void partFinished(JavaCheckResult result);
protected: protected:
virtual void executeTask() override; virtual void executeTask() override;
private: private:
QString m_job_name; QString m_job_name;
QList<JavaCheckerPtr> javacheckers; QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults; QList<JavaCheckResult> javaresults;
qint64 current_progress = 0; int num_finished = 0;
qint64 total_progress = 0;
int num_finished = 0;
bool m_running = false;
}; };

View File

@@ -3,26 +3,26 @@
bool JavaInstall::operator<(const JavaInstall &rhs) bool JavaInstall::operator<(const JavaInstall &rhs)
{ {
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive); auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if(archCompare != 0) if(archCompare != 0)
return archCompare < 0; return archCompare < 0;
if(id < rhs.id) if(id < rhs.id)
{ {
return true; return true;
} }
if(id > rhs.id) if(id > rhs.id)
{ {
return false; return false;
} }
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0; return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
} }
bool JavaInstall::operator==(const JavaInstall &rhs) bool JavaInstall::operator==(const JavaInstall &rhs)
{ {
return arch == rhs.arch && id == rhs.id && path == rhs.path; return arch == rhs.arch && id == rhs.id && path == rhs.path;
} }
bool JavaInstall::operator>(const JavaInstall &rhs) bool JavaInstall::operator>(const JavaInstall &rhs)
{ {
return (!operator<(rhs)) && (!operator==(rhs)); return (!operator<(rhs)) && (!operator==(rhs));
} }

View File

@@ -5,34 +5,34 @@
struct JavaInstall : public BaseVersion struct JavaInstall : public BaseVersion
{ {
JavaInstall(){} JavaInstall(){}
JavaInstall(QString id, QString arch, QString path) JavaInstall(QString id, QString arch, QString path)
: id(id), arch(arch), path(path) : id(id), arch(arch), path(path)
{ {
} }
virtual QString descriptor() virtual QString descriptor()
{ {
return id.toString(); return id.toString();
} }
virtual QString name() virtual QString name()
{ {
return id.toString(); return id.toString();
} }
virtual QString typeString() const virtual QString typeString() const
{ {
return arch; return arch;
} }
bool operator<(const JavaInstall & rhs); bool operator<(const JavaInstall & rhs);
bool operator==(const JavaInstall & rhs); bool operator==(const JavaInstall & rhs);
bool operator>(const JavaInstall & rhs); bool operator>(const JavaInstall & rhs);
JavaVersion id; JavaVersion id;
QString arch; QString arch;
QString path; QString path;
bool recommended = false; bool recommended = false;
}; };
typedef std::shared_ptr<JavaInstall> JavaInstallPtr; typedef std::shared_ptr<JavaInstall> JavaInstallPtr;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -29,92 +29,113 @@ JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
{ {
} }
Task *JavaInstallList::getLoadTask() shared_qobject_ptr<Task> JavaInstallList::getLoadTask()
{ {
return new JavaListLoadTask(this); load();
return getCurrentTask();
}
shared_qobject_ptr<Task> JavaInstallList::getCurrentTask()
{
if(m_status == Status::InProgress)
{
return m_loadTask;
}
return nullptr;
}
void JavaInstallList::load()
{
if(m_status != Status::InProgress)
{
m_status = Status::InProgress;
m_loadTask = new JavaListLoadTask(this);
m_loadTask->start();
}
} }
const BaseVersionPtr JavaInstallList::at(int i) const const BaseVersionPtr JavaInstallList::at(int i) const
{ {
return m_vlist.at(i); return m_vlist.at(i);
} }
bool JavaInstallList::isLoaded() bool JavaInstallList::isLoaded()
{ {
return m_loaded; return m_status == JavaInstallList::Status::Done;
} }
int JavaInstallList::count() const int JavaInstallList::count() const
{ {
return m_vlist.count(); return m_vlist.count();
} }
QVariant JavaInstallList::data(const QModelIndex &index, int role) const QVariant JavaInstallList::data(const QModelIndex &index, int role) const
{ {
if (!index.isValid()) if (!index.isValid())
return QVariant(); return QVariant();
if (index.row() > count()) if (index.row() > count())
return QVariant(); return QVariant();
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]); auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
switch (role) switch (role)
{ {
case VersionPointerRole: case VersionPointerRole:
return qVariantFromValue(m_vlist[index.row()]); return qVariantFromValue(m_vlist[index.row()]);
case VersionIdRole: case VersionIdRole:
return version->descriptor(); return version->descriptor();
case VersionRole: case VersionRole:
return version->id.toString(); return version->id.toString();
case RecommendedRole: case RecommendedRole:
return version->recommended; return version->recommended;
case PathRole: case PathRole:
return version->path; return version->path;
case ArchitectureRole: case ArchitectureRole:
return version->arch; return version->arch;
default: default:
return QVariant(); return QVariant();
} }
} }
BaseVersionList::RoleList JavaInstallList::providesRoles() const BaseVersionList::RoleList JavaInstallList::providesRoles() const
{ {
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole}; return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
} }
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions) void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{ {
beginResetModel(); beginResetModel();
m_vlist = versions; m_vlist = versions;
m_loaded = true; sortVersions();
sortVersions(); if(m_vlist.size())
if(m_vlist.size()) {
{ auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]);
auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]); best->recommended = true;
best->recommended = true; }
} endResetModel();
endResetModel(); m_status = Status::Done;
m_loadTask.reset();
} }
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right) bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
{ {
auto rleft = std::dynamic_pointer_cast<JavaInstall>(left); auto rleft = std::dynamic_pointer_cast<JavaInstall>(left);
auto rright = std::dynamic_pointer_cast<JavaInstall>(right); auto rright = std::dynamic_pointer_cast<JavaInstall>(right);
return (*rleft) > (*rright); return (*rleft) > (*rright);
} }
void JavaInstallList::sortVersions() void JavaInstallList::sortVersions()
{ {
beginResetModel(); beginResetModel();
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas); std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
endResetModel(); endResetModel();
} }
JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task() JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task()
{ {
m_list = vlist; m_list = vlist;
m_currentRecommended = NULL; m_currentRecommended = NULL;
} }
JavaListLoadTask::~JavaListLoadTask() JavaListLoadTask::~JavaListLoadTask()
@@ -123,64 +144,65 @@ JavaListLoadTask::~JavaListLoadTask()
void JavaListLoadTask::executeTask() void JavaListLoadTask::executeTask()
{ {
setStatus(tr("Detecting Java installations...")); setStatus(tr("Detecting Java installations..."));
JavaUtils ju; JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths(); 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(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>))); connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress); connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: "; qDebug() << "Probing the following Java paths: ";
int id = 0; int id = 0;
for(QString candidate : candidate_paths) for(QString candidate : candidate_paths)
{ {
qDebug() << " " << candidate; qDebug() << " " << candidate;
auto candidate_checker = new JavaChecker(); auto candidate_checker = new JavaChecker();
candidate_checker->m_path = candidate; candidate_checker->m_path = candidate;
candidate_checker->m_id = id; candidate_checker->m_id = id;
m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker)); m_job->addJavaCheckerAction(JavaCheckerPtr(candidate_checker));
id++; id++;
} }
m_job->start(); m_job->start();
} }
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results) void JavaListLoadTask::javaCheckerFinished()
{ {
QList<JavaInstallPtr> candidates; QList<JavaInstallPtr> candidates;
auto results = m_job->getResults();
qDebug() << "Found the following valid Java installations:"; qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results) for(JavaCheckResult result : results)
{ {
if(result.validity == JavaCheckResult::Validity::Valid) if(result.validity == JavaCheckResult::Validity::Valid)
{ {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion; javaVersion->id = result.javaVersion;
javaVersion->arch = result.mojangPlatform; javaVersion->arch = result.mojangPlatform;
javaVersion->path = result.path; javaVersion->path = result.path;
candidates.append(javaVersion); candidates.append(javaVersion);
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path; qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
} }
} }
QList<BaseVersionPtr> javas_bvp; QList<BaseVersionPtr> javas_bvp;
for (auto java : candidates) for (auto java : candidates)
{ {
//qDebug() << java->id << java->arch << " at " << java->path; //qDebug() << java->id << java->arch << " at " << java->path;
BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java); BaseVersionPtr bp_java = std::dynamic_pointer_cast<BaseVersion>(java);
if (bp_java) if (bp_java)
{ {
javas_bvp.append(java); javas_bvp.append(java);
} }
} }
m_list->updateListData(javas_bvp); m_list->updateListData(javas_bvp);
emitSucceeded(); emitSucceeded();
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,48 +24,60 @@
#include "JavaCheckerJob.h" #include "JavaCheckerJob.h"
#include "JavaInstall.h" #include "JavaInstall.h"
#include "QObjectPtr.h"
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
class JavaListLoadTask; class JavaListLoadTask;
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
{ {
Q_OBJECT Q_OBJECT
enum class Status
{
NotDone,
InProgress,
Done
};
public: public:
explicit JavaInstallList(QObject *parent = 0); explicit JavaInstallList(QObject *parent = 0);
virtual Task *getLoadTask() override; shared_qobject_ptr<Task> getLoadTask() override;
virtual bool isLoaded() override; bool isLoaded() override;
virtual const BaseVersionPtr at(int i) const override; const BaseVersionPtr at(int i) const override;
virtual int count() const override; int count() const override;
virtual void sortVersions() override; void sortVersions() override;
virtual QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
virtual RoleList providesRoles() const override; RoleList providesRoles() const override;
public slots: public slots:
virtual void updateListData(QList<BaseVersionPtr> versions) override; void updateListData(QList<BaseVersionPtr> versions) override;
protected: protected:
QList<BaseVersionPtr> m_vlist; void load();
shared_qobject_ptr<Task> getCurrentTask();
bool m_loaded = false; protected:
Status m_status = Status::NotDone;
shared_qobject_ptr<JavaListLoadTask> m_loadTask;
QList<BaseVersionPtr> m_vlist;
}; };
class JavaListLoadTask : public Task class JavaListLoadTask : public Task
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit JavaListLoadTask(JavaInstallList *vlist); explicit JavaListLoadTask(JavaInstallList *vlist);
~JavaListLoadTask(); virtual ~JavaListLoadTask();
virtual void executeTask(); void executeTask() override;
public slots: public slots:
void javaCheckerFinished(QList<JavaCheckResult> results); void javaCheckerFinished();
protected: protected:
std::shared_ptr<JavaCheckerJob> m_job; shared_qobject_ptr<JavaCheckerJob> m_job;
JavaInstallList *m_list; JavaInstallList *m_list;
JavaInstall *m_currentRecommended; JavaInstall *m_currentRecommended;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,231 +22,324 @@
#include <QDebug> #include <QDebug>
#include "java/JavaUtils.h" #include "java/JavaUtils.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaInstallList.h" #include "java/JavaInstallList.h"
#include "FileSystem.h" #include "FileSystem.h"
#define IBUS "@im=ibus"
JavaUtils::JavaUtils() JavaUtils::JavaUtils()
{ {
} }
#ifdef Q_OS_LINUX
static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
{
QDir mmcBin(QCoreApplication::applicationDirPath());
auto items = LD_LIBRARY_PATH.split(':');
QStringList final;
for(auto & item: items)
{
QDir test(item);
if(test == mmcBin)
{
qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
continue;
}
final.append(item);
}
return final.join(':');
}
#endif
QProcessEnvironment CleanEnviroment()
{
// prepare the process environment
QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
QProcessEnvironment env;
QStringList ignored =
{
"JAVA_ARGS",
"CLASSPATH",
"CONFIGPATH",
"JAVA_HOME",
"JRE_HOME",
"_JAVA_OPTIONS",
"JAVA_OPTIONS",
"JAVA_TOOL_OPTIONS"
};
for(auto key: rawenv.keys())
{
auto value = rawenv.value(key);
// filter out dangerous java crap
if(ignored.contains(key))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// filter MultiMC-related things
if(key.startsWith("QT_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
#ifdef Q_OS_LINUX
// Do not pass LD_* variables to java. They were intended for MultiMC
if(key.startsWith("LD_"))
{
qDebug() << "Env: ignoring" << key << value;
continue;
}
// Strip IBus
// IBus is a Linux IME framework. For some reason, it breaks MC?
if (key == "XMODIFIERS" && value.contains(IBUS))
{
QString save = value;
value.replace(IBUS, "");
qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
}
if(key == "GAME_PRELOAD")
{
env.insert("LD_PRELOAD", value);
continue;
}
if(key == "GAME_LIBRARY_PATH")
{
env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
continue;
}
#endif
// qDebug() << "Env: " << key << value;
env.insert(key, value);
}
#ifdef Q_OS_LINUX
// HACK: Workaround for QTBUG42500
if(!env.contains("LD_LIBRARY_PATH"))
{
env.insert("LD_LIBRARY_PATH", "");
}
#endif
return env;
}
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch) JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
{ {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = id; javaVersion->id = id;
javaVersion->arch = arch; javaVersion->arch = arch;
javaVersion->path = path; javaVersion->path = path;
return javaVersion; return javaVersion;
} }
JavaInstallPtr JavaUtils::GetDefaultJava() JavaInstallPtr JavaUtils::GetDefaultJava()
{ {
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = "java"; javaVersion->id = "java";
javaVersion->arch = "unknown"; javaVersion->arch = "unknown";
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32)
javaVersion->path = "javaw"; javaVersion->path = "javaw";
#else #else
javaVersion->path = "java"; javaVersion->path = "java";
#endif #endif
return javaVersion; return javaVersion;
} }
#if defined(Q_OS_WIN32) #if defined(Q_OS_WIN32)
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName) QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
{ {
QList<JavaInstallPtr> javas; QList<JavaInstallPtr> javas;
QString archType = "unknown"; QString archType = "unknown";
if (keyType == KEY_WOW64_64KEY) if (keyType == KEY_WOW64_64KEY)
archType = "64"; archType = "64";
else if (keyType == KEY_WOW64_32KEY) else if (keyType == KEY_WOW64_32KEY)
archType = "32"; archType = "32";
HKEY jreKey; HKEY jreKey;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0, if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, keyName.toStdString().c_str(), 0,
KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS) KEY_READ | keyType | KEY_ENUMERATE_SUB_KEYS, &jreKey) == ERROR_SUCCESS)
{ {
// Read the current type version from the registry. // Read the current type version from the registry.
// This will be used to find any key that contains the JavaHome value. // This will be used to find any key that contains the JavaHome value.
char *value = new char[0]; char *value = new char[0];
DWORD valueSz = 0; DWORD valueSz = 0;
if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) == if (RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz) ==
ERROR_MORE_DATA) ERROR_MORE_DATA)
{ {
value = new char[valueSz]; value = new char[valueSz];
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz); RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
} }
QString recommended = value; QString recommended = value;
TCHAR subKeyName[255]; TCHAR subKeyName[255];
DWORD subKeyNameSize, numSubKeys, retCode; DWORD subKeyNameSize, numSubKeys, retCode;
// Get the number of subkeys // Get the number of subkeys
RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL, RegQueryInfoKey(jreKey, NULL, NULL, NULL, &numSubKeys, NULL, NULL, NULL, NULL, NULL,
NULL, NULL); NULL, NULL);
// Iterate until RegEnumKeyEx fails // Iterate until RegEnumKeyEx fails
if (numSubKeys > 0) if (numSubKeys > 0)
{ {
for (int i = 0; i < numSubKeys; i++) for (DWORD i = 0; i < numSubKeys; i++)
{ {
subKeyNameSize = 255; subKeyNameSize = 255;
retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL, retCode = RegEnumKeyEx(jreKey, i, subKeyName, &subKeyNameSize, NULL, NULL, NULL,
NULL); NULL);
if (retCode == ERROR_SUCCESS) if (retCode == ERROR_SUCCESS)
{ {
// Now open the registry key for the version that we just got. // Now open the registry key for the version that we just got.
QString newKeyName = keyName + "\\" + subKeyName; QString newKeyName = keyName + "\\" + subKeyName;
HKEY newKey; HKEY newKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0, if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS) KEY_READ | KEY_WOW64_64KEY, &newKey) == ERROR_SUCCESS)
{ {
// Read the JavaHome value to find where Java is installed. // Read the JavaHome value to find where Java is installed.
value = new char[0]; value = new char[0];
valueSz = 0; valueSz = 0;
if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
&valueSz) == ERROR_MORE_DATA) &valueSz) == ERROR_MORE_DATA)
{ {
value = new char[valueSz]; value = new char[valueSz];
RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value, RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
&valueSz); &valueSz);
// Now, we construct the version object and add it to the list. // Now, we construct the version object and add it to the list.
JavaInstallPtr javaVersion(new JavaInstall()); JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = subKeyName; javaVersion->id = subKeyName;
javaVersion->arch = archType; javaVersion->arch = archType;
javaVersion->path = javaVersion->path =
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe"); QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
javas.append(javaVersion); javas.append(javaVersion);
} }
RegCloseKey(newKey); RegCloseKey(newKey);
} }
} }
} }
} }
RegCloseKey(jreKey); RegCloseKey(jreKey);
} }
return javas; return javas;
} }
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
QList<JavaInstallPtr> java_candidates; QList<JavaInstallPtr> java_candidates;
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey( QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey( QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey( QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment"); KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey( QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit"); KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
java_candidates.append(JRE64s); java_candidates.append(JRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK64s); java_candidates.append(JDK64s);
java_candidates.append(JRE32s); java_candidates.append(JRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe")); java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK32s); java_candidates.append(JDK32s);
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path)); java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
QList<QString> candidates; QList<QString> candidates;
for(JavaInstallPtr java_candidate : java_candidates) for(JavaInstallPtr java_candidate : java_candidates)
{ {
if(!candidates.contains(java_candidate->path)) if(!candidates.contains(java_candidate->path))
{ {
candidates.append(java_candidate->path); candidates.append(java_candidate->path);
} }
} }
return candidates; return candidates;
} }
#elif defined(Q_OS_MAC) #elif defined(Q_OS_MAC)
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java"); javas.append("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java");
javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java"); javas.append("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java");
javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java"); javas.append("/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java");
QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/"); QDir libraryJVMDir("/Library/Java/JavaVirtualMachines/");
QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); QStringList libraryJVMJavas = libraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString &java, libraryJVMJavas) { foreach (const QString &java, libraryJVMJavas) {
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java"); javas.append(libraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/jre/bin/java");
} }
QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/"); QDir systemLibraryJVMDir("/System/Library/Java/JavaVirtualMachines/");
QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); QStringList systemLibraryJVMJavas = systemLibraryJVMDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
foreach (const QString &java, systemLibraryJVMJavas) { foreach (const QString &java, systemLibraryJVMJavas) {
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Home/bin/java");
javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java"); javas.append(systemLibraryJVMDir.absolutePath() + "/" + java + "/Contents/Commands/java");
} }
return javas; return javas;
} }
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
qDebug() << "Linux Java detection incomplete - defaulting to \"java\""; qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
auto scanJavaDir = [&](const QString & dirPath) auto scanJavaDir = [&](const QString & dirPath)
{ {
QDir dir(dirPath); QDir dir(dirPath);
if(!dir.exists()) if(!dir.exists())
return; return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for(auto & entry: entries) for(auto & entry: entries)
{ {
QString prefix; QString prefix;
if(entry.isAbsolute()) if(entry.isAbsolute())
{ {
prefix = entry.absoluteFilePath(); prefix = entry.absoluteFilePath();
} }
else else
{ {
prefix = entry.filePath(); prefix = entry.filePath();
} }
javas.append(FS::PathCombine(prefix, "jre/bin/java")); javas.append(FS::PathCombine(prefix, "jre/bin/java"));
javas.append(FS::PathCombine(prefix, "bin/java")); javas.append(FS::PathCombine(prefix, "bin/java"));
} }
}; };
// oracle RPMs // oracle RPMs
scanJavaDir("/usr/java"); scanJavaDir("/usr/java");
// general locations used by distro packaging // general locations used by distro packaging
scanJavaDir("/usr/lib/jvm"); scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib32/jvm"); scanJavaDir("/usr/lib32/jvm");
// javas stored in MultiMC's folder // javas stored in MultiMC's folder
scanJavaDir("java"); scanJavaDir("java");
return javas; return javas;
} }
#else #else
QList<QString> JavaUtils::FindJavaPaths() QList<QString> JavaUtils::FindJavaPaths()
{ {
qDebug() << "Unknown operating system build - defaulting to \"java\""; qDebug() << "Unknown operating system build - defaulting to \"java\"";
QList<QString> javas; QList<QString> javas;
javas.append(this->GetDefaultJava()->path); javas.append(this->GetDefaultJava()->path);
return javas; return javas;
} }
#endif #endif

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
#include <QStringList> #include <QStringList>
#include "JavaCheckerJob.h"
#include "JavaChecker.h" #include "JavaChecker.h"
#include "JavaInstallList.h" #include "JavaInstallList.h"
@@ -27,17 +26,19 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
QProcessEnvironment CleanEnviroment();
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
JavaUtils(); JavaUtils();
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown"); JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
QList<QString> FindJavaPaths(); QList<QString> FindJavaPaths();
JavaInstallPtr GetDefaultJava(); JavaInstallPtr GetDefaultJava();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName); QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
#endif #endif
}; };

View File

@@ -6,107 +6,116 @@
JavaVersion & JavaVersion::operator=(const QString & javaVersionString) JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
{ {
m_string = javaVersionString; m_string = javaVersionString;
auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
{ {
auto str = match.captured(what); auto str = match.captured(what);
if(str.isEmpty()) if(str.isEmpty())
{ {
return 0; return 0;
} }
return str.toInt(); return str.toInt();
}; };
QRegularExpression pattern; QRegularExpression pattern;
if(javaVersionString.startsWith("1.")) if(javaVersionString.startsWith("1."))
{ {
pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?"); pattern = QRegularExpression ("1[.](?<major>[0-9]+)([.](?<minor>[0-9]+))?(_(?<security>[0-9]+)?)?(-(?<prerelease>[a-zA-Z0-9]+))?");
} }
else else
{ {
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?"); pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
} }
auto match = pattern.match(m_string); auto match = pattern.match(m_string);
m_parseable = match.hasMatch(); m_parseable = match.hasMatch();
m_major = getCapturedInteger(match, "major"); m_major = getCapturedInteger(match, "major");
m_minor = getCapturedInteger(match, "minor"); m_minor = getCapturedInteger(match, "minor");
m_security = getCapturedInteger(match, "security"); m_security = getCapturedInteger(match, "security");
m_prerelease = match.captured("prerelease"); m_prerelease = match.captured("prerelease");
return *this; return *this;
} }
JavaVersion::JavaVersion(const QString &rhs) JavaVersion::JavaVersion(const QString &rhs)
{ {
operator=(rhs); operator=(rhs);
} }
QString JavaVersion::toString() QString JavaVersion::toString()
{ {
return m_string; return m_string;
} }
bool JavaVersion::requiresPermGen() bool JavaVersion::requiresPermGen()
{ {
if(m_parseable) if(m_parseable)
{ {
return m_major < 8; return m_major < 8;
} }
return true; return true;
} }
bool JavaVersion::operator<(const JavaVersion &rhs) bool JavaVersion::operator<(const JavaVersion &rhs)
{ {
if(m_parseable && rhs.m_parseable) if(m_parseable && rhs.m_parseable)
{ {
if(m_major < rhs.m_major) auto major = m_major;
return true; auto rmajor = rhs.m_major;
if(m_major > rhs.m_major)
return false;
if(m_minor < rhs.m_minor)
return true;
if(m_minor > rhs.m_minor)
return false;
if(m_security < rhs.m_security)
return true;
if(m_security > rhs.m_security)
return false;
// everything else being equal, consider prerelease status // HACK: discourage using java 9
bool thisPre = !m_prerelease.isEmpty(); if(major > 8)
bool rhsPre = !rhs.m_prerelease.isEmpty(); major = -major;
if(thisPre && !rhsPre) if(rmajor > 8)
{ rmajor = -rmajor;
// this is a prerelease and the other one isn't -> lesser
return true; if(major < rmajor)
} return true;
else if(!thisPre && rhsPre) if(major > rmajor)
{ return false;
// this isn't a prerelease and the other one is -> greater if(m_minor < rhs.m_minor)
return false; return true;
} if(m_minor > rhs.m_minor)
else if(thisPre && rhsPre) return false;
{ if(m_security < rhs.m_security)
// both are prereleases - use natural compare... return true;
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0; if(m_security > rhs.m_security)
} return false;
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false; // everything else being equal, consider prerelease status
} bool thisPre = !m_prerelease.isEmpty();
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0; bool rhsPre = !rhs.m_prerelease.isEmpty();
if(thisPre && !rhsPre)
{
// this is a prerelease and the other one isn't -> lesser
return true;
}
else if(!thisPre && rhsPre)
{
// this isn't a prerelease and the other one is -> greater
return false;
}
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
}
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false;
}
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
} }
bool JavaVersion::operator==(const JavaVersion &rhs) bool JavaVersion::operator==(const JavaVersion &rhs)
{ {
if(m_parseable && rhs.m_parseable) if(m_parseable && rhs.m_parseable)
{ {
return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease; return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease;
} }
return m_string == rhs.m_string; return m_string == rhs.m_string;
} }
bool JavaVersion::operator>(const JavaVersion &rhs) bool JavaVersion::operator>(const JavaVersion &rhs)
{ {
return (!operator<(rhs)) && (!operator==(rhs)); return (!operator<(rhs)) && (!operator==(rhs));
} }

View File

@@ -3,40 +3,48 @@
#include "multimc_logic_export.h" #include "multimc_logic_export.h"
#include <QString> #include <QString>
// NOTE: apparently the GNU C library pollutes the global namespace with these... undef them.
#ifdef major
#undef major
#endif
#ifdef minor
#undef minor
#endif
class MULTIMC_LOGIC_EXPORT JavaVersion class MULTIMC_LOGIC_EXPORT JavaVersion
{ {
friend class JavaVersionTest; friend class JavaVersionTest;
public: public:
JavaVersion() {}; JavaVersion() {};
JavaVersion(const QString & rhs); JavaVersion(const QString & rhs);
JavaVersion & operator=(const QString & rhs); JavaVersion & operator=(const QString & rhs);
bool operator<(const JavaVersion & rhs); bool operator<(const JavaVersion & rhs);
bool operator==(const JavaVersion & rhs); bool operator==(const JavaVersion & rhs);
bool operator>(const JavaVersion & rhs); bool operator>(const JavaVersion & rhs);
bool requiresPermGen(); bool requiresPermGen();
QString toString(); QString toString();
int major() int major()
{ {
return m_major; return m_major;
} }
int minor() int minor()
{ {
return m_minor; return m_minor;
} }
int security() int security()
{ {
return m_security; return m_security;
} }
private: private:
QString m_string; QString m_string;
int m_major = 0; int m_major = 0;
int m_minor = 0; int m_minor = 0;
int m_security = 0; int m_security = 0;
bool m_parseable = false; bool m_parseable = false;
QString m_prerelease; QString m_prerelease;
}; };

View File

@@ -5,110 +5,110 @@
class JavaVersionTest : public QObject class JavaVersionTest : public QObject
{ {
Q_OBJECT Q_OBJECT
private private
slots: slots:
void test_Parse_data() void test_Parse_data()
{ {
QTest::addColumn<QString>("string"); QTest::addColumn<QString>("string");
QTest::addColumn<int>("major"); QTest::addColumn<int>("major");
QTest::addColumn<int>("minor"); QTest::addColumn<int>("minor");
QTest::addColumn<int>("security"); QTest::addColumn<int>("security");
QTest::addColumn<QString>("prerelease"); QTest::addColumn<QString>("prerelease");
QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString(); QTest::newRow("old format") << "1.6.0_33" << 6 << 0 << 33 << QString();
QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea"; QTest::newRow("old format prerelease") << "1.9.0_1-ea" << 9 << 0 << 1 << "ea";
QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString(); QTest::newRow("new format major") << "9" << 9 << 0 << 0 << QString();
QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString(); QTest::newRow("new format minor") << "9.1" << 9 << 1 << 0 << QString();
QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString(); QTest::newRow("new format security") << "9.0.1" << 9 << 0 << 1 << QString();
QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea"; QTest::newRow("new format prerelease") << "9-ea" << 9 << 0 << 0 << "ea";
QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea"; QTest::newRow("new format long prerelease") << "9.0.1-ea" << 9 << 0 << 1 << "ea";
} }
void test_Parse() void test_Parse()
{ {
QFETCH(QString, string); QFETCH(QString, string);
QFETCH(int, major); QFETCH(int, major);
QFETCH(int, minor); QFETCH(int, minor);
QFETCH(int, security); QFETCH(int, security);
QFETCH(QString, prerelease); QFETCH(QString, prerelease);
JavaVersion test(string); JavaVersion test(string);
QCOMPARE(test.m_string, string); QCOMPARE(test.m_string, string);
QCOMPARE(test.toString(), string); QCOMPARE(test.toString(), string);
QCOMPARE(test.m_major, major); QCOMPARE(test.m_major, major);
QCOMPARE(test.m_minor, minor); QCOMPARE(test.m_minor, minor);
QCOMPARE(test.m_security, security); QCOMPARE(test.m_security, security);
QCOMPARE(test.m_prerelease, prerelease); QCOMPARE(test.m_prerelease, prerelease);
} }
void test_Sort_data() void test_Sort_data()
{ {
QTest::addColumn<QString>("lhs"); QTest::addColumn<QString>("lhs");
QTest::addColumn<QString>("rhs"); QTest::addColumn<QString>("rhs");
QTest::addColumn<bool>("smaller"); QTest::addColumn<bool>("smaller");
QTest::addColumn<bool>("equal"); QTest::addColumn<bool>("equal");
QTest::addColumn<bool>("bigger"); QTest::addColumn<bool>("bigger");
// old format and new format equivalence // old format and new format equivalence
QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false; QTest::newRow("1.6.0_33 == 6.0.33") << "1.6.0_33" << "6.0.33" << false << true << false;
// old format major version // old format major version
QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false; QTest::newRow("1.5.0_33 < 1.6.0_33") << "1.5.0_33" << "1.6.0_33" << true << false << false;
// new format - first release vs first security patch // new format - first release vs first security patch
QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false; QTest::newRow("9 < 9.0.1") << "9" << "9.0.1" << true << false << false;
QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true; QTest::newRow("9.0.1 > 9") << "9.0.1" << "9" << false << false << true;
// new format - first minor vs first release/security patch // new format - first minor vs first release/security patch
QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true; QTest::newRow("9.1 > 9.0.1") << "9.1" << "9.0.1" << false << false << true;
QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false; QTest::newRow("9.0.1 < 9.1") << "9.0.1" << "9.1" << true << false << false;
QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true; QTest::newRow("9.1 > 9") << "9.1" << "9" << false << false << true;
QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false; QTest::newRow("9 > 9.1") << "9" << "9.1" << true << false << false;
// new format - omitted numbers // new format - omitted numbers
QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false; QTest::newRow("9 == 9.0") << "9" << "9.0" << false << true << false;
QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false; QTest::newRow("9 == 9.0.0") << "9" << "9.0.0" << false << true << false;
QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false; QTest::newRow("9.0 == 9.0.0") << "9.0" << "9.0.0" << false << true << false;
// early access and prereleases compared to final release // early access and prereleases compared to final release
QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false; QTest::newRow("9-ea < 9") << "9-ea" << "9" << true << false << false;
QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false; QTest::newRow("9 < 9.0.1-ea") << "9" << "9.0.1-ea" << true << false << false;
QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true; QTest::newRow("9.0.1-ea > 9") << "9.0.1-ea" << "9" << false << false << true;
// prerelease difference only testing // prerelease difference only testing
QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false; QTest::newRow("9-1 == 9-1") << "9-1" << "9-1" << false << true << false;
QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false; QTest::newRow("9-1 < 9-2") << "9-1" << "9-2" << true << false << false;
QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false; QTest::newRow("9-5 < 9-20") << "9-5" << "9-20" << true << false << false;
QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false; QTest::newRow("9-rc1 < 9-rc2") << "9-rc1" << "9-rc2" << true << false << false;
QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false; QTest::newRow("9-rc5 < 9-rc20") << "9-rc5" << "9-rc20" << true << false << false;
QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false; QTest::newRow("9-rc < 9-rc2") << "9-rc" << "9-rc2" << true << false << false;
QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false; QTest::newRow("9-ea < 9-rc") << "9-ea" << "9-rc" << true << false << false;
} }
void test_Sort() void test_Sort()
{ {
QFETCH(QString, lhs); QFETCH(QString, lhs);
QFETCH(QString, rhs); QFETCH(QString, rhs);
QFETCH(bool, smaller); QFETCH(bool, smaller);
QFETCH(bool, equal); QFETCH(bool, equal);
QFETCH(bool, bigger); QFETCH(bool, bigger);
JavaVersion lver(lhs); JavaVersion lver(lhs);
JavaVersion rver(rhs); JavaVersion rver(rhs);
QCOMPARE(lver < rver, smaller); QCOMPARE(lver < rver, smaller);
QCOMPARE(lver == rver, equal); QCOMPARE(lver == rver, equal);
QCOMPARE(lver > rver, bigger); QCOMPARE(lver > rver, bigger);
} }
void test_PermGen_data() void test_PermGen_data()
{ {
QTest::addColumn<QString>("version"); QTest::addColumn<QString>("version");
QTest::addColumn<bool>("needs_permgen"); QTest::addColumn<bool>("needs_permgen");
QTest::newRow("1.6.0_33") << "1.6.0_33" << true; QTest::newRow("1.6.0_33") << "1.6.0_33" << true;
QTest::newRow("1.7.0_60") << "1.7.0_60" << true; QTest::newRow("1.7.0_60") << "1.7.0_60" << true;
QTest::newRow("1.8.0_22") << "1.8.0_22" << false; QTest::newRow("1.8.0_22") << "1.8.0_22" << false;
QTest::newRow("9-ea") << "9-ea" << false; QTest::newRow("9-ea") << "9-ea" << false;
QTest::newRow("9.2.4") << "9.2.4" << false; QTest::newRow("9.2.4") << "9.2.4" << false;
} }
void test_PermGen() void test_PermGen()
{ {
QFETCH(QString, version); QFETCH(QString, version);
QFETCH(bool, needs_permgen); QFETCH(bool, needs_permgen);
JavaVersion v(version); JavaVersion v(version);
QCOMPARE(needs_permgen, v.requiresPermGen()); QCOMPARE(needs_permgen, v.requiresPermGen());
} }
}; };
QTEST_GUILESS_MAIN(JavaVersionTest) QTEST_GUILESS_MAIN(JavaVersionTest)

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,115 +22,115 @@
void CheckJava::executeTask() void CheckJava::executeTask()
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
auto settings = instance->settings(); auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString()); m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool(); bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath); auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty()) if (realJavaPath.isEmpty())
{ {
if (perInstance) if (perInstance)
{ {
emit logLine( emit logLine(
tr("The java binary \"%1\" couldn't be found. Please fix the java path " tr("The java binary \"%1\" couldn't be found. Please fix the java path "
"override in the instance's settings or disable it.").arg(m_javaPath), "override in the instance's settings or disable it.").arg(m_javaPath),
MessageLevel::Warning); MessageLevel::Warning);
} }
else else
{ {
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in " emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
"the settings.").arg(m_javaPath), "the settings.").arg(m_javaPath),
MessageLevel::Warning); MessageLevel::Warning);
} }
emitFailed(tr("Java path is not valid.")); emitFailed(tr("Java path is not valid."));
return; return;
} }
else else
{ {
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC); emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
} }
QFileInfo javaInfo(realJavaPath); QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch(); qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong(); auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString(); auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString(); auto storedVersion = settings->get("JavaVersion").toString();
m_javaUnixTime = javaUnixTime; m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check! // if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0) 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); emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished); connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath; m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck(); m_JavaChecker->performCheck();
return; return;
} }
else else
{ {
auto verString = instance->settings()->get("JavaVersion").toString(); auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString(); auto archString = instance->settings()->get("JavaArchitecture").toString();
printJavaInfo(verString, archString); printJavaInfo(verString, archString);
} }
emitSucceeded(); emitSucceeded();
} }
void CheckJava::checkJavaFinished(JavaCheckResult result) void CheckJava::checkJavaFinished(JavaCheckResult result)
{ {
switch (result.validity) switch (result.validity)
{ {
case JavaCheckResult::Validity::Errored: case JavaCheckResult::Validity::Errored:
{ {
// Error message displayed if java can't start // Error message displayed if java can't start
emit logLine(tr("Could not start java:"), MessageLevel::Error); emit logLine(tr("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC); emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
printSystemInfo(false, false); printSystemInfo(false, false);
emitFailed(tr("Could not start java!")); emitFailed(tr("Could not start java!"));
return; return;
} }
case JavaCheckResult::Validity::ReturnedInvalidData: case JavaCheckResult::Validity::ReturnedInvalidData:
{ {
emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error); emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC); emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
printSystemInfo(false, false); printSystemInfo(false, false);
emitSucceeded(); emitSucceeded();
return; return;
} }
case JavaCheckResult::Validity::Valid: case JavaCheckResult::Validity::Valid:
{ {
auto instance = m_parent->instance(); auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform); instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaTimestamp", m_javaUnixTime); instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded(); emitSucceeded();
return; return;
} }
} }
} }
void CheckJava::printJavaInfo(const QString& version, const QString& architecture) void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
{ {
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC); emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
printSystemInfo(true, architecture == "64"); printSystemInfo(true, architecture == "64");
} }
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
{ {
auto cpu64 = Sys::isCPU64bit(); auto cpu64 = Sys::isCPU64bit();
auto system64 = Sys::isSystem64bit(); auto system64 = Sys::isSystem64bit();
if(cpu64 != system64) if(cpu64 != system64)
{ {
emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error); emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
} }
if(javaIsKnown) if(javaIsKnown)
{ {
if(javaIs64bit != system64) if(javaIs64bit != system64)
{ {
emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error); emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,25 +21,25 @@
class CheckJava: public LaunchStep class CheckJava: public LaunchStep
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){}; explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
virtual ~CheckJava() {}; virtual ~CheckJava() {};
virtual void executeTask(); virtual void executeTask();
virtual bool canAbort() const virtual bool canAbort() const
{ {
return false; return false;
} }
private slots: private slots:
void checkJavaFinished(JavaCheckResult result); void checkJavaFinished(JavaCheckResult result);
private: private:
void printJavaInfo(const QString & version, const QString & architecture); void printJavaInfo(const QString & version, const QString & architecture);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit); void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private: private:
QString m_javaPath; QString m_javaPath;
qlonglong m_javaUnixTime; qlonglong m_javaUnixTime;
JavaCheckerPtr m_JavaChecker; JavaCheckerPtr m_JavaChecker;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,10 +18,10 @@
void LaunchStep::bind(LaunchTask *parent) void LaunchStep::bind(LaunchTask *parent)
{ {
m_parent = parent; m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished); connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested); connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested);
} }

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,28 +23,28 @@
class LaunchTask; class LaunchTask;
class LaunchStep: public Task class LaunchStep: public Task
{ {
Q_OBJECT Q_OBJECT
public: /* methods */ public: /* methods */
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent) explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
{ {
bind(parent); bind(parent);
}; };
virtual ~LaunchStep() {}; virtual ~LaunchStep() {};
protected: /* methods */ private: /* methods */
virtual void bind(LaunchTask *parent); void bind(LaunchTask *parent);
signals: signals:
void logLines(QStringList lines, MessageLevel::Enum level); void logLines(QStringList lines, MessageLevel::Enum level);
void logLine(QString line, MessageLevel::Enum level); void logLine(QString line, MessageLevel::Enum level);
void readyForLaunch(); void readyForLaunch();
void progressReportingRequest(); void progressReportingRequest();
public slots: public slots:
virtual void proceed() {}; virtual void proceed() {};
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends // called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
virtual void finalize() {}; virtual void finalize() {};
protected: /* data */ protected: /* data */
LaunchTask *m_parent; LaunchTask *m_parent;
}; };

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2017 MultiMC Contributors /* Copyright 2013-2019 MultiMC Contributors
* *
* Authors: Orochimarufan <orochimarufan.x3@gmail.com> * Authors: Orochimarufan <orochimarufan.x3@gmail.com>
* *
@@ -30,251 +30,251 @@
void LaunchTask::init() void LaunchTask::init()
{ {
m_instance->setRunning(true); m_instance->setRunning(true);
} }
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst) shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
{ {
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst)); shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init(); proc->init();
return proc; return proc;
} }
LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance) LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{ {
} }
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step) void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
{ {
m_steps.append(step); m_steps.append(step);
} }
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step) void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
{ {
m_steps.prepend(step); m_steps.prepend(step);
} }
void LaunchTask::executeTask() void LaunchTask::executeTask()
{ {
m_instance->setCrashed(false); m_instance->setCrashed(false);
if(!m_steps.size()) if(!m_steps.size())
{ {
state = LaunchTask::Finished; state = LaunchTask::Finished;
emitSucceeded(); emitSucceeded();
} }
state = LaunchTask::Running; state = LaunchTask::Running;
onStepFinished(); onStepFinished();
} }
void LaunchTask::onReadyForLaunch() void LaunchTask::onReadyForLaunch()
{ {
state = LaunchTask::Waiting; state = LaunchTask::Waiting;
emit readyForLaunch(); emit readyForLaunch();
} }
void LaunchTask::onStepFinished() void LaunchTask::onStepFinished()
{ {
// initial -> just start the first step // initial -> just start the first step
if(currentStep == -1) if(currentStep == -1)
{ {
currentStep ++; currentStep ++;
m_steps[currentStep]->start(); m_steps[currentStep]->start();
return; return;
} }
auto step = m_steps[currentStep]; auto step = m_steps[currentStep];
if(step->successful()) if(step->wasSuccessful())
{ {
// end? // end?
if(currentStep == m_steps.size() - 1) if(currentStep == m_steps.size() - 1)
{ {
finalizeSteps(true, QString()); finalizeSteps(true, QString());
} }
else else
{ {
currentStep ++; currentStep ++;
step = m_steps[currentStep]; step = m_steps[currentStep];
step->start(); step->start();
} }
} }
else else
{ {
finalizeSteps(false, step->failReason()); finalizeSteps(false, step->failReason());
} }
} }
void LaunchTask::finalizeSteps(bool successful, const QString& error) void LaunchTask::finalizeSteps(bool successful, const QString& error)
{ {
for(auto step = currentStep; step >= 0; step--) for(auto step = currentStep; step >= 0; step--)
{ {
m_steps[step]->finalize(); m_steps[step]->finalize();
} }
if(successful) if(successful)
{ {
emitSucceeded(); emitSucceeded();
} }
else else
{ {
emitFailed(error); emitFailed(error);
} }
} }
void LaunchTask::onProgressReportingRequested() void LaunchTask::onProgressReportingRequested()
{ {
state = LaunchTask::Waiting; state = LaunchTask::Waiting;
emit requestProgress(m_steps[currentStep].get()); emit requestProgress(m_steps[currentStep].get());
} }
void LaunchTask::setCensorFilter(QMap<QString, QString> filter) void LaunchTask::setCensorFilter(QMap<QString, QString> filter)
{ {
m_censorFilter = filter; m_censorFilter = filter;
} }
QString LaunchTask::censorPrivateInfo(QString in) QString LaunchTask::censorPrivateInfo(QString in)
{ {
auto iter = m_censorFilter.begin(); auto iter = m_censorFilter.begin();
while (iter != m_censorFilter.end()) while (iter != m_censorFilter.end())
{ {
in.replace(iter.key(), iter.value()); in.replace(iter.key(), iter.value());
iter++; iter++;
} }
return in; return in;
} }
void LaunchTask::proceed() void LaunchTask::proceed()
{ {
if(state != LaunchTask::Waiting) if(state != LaunchTask::Waiting)
{ {
return; return;
} }
m_steps[currentStep]->proceed(); m_steps[currentStep]->proceed();
} }
bool LaunchTask::canAbort() const bool LaunchTask::canAbort() const
{ {
switch(state) switch(state)
{ {
case LaunchTask::Aborted: case LaunchTask::Aborted:
case LaunchTask::Failed: case LaunchTask::Failed:
case LaunchTask::Finished: case LaunchTask::Finished:
return false; return false;
case LaunchTask::NotStarted: case LaunchTask::NotStarted:
return true; return true;
case LaunchTask::Running: case LaunchTask::Running:
case LaunchTask::Waiting: case LaunchTask::Waiting:
{ {
auto step = m_steps[currentStep]; auto step = m_steps[currentStep];
return step->canAbort(); return step->canAbort();
} }
} }
return false; return false;
} }
bool LaunchTask::abort() bool LaunchTask::abort()
{ {
switch(state) switch(state)
{ {
case LaunchTask::Aborted: case LaunchTask::Aborted:
case LaunchTask::Failed: case LaunchTask::Failed:
case LaunchTask::Finished: case LaunchTask::Finished:
return true; return true;
case LaunchTask::NotStarted: case LaunchTask::NotStarted:
{ {
state = LaunchTask::Aborted; state = LaunchTask::Aborted;
emitFailed("Aborted"); emitFailed("Aborted");
return true; return true;
} }
case LaunchTask::Running: case LaunchTask::Running:
case LaunchTask::Waiting: case LaunchTask::Waiting:
{ {
auto step = m_steps[currentStep]; auto step = m_steps[currentStep];
if(!step->canAbort()) if(!step->canAbort())
{ {
return false; return false;
} }
if(step->abort()) if(step->abort())
{ {
state = LaunchTask::Aborted; state = LaunchTask::Aborted;
return true; return true;
} }
} }
default: default:
break; break;
} }
return false; return false;
} }
shared_qobject_ptr<LogModel> LaunchTask::getLogModel() shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
{ {
if(!m_logModel) if(!m_logModel)
{ {
m_logModel.reset(new LogModel()); m_logModel.reset(new LogModel());
m_logModel->setMaxLines(m_instance->getConsoleMaxLines()); m_logModel->setMaxLines(m_instance->getConsoleMaxLines());
m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow()); m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow());
// FIXME: should this really be here? // FIXME: should this really be here?
m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n" m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
"You may have to fix your mods because the game is still logging to files and" "You may have to fix your mods because the game is still logging to files and"
" likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines())); " likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines()));
} }
return m_logModel; return m_logModel;
} }
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel) void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
{ {
for (auto & line: lines) for (auto & line: lines)
{ {
onLogLine(line, defaultLevel); onLogLine(line, defaultLevel);
} }
} }
void LaunchTask::onLogLine(QString line, MessageLevel::Enum level) void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
{ {
// if the launcher part set a log level, use it // if the launcher part set a log level, use it
auto innerLevel = MessageLevel::fromLine(line); auto innerLevel = MessageLevel::fromLine(line);
if(innerLevel != MessageLevel::Unknown) if(innerLevel != MessageLevel::Unknown)
{ {
level = innerLevel; level = innerLevel;
} }
// If the level is still undetermined, guess level // If the level is still undetermined, guess level
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown) if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
{ {
level = m_instance->guessLevel(line, level); level = m_instance->guessLevel(line, level);
} }
// censor private user info // censor private user info
line = censorPrivateInfo(line); line = censorPrivateInfo(line);
auto &model = *getLogModel(); auto &model = *getLogModel();
model.append(level, line); model.append(level, line);
} }
void LaunchTask::emitSucceeded() void LaunchTask::emitSucceeded()
{ {
m_instance->setRunning(false); m_instance->setRunning(false);
Task::emitSucceeded(); Task::emitSucceeded();
} }
void LaunchTask::emitFailed(QString reason) void LaunchTask::emitFailed(QString reason)
{ {
m_instance->setRunning(false); m_instance->setRunning(false);
m_instance->setCrashed(true); m_instance->setCrashed(true);
Task::emitFailed(reason); Task::emitFailed(reason);
} }
QString LaunchTask::substituteVariables(const QString &cmd) const QString LaunchTask::substituteVariables(const QString &cmd) const
{ {
QString out = cmd; QString out = cmd;
auto variables = m_instance->getVariables(); auto variables = m_instance->getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it) for (auto it = variables.begin(); it != variables.end(); ++it)
{ {
out.replace("$" + it.key(), it.value()); out.replace("$" + it.key(), it.value());
} }
auto env = QProcessEnvironment::systemEnvironment(); auto env = QProcessEnvironment::systemEnvironment();
for (auto var : env.keys()) for (auto var : env.keys())
{ {
out.replace("$" + var, env.value(var)); out.replace("$" + var, env.value(var));
} }
return out; return out;
} }

Some files were not shown because too many files have changed in this diff Show More