Compare commits

..

448 Commits
0.4.7 ... 0.5.1

Author SHA1 Message Date
Petr Mrázek
aafac3934b NOISSUE update changelog 2017-02-11 22:53:03 +01:00
Petr Mrázek
17fac2e0df NOISSUE switch paste.ee over to https only 2017-02-08 21:30:39 +01:00
Petr Mrázek
8bbaab334c NOISSUE set line limit and overflow behaviour even for hidden console 2017-02-08 20:01:42 +01:00
Petr Mrázek
155de307bc GH-1802 fix log resize handling
The log could end up with many empty lines because the wrong maximum size was
used during the resize.
2017-02-08 01:59:16 +01:00
Petr Mrázek
2e8d8b60b0 GH-1807 fix 'loggging' typo 2017-02-01 00:44:21 +01:00
Petr Mrázek
c200301673 GH-1801 Do not print the launch script to MultiMC's log. 2017-01-24 08:20:16 +01:00
Petr Mrázek
0a6af3cff3 NOISSUE update changelog 2017-01-22 16:11:01 +01:00
Petr Mrázek
c7f89ec6b5 GH-1798 Do not enable skin upload button without selected account. 2017-01-22 15:45:57 +01:00
Petr Mrázek
cf43abc87e GH-1794 Only write to the instance.cfg once after copying FTB instances. 2017-01-22 15:20:05 +01:00
Petr Mrázek
c421134d49 NOISSUE set the instance settings page default tab back to the first 2017-01-21 20:15:07 +01:00
Petr Mrázek
782384f185 GH-1793 rearrange setup wizard logic to only check if they are needed once 2017-01-21 18:27:16 +01:00
Petr Mrázek
ceb5fc6d75 GH-1790 do not apply system theme on launch if it is selected
This prevents some ugly colors to show up on macOS in most cases.
It still looks ugly right after you switch to the it though.
2017-01-18 02:48:29 +01:00
Petr Mrázek
201d4ac317 GH-1789 fix icon deletion 2017-01-17 23:36:41 +01:00
Petr Mrázek
85320777c6 GH-1788 missed one call wrong call... this is the fix. 2017-01-17 21:19:38 +01:00
Petr Mrázek
ba38991c13 GH-1788 fix missing translation strings 2017-01-17 21:17:36 +01:00
Tim Flynn
98e17998fe GH-1065 Normalize usage of directory vs folder 2017-01-16 22:42:22 +01:00
Petr Mrázek
944ff256b2 NOISSUE add hack for system themes. Maybe it works? 2017-01-15 22:56:03 +01:00
Petr Mrázek
71584fb8cc NOISSUE temporarily disable themes to check if they cause issues on macOS 2017-01-15 20:32:55 +01:00
Petr Mrázek
b2dbaaa9e2 NOISSUE also preserve x.y.z version numbering in the application 2017-01-14 19:44:34 +01:00
Petr Mrázek
0a89b04afd GH-1665 Add line breaks to the fatal error dialogs.
Makes is slightly more readable.
2017-01-14 18:22:50 +01:00
Petr Mrázek
ffa8792c13 NOISSUE always include the hotfix version in the version string
It's 0.5.0, not 0.5.
2017-01-14 18:12:15 +01:00
Petr Mrázek
fbcbddd4d0 GH-1665 put the macOS hint into all of the new fatal error messages. 2017-01-14 16:51:08 +01:00
Petr Mrázek
a6ef0059cc GH-1665 attempt at workaround for MultiMC not starting on macOS Sierra
This tries to detect the issue and instructs the user to fix it by
moving the application to /Applications or ~/Applications.

In addition, several other previously poorly handled fatal errors
now show an error dialog.
2017-01-14 15:47:58 +01:00
Petr Mrázek
3e81e2cb5b NOISSUE do not show the analytics wizard page without analytics in the build 2017-01-13 02:12:40 +01:00
Petr Mrázek
858b490c74 GH-1778: Fix placement of modded Minecraft.jar in OneSix instances. 2017-01-10 23:03:00 +01:00
Petr Mrázek
12c3683ec0 NOISSUE set version to 0.5.0 2017-01-08 05:00:24 +01:00
Petr Mrázek
f530aae9d3 NOISSUE update all the Copyright comments and texts to include 2017 2017-01-08 04:58:05 +01:00
Petr Mrázek
5ea224f8f5 NOISSUE corrected changelog
Proofreading by Zekken and bvanseghi. Let's hope no error escaped.
2017-01-08 04:43:22 +01:00
Petr Mrázek
e033cf8974 NOISSUE change 'Upload' to 'Upload Skin' on tha Accounts page 2017-01-08 03:02:27 +01:00
Petr Mrázek
8e7330ae54 NOISSUE update changelog for 0.5.0 release. 2017-01-08 02:05:28 +01:00
Petr Mrázek
ccb524ed9f NOISSUE disable travis builds with Qt 5.6.2
It has a regression that breaks one of the unit tests.
MultiMC doesn't even use 5.6.x yet.
2017-01-07 23:40:14 +01:00
Petr Mrázek
cff34a14dc NOISSUE dump the json to log in MojangVersionFormatTest 2017-01-07 19:32:39 +01:00
Petr Mrázek
8421ef622d NOISSUE even more java polishing
* Memory minimums lowered to 128M in all Java settings UIs
* Changing the memory sizes on the wizard page does not automatically trigger checks if the executable doesn't have 'java' in the name
* Java detection on linux now scans some common JRE locations, not just /usr/bin/java
2017-01-07 18:11:41 +01:00
Petr Mrázek
c4ec6bc0f5 NOISSUE polish the java setup wizard page
* Added a button to check why Java failed
* It will now avoid automatically scanning binaries that do not have 'java' in their filename
* Fixed some crashes related to running too many Java checks (it only does one at a time now)
* It can now distinguish between more Java failure states (not there at all, crashing, returning nonsense)
* Changed '...' button to Browse button to match the wizard page subtitle
* Changing minimum and maximum memory will no longer trigger a java check twice
2017-01-07 06:52:09 +01:00
Petr Mrázek
705a658fef NOISSUE Do not log the analytics client ID into the application log. 2017-01-06 17:19:28 +01:00
Petr Mrázek
6f17183bf0 NOISSUE make the setup wizard use the main event loop
This should fix any issues with receiving events over IPC.
2017-01-06 06:08:45 +01:00
Petr Mrázek
0249bd9eea NOISSUE default to javaw on Windows 2017-01-05 14:20:37 +01:00
Petr Mrázek
e1bd1c6145 NOISSUE feature complete setup wizard 2017-01-05 04:05:08 +01:00
Petr Mrázek
4c0db2b99d NOISSUE fix travis.ci build
Qt 5.6 version needed incrementing to 5.6.2.
2017-01-02 16:15:40 +01:00
Petr Mrázek
9ca9addad3 NOISSUE create a dumb and ugly java setup wizard page
All it does is create the existing Java selection dialog
2017-01-02 16:02:54 +01:00
Petr Mrázek
64723f68e3 NOISSUE force SetupWizard into 'classic' look and feel and increment analytics version
This should force the dialog to show again.
If it still looks wrong on Windows 10, please report this on discord.
2017-01-02 08:23:03 +01:00
Petr Mrázek
a666dc0a1a NOISSUE fix up translation selection in settings and add OS/sys arch reporting 2017-01-01 20:04:08 +01:00
Petr Mrázek
722896d41f NOISSUE Translations model and initial setup wizard work 2017-01-01 20:04:08 +01:00
Petr Mrázek
46c5368a78 NOISSUE fix up analytics wizard page 2016-12-28 21:39:09 +01:00
Petr Mrázek
476d641841 NOISSUE add skeleton of the setup wizard
Very wizardly. Also very empty and opening on every start for now.
2016-12-28 21:39:09 +01:00
Petr Mrázek
374710a87b GH-1379 update nbt++ to allow renaming its library file 2016-12-28 21:14:44 +01:00
Petr Mrázek
2344ee2dcd GH-1379 rename shared libraries to avoid collisions with system libraries
It was unlikely, now it's impossible.
2016-12-28 17:23:48 +01:00
Petr Mrázek
481ecb178c NOISSUE fix credits entry for RoboSky 2016-12-26 12:45:30 +01:00
Petr Mrázek
123b59e63f NOISSUE Fix up Credits section in About dialog 2016-12-26 01:47:29 +01:00
Petr Mrázek
92bb001787 NOISSUE fix crash caused by missing instance view layout updates
Layout wasn't updated in some cases while deleting instances.
2016-12-19 00:35:57 +01:00
Taylor Smock
03d2858c62 BUILD: Remove extraneous " from CreateServerResourcePacksFolder.cpp (#1749)
NOISSUE Remove extraneous " from CreateServerResourcePacksFolder.cpp

This did not affect build, but was visible in logs.
2016-12-12 14:36:42 +01:00
Petr Mrázek
a6882787b0 GH-1745 fix crash when using path matching filter on copy operations
Copying instances without saves doesn't crash anymore.
2016-12-08 21:58:31 +01:00
Petr Mrázek
2517d2c84d GH-1743 selected instance can be null - do not assume it isn't
This fixes a crash when closing settings and not having any selected instance.
2016-12-07 01:19:03 +01:00
Petr Mrázek
035bdc7576 GH-1524 Regenerate Minecraft client token when the auth token is invalid
This makes the case where users copy MultiMC to other machines
easier to handle. It doesn't require manual intervention and the tokens
do not go in a desync loop.
2016-11-30 00:19:27 +01:00
Petr Mrázek
4ca6878743 GH-1670 Fix LWJGL list loading
Now it uses the standard Download class that supports redirects and SSL.
2016-11-27 01:45:55 +01:00
Petr Mrázek
ef73a2bd32 NOISSUE fix Windows kernel numbers and add unit test for them 2016-11-27 00:40:02 +01:00
Petr Mrázek
5994c47d7c NOISSUE add ganalytics and LocalPeer licenses 2016-11-26 23:53:56 +01:00
Petr Mrázek
66ffab71ae NOISSUE allow killing the instance from main window 2016-11-26 18:06:08 +01:00
Petr Mrázek
ce70407363 NOISSUE add button for opening the config folder from mods pages 2016-11-26 14:59:27 +01:00
Petr Mrázek
dccf9d7219 NOISSUE fix text of log upload, do not open browser on screenshot upload 2016-11-26 14:37:36 +01:00
Petr Mrázek
dd0c815396 NOISSUE fix macOS build (stray assignment to removed variable) 2016-11-26 02:22:40 +01:00
Petr Mrázek
55541c387c NOISSUE simplify system detection and user agent handling
Now it only checks OS kernel name/version.
User agent is 'MultiMC5/$version'.
Kernel info is passed through custom dimensions in analytics.
2016-11-26 02:18:05 +01:00
Petr Mrázek
d5fdc23eb2 NOISSUE dumb down Windows version detection...
Hopefully the analytics thing will accept it this time.
2016-11-25 00:39:15 +01:00
Petr Mrázek
a5fb931e8e NOISSUE fix build (OSVERSIONINFOW) 2016-11-24 23:32:21 +01:00
Petr Mrázek
486d653586 NOISSUE Better Windows version detection and user agent 2016-11-24 23:28:55 +01:00
Petr Mrázek
121e2fd46c NOISSUE add analytics settings (enable/disable) 2016-11-24 04:10:07 +01:00
Petr Mrázek
295c6e808a NOISSUE fix translation listing in settings and translation loading
It was impossible to select and load translations properly.
2016-11-23 02:25:49 +01:00
Petr Mrázek
7a14b63957 NOISSUE send custom analytics values
* System, Java and CPU architecture (either 32 or 64).
* Java version.
* System memory size in MB.
* Java min/max heap size in MB.
2016-11-22 02:46:18 +01:00
Petr Mrázek
44805145dc NOISSUE add implementations of system query functions
* system memory size in bytes
* system architecture is 64bit?
* CPU architecture is 64bit?
2016-11-22 02:46:18 +01:00
Petr Mrázek
00c4aebeaa GH-1731 more screenshot folder view hardening 2016-11-22 00:56:48 +01:00
Petr Mrázek
ee6f2f0a8e NOISSUE implement analytics IP anonymization 2016-11-21 21:18:30 +01:00
Petr Mrázek
95f961fb61 GH-1731 Do not show screenshots model if it can't be set up properly
Otherwise it would show all system drives instead of screenshots.
2016-11-21 09:17:01 +01:00
Petr Mrázek
ad25c89ac4 NOISSUE ifdef out unknown Apple system versions in analytics 2016-11-21 01:30:39 +01:00
Petr Mrázek
905bc2e440 NOISSUE most basic analytics integration possible 2016-11-21 01:19:34 +01:00
Petr Mrázek
2f8c752d1f NOISSUE reformat and sanitize ganalytics 2016-11-21 01:19:22 +01:00
Petr Mrázek
2ec15c32e4 NOISSUE import google analytics from third party
See: https://github.com/HSAnet/qt-google-analytics

Sadly, the API and its internals are not acceptable and it needs changes
upstream likely wouldn't allow.
2016-11-20 12:04:29 +01:00
Petr Mrázek
69be23c5f6 GH-1726 better failure detection for updates
Instead of just checking if the new version started, make sure
it is able to write its IPC key to a file and then use the key
to connect to the process.
2016-11-19 22:11:45 +01:00
Petr Mrázek
e974950d48 GH-1699 do not include libxcb.so* in linux builds 2016-11-19 22:11:41 +01:00
Petr Mrázek
9efdd7232c NOISSUE include json path in errors when FTB json is missing. 2016-11-17 04:40:07 +01:00
Petr Mrázek
9b41986634 GH-347 update timestamps of added mods 2016-11-17 04:09:24 +01:00
Petr Mrázek
b09fad9cbf GH-347 Add timestamp column to mod lists
It shows when the file was changed (in most cases added).
2016-11-17 02:55:02 +01:00
Petr Mrázek
fd34ca5a0f NOISSUE always run the ExtractNatives task during launch
The task now checks the conditions, giving the update process time
to supply all the metadata.
2016-11-17 01:21:49 +01:00
Petr Mrázek
9cf8b42d89 NOISSUE mark profile in use also on the main window profile button 2016-11-17 01:00:15 +01:00
Petr Mrázek
12f6534e77 NOISSUE mark used accounts/sessions in selection menus 2016-11-17 01:00:15 +01:00
Petr Mrázek
3769897be1 NOISSUE do not open browser window on log upload 2016-11-17 01:00:15 +01:00
44trent3
590ff82fd1 NOISSUE Add Windows instructions to build from command line. 2016-11-15 23:13:43 +01:00
Petr Mrázek
f9d94a45ee NOISSUE allow using icon themes for instances and instance badges 2016-11-10 02:54:53 +01:00
Petr Mrázek
27e26a656b NOISSUE remove unused hourglass icon 2016-11-10 00:19:04 +01:00
Petr Mrázek
b6f133f579 GH-1713 fix FTB loading crashes 2016-11-09 01:22:02 +01:00
Petr Mrázek
01649f761d NOISSUE remove some unused icons 2016-11-09 01:20:42 +01:00
Petr Mrázek
dae3b06885 NOISSUE fix horrible globals crash
FIXME: remove all globals.
2016-11-07 02:28:18 +01:00
Petr Mrázek
07589b5114 NOISSUE shut down logger when MultiMC exits
Prevents crash bugs...
2016-11-07 01:54:00 +01:00
Petr Mrázek
7cff5ba2e1 GH-1445 update page list when version and log pages need it 2016-11-07 00:18:27 +01:00
Petr Mrázek
1276ecdbb7 NOISSUE ask user if closing is OK when instances are still running 2016-11-06 23:06:49 +01:00
Petr Mrázek
8b952b3870 NOISSUE Refactor and sanitize MultiMC startup/shutdown
* Always create main window.
* Properly handle netowrk manager - it was created twice, leading to potential crashes.
2016-11-06 21:58:54 +01:00
Petr Mrázek
37cc59c04d GH-378 add a resource search path to custom themes
This allows adding images and other bits and pieces to themes.
2016-11-06 05:48:52 +01:00
Petr Mrázek
bc753859b5 GH-378 add basic custom theme support
Files you can customize are created in themes/custom/
2016-11-06 04:29:12 +01:00
Petr Mrázek
13b575f7a9 GH-1711 fix inactive element shading in Dark and Bright themes 2016-11-06 00:17:02 +01:00
Stefan
495e752f8a NOISSUE Point people to the wiki for the explanation about bug reports 2016-11-04 09:03:34 +01:00
Petr Mrázek
87dd951505 NOISSUE add a badge for crashed instances
Not persistent across MultiMC runs.
2016-11-04 01:19:04 +01:00
Petr Mrázek
3780a25d27 NOISSUE add an option to show console on error and default other options to false 2016-11-04 00:19:32 +01:00
Petr Mrázek
6ebf6e7785 NOISSUE ifdef the hell out of MCEdit launch on Windows
Setting the work directory was not enough.
2016-11-03 02:41:01 +01:00
Petr Mrázek
f4de049b13 NOISSUE Set working directory for MCEdit on Windows
It seems to require it, unlike the other versions.
2016-11-03 02:21:50 +01:00
Petr Mrázek
f0b71f989e NOISSUE use LoggedProcess to work around issues with QProcess on macOS 2016-11-03 01:11:57 +01:00
Petr Mrázek
ac66af6c13 NOISSUE fix reversed MCEdit check condition
It was causing fake errors.
2016-11-02 02:55:16 +01:00
Petr Mrázek
85b64ad767 NOISSUE MCEdit integration - remove old 'tool', replace with Worlds 2016-11-02 02:37:54 +01:00
Petr Mrázek
3a4304d89d NOISSUE simplify retranslation code of MainWindow 2016-11-02 01:17:19 +01:00
Petr Mrázek
a9c0d812a6 NOISSUE prefer shell script for running MCEdit on linux 2016-11-02 01:16:41 +01:00
Petr Mrázek
b6b2350e02 NOISSUE improve launch button interaction
Now has a drop-down arrow that actually works as expected.
2016-11-01 23:33:20 +01:00
Petr Mrázek
2e0a45cc2f NOISSUE add bright theme to complement the dark theme
Same style, different colors.
2016-11-01 09:04:37 +01:00
Petr Mrázek
fe68d59460 GH-1645 reimplement open/close instance window based on settings 2016-11-01 01:25:04 +01:00
Petr Mrázek
4b03dfcbd7 NOISSUE rearrange MultiMC.cpp to make it make more sense 2016-10-31 00:57:40 +01:00
Petr Mrázek
a36c962a31 NOISSUE use current directory for application ID
Using QDir(dataPath).absolutePath() for the path
2016-10-30 14:47:16 +01:00
Petr Mrázek
e9949e3a54 NOISSUE use absolute data path for application ID 2016-10-30 03:45:41 +01:00
Petr Mrázek
a717864013 NOISSUE fix the build even more - win32 types are weird 2016-10-30 03:03:49 +01:00
Petr Mrázek
54e0b9bc9b NOISSUE fix build some more - Windows 2016-10-30 02:53:13 +01:00
Petr Mrázek
249e5c13d7 NOISSUE fix build 2016-10-30 02:49:07 +01:00
Petr Mrázek
412855ae3d NOISSUE refactor window management and launch, make MultiMC a single instance application. 2016-10-30 02:37:38 +01:00
Petr Mrázek
deabfa78f8 GH-1652 set instance running status before notifying event listeners 2016-10-29 02:19:42 +02:00
Petr Mrázek
2b9017a69c NOISSUE clean up some bad/dead code 2016-10-29 01:34:43 +02:00
Petr Mrázek
172ff47a65 NOISSUE clarify linux build steps (add command line instructions) 2016-10-29 01:33:47 +02:00
Petr Mrázek
b5aaf88f12 NOISSUE remove unused 'test mode' 2016-10-28 21:54:12 +02:00
Petr Mrázek
8731318fef GH-1652 save all instance settings on launch if instance window is already open 2016-10-28 03:42:34 +02:00
Petr Mrázek
dd0e996081 GH-1697 always stale files tolerate errors if a local copy is present
This fixes the situation when liteloader snapshot site is broken
and there's an older local snapshot already present.
2016-10-28 02:19:19 +02:00
Petr Mrázek
3d94fb8d24 Revert "GH-1665 diagnostic build - check if log file has been created and opened"
This reverts commit 2597bde4f9.
2016-10-27 17:00:37 +02:00
Petr Mrázek
2c2c1b0a17 Revert "GH-1665 diagnostic build - disable file logging entirely"
This reverts commit 0493170936.
2016-10-27 17:00:34 +02:00
Petr Mrázek
0493170936 GH-1665 diagnostic build - disable file logging entirely 2016-10-27 13:40:53 +02:00
Petr Mrázek
2597bde4f9 GH-1665 diagnostic build - check if log file has been created and opened 2016-10-27 13:17:19 +02:00
Petr Mrázek
cee53f7f3c Revert "NOISSUE nuke builtin Minecraft versions"
This reverts commit 5ae3b2c114.

We need those builtin versions for now.
2016-10-26 18:23:39 +02:00
Petr Mrázek
1b4851a941 NOISSUE use QtConcurrent to run FS operations in worker threads
Not all operations - only the ones that aren't in error handling.
The API for QFuture is too nasty to do much more in a sensible way.
2016-10-26 18:21:25 +02:00
Petr Mrázek
d66fdcd4cc NOISSUE Granular instance reload 2016-10-26 18:21:24 +02:00
Petr Mrázek
bbe139dce5 GH-903 force Dark theme to use Fusion Qt style
Themes now include Qt styles.
2016-10-22 01:43:36 +02:00
Petr Mrázek
872cfe036d GH-903 simple theme switching and dark theme 2016-10-21 09:07:26 +02:00
Petr Mrázek
f07496ac6d GH-1675 reimplement suspesion of log watch 2016-10-11 21:34:02 +02:00
Petr Mrázek
6e80f03409 NOISSUE add instance-local library storage
Any libraries stored in $instanceroot/libraries/ will override
the libraries from MultiMC's global folders, as long as they are marked 'local'
in the json patch.
2016-10-02 00:26:10 +02:00
Petr Mrázek
69f3ab019d NOISSUE delete dead code 2016-09-18 22:53:37 +02:00
Petr Mrázek
eb747e08b7 NOISSUE fix minor memory leaks 2016-08-19 09:04:58 +02:00
Petr Mrázek
67eca08b22 NOISSUE use model/view for Minecraft log data 2016-08-19 08:05:43 +02:00
Petr Mrázek
9aff21c181 NOISSUE make progress dialog abort button resistant to accidental key presses 2016-08-17 23:09:33 +02:00
Petr Mrázek
ec05ca2775 SCRATCH make instance windows independent 2016-08-15 00:50:13 +02:00
Petr Mrázek
042f3ef55c GH-352 Make OneSix instance update downloads cancellable 2016-08-14 23:22:54 +02:00
Petr Mrázek
2f0441b3c1 GH-1433 with no default account, show profiles instead of accounts in selection dialog 2016-08-11 00:44:01 +02:00
Petr Mrázek
55544893a3 GH-1643 do not censor preferredLanguage in logs 2016-08-10 19:52:38 +02:00
Petr Mrázek
e2f3652a0f Revert "NOISSUE rework of minecraft log"
This reverts commit fc198dd308.
2016-08-10 08:41:58 +02:00
Petr Mrázek
c60db13af7 NOISSUE Do not kill running instances when MultiMC shuts down 2016-08-10 00:28:33 +02:00
Petr Mrázek
fc198dd308 NOISSUE rework of minecraft log
Now uses a model and a list view instead of text
This lets mmc keep track of the contents regardless of whether the instance windows are open

This is currently missing a way to select and copy text from the log.
2016-08-10 00:28:33 +02:00
Petr Mrázek
74b4343c43 GH-1642 fix instance launch from console 2016-08-09 22:29:17 +02:00
Petr Mrázek
877d1020db GH-1641 we use Qt 5.4.1, not 5.5.1 2016-08-08 22:33:25 +02:00
Petr Mrázek
bc6d1b5304 GH-338, GH-513, GH-700 Unify edit instance with console window
* The resulting instance window can be closed at any point.
* Main window is kept open and running instances are marked with a badge.
* Multiple instances can now run from the same MultiMC - it's even more **multi** now.
* MultiMC can be entirely closed, keeping Minecraft(s) running.
2016-08-07 11:48:15 +02:00
Petr Mrázek
c44d41ee9b NOISSUE do not attempt to stop watching world folders if they are not being watched 2016-08-07 11:44:42 +02:00
Petr Mrázek
cf0694a0cb NOISSUE allow user to sort mod list by clicking on column headers 2016-08-05 00:10:33 +02:00
Petr Mrázek
b76d4573cd GH-589 GH-842 GH-901 GH-1117 allow mass-enabling/disabling of mods 2016-08-04 23:16:03 +02:00
Petr Mrázek
6ec2652b45 GH-1273 allow extended selection in mod lists 2016-08-04 22:57:16 +02:00
Petr Mrázek
eec87db86a GH-1635 add filter bar to mod list pages 2016-08-04 21:54:25 +02:00
Petr Mrázek
42a98c3661 NOISSUE move creation of server resource pack folder to a separate task 2016-08-01 21:15:08 +02:00
Petr Mrázek
1f2bed2ef1 NOISSUE implement direct java launch
Just running the Java process and giving it params on the command line
2016-08-01 21:15:08 +02:00
Mrazek, Petr
57c84ec2b1 NOISSUE more travis issues
There is no Qt 5.7 build for Ubuntu Precise
2016-07-09 05:31:35 +02:00
Mrazek, Petr
2164dc13f4 NOISSUE fix up travis builds
Qt 5.6 -> 5.6.1
+ Qt 5.7
2016-07-08 21:18:57 +02:00
FyberOptic
f626fd02c7 GH-1610 Make Forge installs only use newer list to fix older version downloads 2016-07-08 14:08:07 -04:00
Benjamin Hoffmeyer
7da70a75eb Merge pull request #1590 from Heufneutje/patch-1
Point people to the wiki for attaching logs to issues
2016-06-17 14:48:15 -04:00
Petr Mrázek
969418f01f NOISSUE make liteloader version list pretty and usable 2016-06-14 02:08:56 +02:00
Petr Mrázek
6ecfe8546f NOISSUE implement support for liteloader snapshots 2016-06-13 22:00:09 +02:00
Petr Mrázek
8b74f6dcf0 NOISSUE reset wroteAnyData flag when resetting FileSink
This fixes files getting overwritten with null content when stale cache
and redirects are combined
2016-06-13 21:56:22 +02:00
Petr Mrázek
d4109938fe NOISSUE implement 'always-stale' cache entries 2016-06-13 21:53:56 +02:00
Stefan
be89024d4e NOISSUE Point people to the wiki for attaching logs to issues
A lot of people miss this and you just end up with a bunch of clutter in the actual issue, often hard or impossible to read because of the lack of backticks.
2016-06-13 08:42:41 +02:00
Petr Mrázek
56394f93e5 NOISSUE log which file couldn't be copied during recursive copy 2016-06-10 00:58:30 +02:00
Petr Mrázek
e07456f4bf GH-1586 Windows: attach to the parent process console if there is any, so command line output gets printed there 2016-06-10 00:58:30 +02:00
Petr Mrázek
54e5a98da0 NOISSUE refactor liteloader version file creation
It no longer implements yet another version file format serialization
2016-06-07 01:23:31 +02:00
Petr Mrázek
a1abbd9e05 NOISSUE refactor *Download into more, smaller pieces
* Download is now Download.
* Download uses Sink subclasses to process various events.
* Validators can be used to further customize the Sink behaviour.
2016-06-05 23:55:39 +02:00
Petr Mrázek
a750f6e63c NOISSUE remove excessive build notifications from travis.ci 2016-05-15 23:37:58 +02:00
Petr Mrázek
4440f68e59 GH-575 Add back file drop support to ModList 2016-05-15 23:27:06 +02:00
Petr Mrázek
67b22c8105 GH-575 clean up ModList - remove all legacy and obsolete parts 2016-05-15 22:56:14 +02:00
Petr Mrázek
12413f722d GH-575 separate legacy jar mod list from mod list 2016-05-15 22:56:14 +02:00
Petr Mrázek
5aff10d51d NOISSUE Update travis CI
Remove Qt < 5.4
Add Qt 5.6
2016-05-15 22:50:53 +02:00
Alexia
377316999e GH-767 Basic skin upload 2016-05-15 16:01:05 +02:00
Petr Mrázek
f9791a5cc8 GH-1560 trim whitespace from instance name on rename 2016-05-04 00:31:27 +02:00
Petr Mrázek
603b0408ab GH-1560 trim whitespace from instance names when creating instances 2016-05-04 00:29:40 +02:00
Mrazek, Petr
ecd5d3a2db NOISSUE do not scan extra folders for libraries 2016-05-03 18:26:17 +02:00
Mrazek, Petr
898e3cd4e7 GH-1559 Export IIconList from logic API 2016-05-03 17:49:56 +02:00
Petr Mrázek
e1a530f84d GH-1559 Fix FTB icons
This was caused by separation of GUI and logic. Now logic has an interface that GUI implements.
It should be expanded upon later.
2016-05-03 00:27:28 +02:00
Petr Mrázek
c50b3cdeec NOISSUE fix silly path issue with folders and files with the same names 2016-05-01 04:06:24 +02:00
Petr Mrázek
b0bfffcd90 NOISSUE revert to dumping all build artifacts to the root
This fixes unit tests on Windows... Windows has no mechanism to set library lookup path.
2016-05-01 03:49:46 +02:00
Petr Mrázek
80b28e7d49 NOISSUE add nbt lib to bundle utilities search path 2016-05-01 02:05:21 +02:00
Petr Mrázek
16650790d0 NOISSUE fix dll export for IconList 2016-05-01 00:45:48 +02:00
Petr Mrázek
e32d7238c9 NOISSUE tell bundle utilities about more places where libraries hide
It was super effective.
2016-05-01 00:02:15 +02:00
Petr Mrázek
771dd6f9ab NOISSUE reorganize unit tests to be placed next to the code they test. Nuke more dead tests. 2016-05-01 00:02:15 +02:00
Petr Mrázek
e8ba5dafc6 NOISSUE remove dead unit tests and reorganize CMake code related to unit tests 2016-05-01 00:01:39 +02:00
Petr Mrázek
ed3884fd38 NOISSUE move Java and Minecraft launch tasks to the proper places
Minecraft and Java are not generic.
2016-05-01 00:00:24 +02:00
Petr Mrázek
1be7d57332 NOISSUE re/move some dead code and unused build system parts 2016-05-01 00:00:24 +02:00
Petr Mrázek
aa4842a91d NOISSUE make travis.ci shut up about fancy QJsonObject initialization in Wonko parser 2016-05-01 00:00:24 +02:00
Petr Mrázek
b6d455a02b NOISSUE reorganize and document libraries 2016-05-01 00:00:14 +02:00
Petr Mrázek
47e37635f5 NOISSUE split GUI stuff from logic library 2016-04-30 23:59:23 +02:00
Petr Mrázek
fcd4a482f7 NOISSUE tiny skeleton for a CLI wonko client 2016-04-30 23:59:23 +02:00
Jan Dalheimer
00e5968bd2 NOISSUE Add a skeleton of the wonko system 2016-04-30 23:59:23 +02:00
Petr Mrázek
5ae3b2c114 NOISSUE nuke builtin Minecraft versions
Use upstream Mojang versions.
2016-04-30 23:59:03 +02:00
Petr Mrázek
4392abfb8d GH-1556 disable export for tracked FTB instances 2016-04-28 00:04:37 +02:00
Petr Mrázek
72c92893a5 GH-1556 do not crash when instance has no update task while creating an instance 2016-04-27 23:55:18 +02:00
TheDoctorsLife
0890a81695 NOISSUE: Add double spaces between each section in issue template.
Makes it look pretty once filed out.
2016-04-17 11:15:00 -04:00
Petr Mrázek
432ec74174 GH-1404 allow deleting groups and creating instances in groups directly using context menu 2016-04-11 01:30:50 +02:00
Petr Mrázek
b795ad5209 NOISSUE add line wrapping checkbox to log page 2016-04-10 21:52:01 +02:00
TheDoctorsLife
c44e85c765 NOISSUE: Move the issue template file out of project root to help make it just a little more clean. 2016-04-05 20:38:31 -04:00
Petr Mrázek
b29ef49415 NOISSUE fix some forge version processing issues 2016-04-03 22:17:06 +02:00
Petr Mrázek
f184eff71a NOISSUE write +libraries instead of libraries to stay compatible 2016-04-03 21:53:46 +02:00
Benjamin Hoffmeyer
b3e3a6fc88 NOISSUE Make formatting pretty Idiot proof 2016-04-01 15:06:03 -04:00
Benjamin Hoffmeyer
0ff6f3a036 NOISSUE Make formatting little easier to understand 2016-04-01 15:01:55 -04:00
Benjamin Hoffmeyer
bf0f27bd60 NOISSUE Remove formatting for help section 2016-04-01 14:59:28 -04:00
Benjamin Hoffmeyer
dd5b07e38d NOISSUE Make the notes to the user not show up if not deleted.
Also add a line about formatting.
2016-04-01 14:55:32 -04:00
Alexia
ea685651a1 NOISSUE Quick fix for icons being required 2016-03-28 16:45:52 -04:00
Petr Mrázek
53b4bd019f NOISSUE fix bug in unpacking of forge pack200 jar files
This caused failed downloads and broken files to be used.
2016-03-28 20:52:14 +02:00
Petr Mrázek
f032e32133 NOISSUE finalize support for new mojang version format 2016-03-27 22:35:06 +02:00
Petr Mrázek
d587720010 NOISSUE use new mojang assets locations 2016-03-26 17:05:27 +01:00
Petr Mrázek
2929ca7413 NOISSUE use unique_ptr for cached download's QSaveFile 2016-03-26 17:05:27 +01:00
Petr Mrázek
ff8f495d44 NOISSUE remove unused 'INetworkValidator' 2016-03-26 17:05:27 +01:00
Petr Mrázek
f56983e5ca NOISSUE do not write 'time' and 'releaseTime' when they are null 2016-03-26 17:05:27 +01:00
Petr Mrázek
ec6204e447 NOISSUE share logic for new attributes between mojang and onesix format 2016-03-26 17:05:27 +01:00
Petr Mrázek
9e3534f2f6 NOISSUE stop update task when it can't read version files 2016-03-26 17:05:27 +01:00
Petr Mrázek
b7d8e512f4 NOISSUE Use patch problems and problem levels instead of exceptions for minecraft profiles. 2016-03-26 17:05:27 +01:00
Petr Mrázek
fb9dfcb951 NOISSUE stop referring to the minecraft profile as 'version' 2016-03-26 17:05:27 +01:00
Petr Mrázek
010e07eb45 NOISSUE clean up forge installer 2016-03-26 17:05:27 +01:00
Petr Mrázek
576d808d71 NOISSUE resolve library activeness during application to profile 2016-03-26 17:05:27 +01:00
Petr Mrázek
f63d1bc99c NOISSUE revert attempt to use normal binary output locations 2016-03-26 17:05:27 +01:00
Petr Mrázek
02c1df2c3c NOISSUE continue version file format refactors 2016-03-26 17:05:27 +01:00
Alexia
1854e05e1b NOISSUE Fix building when embedded in another project 2016-03-26 17:01:20 +01:00
Loetkolben
0c06ab364c NOISSUE Remove tr(...) where unnecessary. 2016-03-23 12:42:38 +01:00
Petr Mrázek
07608ebc4c GH-1521 When json editor is custom, actually pass the filename to it 2016-03-17 13:25:57 +01:00
Spencer Burris
36f3813ce5 NOISSUE: Change text to also refer to later java versions 2016-03-16 21:22:13 -07:00
Spencer Burris
f96d20b6f7 NOISSUE: Add note about Java 8 settings permgen automatically 2016-03-15 09:19:16 -07:00
Petr Mrázek
ead4c17d0a NOISSUE hide mojang structs inside the mojang format entirely 2016-03-07 22:26:44 +01:00
Petr Mrázek
d4eacb56b3 NOISSUE make new Mojang version format pass through MultiMC structures
Not yet used effectively, but it is read and written properly
2016-03-07 02:03:36 +01:00
Petr Mrázek
3d8728f52f NOISSUE no issue. with unit tests. 2016-03-03 22:47:41 +01:00
Petr Mrázek
2e4fa7ec13 NOISSUE Fix bad unit test data path and usage of std::abs 2016-03-03 02:13:07 +01:00
Petr Mrázek
fd2103d6ee NOISSUE disable complex variant of mojang version format test 2016-03-03 01:59:47 +01:00
Petr Mrázek
94d4684809 NOISSUE add basic unit tests for MojangVersionFormat reading/writing
will have to make them pass now
2016-03-03 01:40:12 +01:00
Petr Mrázek
b54839b897 NOISSUE eliminate timestamp strings 2016-03-02 09:16:58 +01:00
Petr Mrázek
80b81c2c1e SCRATCH some version file member variables commented 2016-03-01 09:47:12 +01:00
Petr Mrázek
f53cd55fbb NOISSUE bump version number and update changelog for next release 2016-02-29 09:08:35 +01:00
Petr Mrázek
a3cd3d5ff1 NOISSUE update changelog 2016-02-28 20:08:59 +01:00
Petr Mrázek
1a9793197f GH-1502 move launch script generation to the Minecraft launch step 2016-02-28 19:33:05 +01:00
Petr Mrázek
9497b7e96c NOISSUE even more version file refactors
There is no end to them in sight
2016-02-28 19:01:54 +01:00
Petr Mrázek
a0b47aee5b NOISSUE move version file reading and writing to dedicated namespaces 2016-02-27 22:02:56 +01:00
Petr Mrázek
17ad1e64f8 NOISSUE move files into paths that make more sense 2016-02-27 19:58:40 +01:00
Petr Mrázek
71e4b147ec NOISSUE remove OneSixLibrary 2016-02-26 02:04:21 +01:00
Petr Mrázek
f6b2ccb110 NOISSUE remove old unused version file features 2016-02-25 00:29:50 +01:00
Petr Mrázek
c943019ab5 NOISSUE fix a benign leak in FTB implementation 2016-02-25 00:29:08 +01:00
Spencer Burris
fc43fd1105 NOISSUE Add issue template 2016-02-21 14:53:53 -08:00
Petr Mrázek
401d5b698f GH-1453 handle certain version loading corner cases better, clean up FTB 2016-02-21 05:51:36 +01:00
Petr Mrázek
1a0bbdd9ac GH-1453 report version file problems in the version page 2016-02-21 01:44:27 +01:00
Petr Mrázek
495d320ce2 Revert "Update libnbtplusplus with magical changes"
This reverts commit 6e6e2bf262.
2016-02-20 09:11:10 +01:00
Petr Mrázek
5e737f42bf GH-1410 use libc++ on OSX 2016-02-19 00:57:46 +01:00
Petr Mrázek
6e6e2bf262 Update libnbtplusplus with magical changes 2016-02-19 00:02:57 +01:00
Petr Mrázek
163a3095b1 GH-1453 separate out Mojang version reading, use version file URLs 2016-02-17 08:23:57 +01:00
Petr Mrázek
a20e2590da GH-1453 React to the minimum version change - 18 2016-02-13 17:34:27 +01:00
Petr Mrázek
1978078662 NOISSUE remove dead code from MC version list 2016-02-13 17:32:14 +01:00
Petr Mrázek
ea08ede4c3 GH-1483 Use the new version index URL 2016-02-13 16:41:26 +01:00
Petr Mrázek
b7f75637fa GH-1451 add make install step to OSX build instructions 2016-02-11 21:04:10 +01:00
Petr Mrázek
4ee1900201 Merge branch 'patch-1' of git://github.com/iarspider/MultiMC5 into develop 2016-02-03 19:24:51 +01:00
Petr Mrázek
ab67d763f4 NOISSUE bump release number 2016-02-03 19:22:55 +01:00
Petr Mrázek
7ca9f92343 GH-1422 bump version to 0.4.10 and update changelog 2016-01-22 02:00:20 +01:00
Petr Mrázek
cbd4b88e91 GH-1422 remove bearer plugins again. 2016-01-21 20:16:57 +01:00
Petr Mrázek
0958bb2fcc NOISSUE bump version number to 0.4.9 2016-01-21 01:28:39 +01:00
iarspider
fbec48080b NOISSUE Do not ask to overwrite existing file twice when exporting instances 2016-01-20 21:11:58 +03:00
Petr Mrázek
33b6222f9f NOISSUE update changelog 2016-01-20 04:08:29 +01:00
Petr Mrázek
5ecaed21b5 GH-1411 fail if pre-launch and post-exit commands return non-zero values 2016-01-20 03:23:19 +01:00
Petr Mrázek
9fd66b3bb1 GH-1362 Put timestamps into the log when it is copied or uploaded 2016-01-20 03:10:02 +01:00
Petr Mrázek
47ea2a71c0 GH-1408 add bearer plugins
This should fix wifi issues on Windows
2016-01-20 02:44:57 +01:00
Petr Mrázek
2c2b960ab4 GH-1055 fix more types in changelog 2016-01-17 14:43:51 +01:00
Petr Mrázek
ce326ccdff GH-1055 fix typo and missing description 2016-01-17 13:24:59 +01:00
Petr Mrázek
1ed86bfe1a GH-1055 update changelog for 0.4.8 2016-01-17 13:04:07 +01:00
Petr Mrázek
781e53cccb GH-1402 add a hack for updater to respect renamed binary on Windows 2016-01-12 06:52:29 +01:00
Petr Mrázek
25991c36af GH-1280 modpack import: make the progress dialog say what is being downloaded 2016-01-12 05:30:03 +01:00
Petr Mrázek
5ccfbba435 NOISSUE select modpack field content on focus in new instance dialog 2016-01-12 04:37:30 +01:00
Petr Mrázek
1f0e76a3c1 GH-1397 add discord icon to MultiMC toolbar 2016-01-09 01:39:51 +01:00
Petr Mrázek
d8b1ae38fb GH-1379 fix build on win32 2016-01-07 06:47:07 +01:00
Petr Mrázek
59e6b4ed55 GH-1379 fix build with Qt 5.2.1 on ubuntu64 2016-01-07 06:40:26 +01:00
Petr Mrázek
5ff9f90ce9 Merge pull request #1393 from MultiMC/feature/travis-older-qt
GH-1393 Add more qt versions to travis
2016-01-06 11:28:44 +01:00
Jan Dalheimer
34bf4ccdc7 NOISSUE Add more qt versions to travis
5.0, 5.1 and 5.2 are currently marked as "allow failure". If they can be made
to pass they should be removed from this list, if not they should be removed
entirely.
2016-01-06 10:33:47 +01:00
Petr Mrázek
aa8103adf2 GH-1390 improve linux runner script more
* use `-q` while calling dnf to avoid garbage in output
2016-01-05 08:50:47 +01:00
Petr Mrázek
b300c4956c GH-1390 improve linux runner script
* include fedora dnf package manager
* output of the missing dependency lookup is now passed through `sort -u`
2016-01-05 08:40:30 +01:00
Petr Mrázek
4d0caf6254 GH-1389 wrap QDesktopServices and QProcess::startDetached
Essentially do not pass some environment variables to subprocesses:
* LD_PRELOAD
* LD_LIBRARY_PATH
* LD_DEBUG
* QT_PLUGIN_PATH
* QT_FONTPATH
2016-01-05 07:32:52 +01:00
Petr Mrázek
d1e344f28f GH-1389 Revert "GH-1389 remove use of LD_LIBRARY_PATH for mmc libs"
This reverts commit 6f92ca843e.
2016-01-04 02:00:24 +01:00
Petr Mrázek
e0a9970d59 NOISSUE Revert "NOISSUE remove old BundleUtilities. 2014 called and wants its cmake scripts back."
This reverts commit 8cf23bd5aa.
2016-01-04 01:42:48 +01:00
Petr Mrázek
6f92ca843e GH-1389 remove use of LD_LIBRARY_PATH for mmc libs 2016-01-04 01:37:46 +01:00
Petr Mrázek
8cf23bd5aa NOISSUE remove old BundleUtilities. 2014 called and wants its cmake scripts back. 2016-01-03 22:49:31 +01:00
Petr Mrázek
c6afa7d73e GH-1378 fix libpng path matcher for bundle fixup 2016-01-03 16:04:26 +01:00
Petr Mrázek
dcb4e0fa6f GH-1313 tweak the window title to be easier to translate 2016-01-03 16:03:49 +01:00
Petr Mrázek
15aaded80b GH-1313 remove or disable some translateable strings 2016-01-03 03:15:12 +01:00
Petr Mrázek
b9b5a82c2e GH-1313 remove translatable strings from MCModInfoFrame 2016-01-02 04:01:00 +01:00
Petr Mrázek
da2af5e449 GH-1365 fix java detection build on windows 2016-01-02 01:16:44 +01:00
Petr Mrázek
1363b1d364 GH-1365 fix java detection OS ifdefs 2016-01-02 01:13:42 +01:00
Petr Mrázek
a008efd24e GH-1365 rework java version parsing and sorting 2016-01-02 00:35:54 +01:00
Petr Mrázek
5f57df8110 GH-1365 Revert "GH-1365 use the first part of the java version number as the major version - native extraction"
This reverts commit b437988d7b.
2015-12-30 14:24:56 +01:00
Petr Mrázek
b437988d7b GH-1365 use the first part of the java version number as the major version - native extraction 2015-12-28 19:06:03 +01:00
Petr Mrázek
07449e514a GH-1360 fix some typos 2015-12-28 06:00:36 +01:00
Petr Mrázek
bd2843952a NOISSUE export more stuff from Json 2015-12-28 05:47:11 +01:00
Petr Mrázek
5402acb3c6 GH-1360 add basic changelog based on github API, fix update dialog buttons 2015-12-28 05:36:17 +01:00
Petr Mrázek
e8063d193d GH-1308 remove use of static data (translations) and root (notifications.json) paths. 2015-12-27 03:34:03 +01:00
Petr Mrázek
7670d72bd9 GH-1178 sanitize mod paths while installing folder mods 2015-12-26 03:20:41 +01:00
Petr Mrázek
c64a7940c1 GH-1178 add failing test 2015-12-26 03:20:19 +01:00
Petr Mrázek
243c5d1cfb NOISSUE add a basic test for FS::copy 2015-12-26 02:44:33 +01:00
Petr Mrázek
478ff11485 GH-1355 do not allow setting LD_LIBRARY_PATh for Minecraft to the MultiMC bin folder 2015-12-18 05:43:44 +01:00
Soni L
2db4a595dd GH-1355 Fix MMC env propagating to MC after update
This closes GH-1355
2015-12-17 19:38:14 -02:00
Petr Mrázek
2da3162206 GH-1320 do not fail when launcher part can't change sys_paths 2015-12-03 23:27:26 +01:00
Petr Mrázek
a5b8f22eab GH-1339 do not destroy console window while screenshot upload is active 2015-12-03 23:00:51 +01:00
Petr Mrázek
b5902b739e GH-1338: mark asset index as stale during instance update
This forces an update for assets changing on mojang servers
2015-11-19 22:20:40 +01:00
Petr Mrázek
11afc61426 GH-1322 fix mod description ... button popping up multiple dialogs 2015-11-13 00:50:38 +01:00
Petr Mrázek
384c03c9c5 NOISSUE travis: just force it to work. 2015-11-13 00:32:23 +01:00
Petr Mrázek
21490a42bd Revert "NOISSUE travis: use a ppa for cmake 3.2.3"
This reverts commit d855f4905d.
2015-11-13 00:30:40 +01:00
Petr Mrázek
d855f4905d NOISSUE travis: use a ppa for cmake 3.2.3 2015-11-13 00:24:07 +01:00
Petr Mrázek
12c7b11fe6 NOISSUE fix bad patch.exe link and formatting in BUILD document 2015-11-13 00:14:12 +01:00
Petr Mrázek
055774dd58 NOISSUE reword and reformat the README 2015-11-09 00:27:57 +01:00
Petr Mrázek
fdbe431d6b NOISSUE update versions in build how-to 2015-11-09 00:27:24 +01:00
Petr Mrázek
9c01b2294f GH-1319 add patch.exe mention to the build how-to 2015-11-09 00:24:43 +01:00
Petr Mrázek
895d8ab469 GH-1300 call application quit when direct launch instance quits 2015-10-24 00:57:54 +02:00
Petr Mrázek
8a4fd8c468 GH-1301 fix linux runner script argument escaping 2015-10-23 20:01:33 +02:00
Petr Mrázek
61ad480588 NOISSUE update travis to use Qt 5.5.1 2015-10-20 17:42:22 +02:00
Petr Mrázek
79ae4ef2f7 NOISSUE reformat MainWindow with new clang_format settings 2015-10-20 17:22:13 +02:00
Petr Mrázek
125abf5027 NOISSUE rename QObjectPtr to shared_qobject_ptr, introduce unique_qobject_ptr, refactor MainWindow to match 2015-10-20 17:18:53 +02:00
Petr Mrázek
9ad99ac481 NOISSUE raise formatting column limit to 160 2015-10-20 17:02:12 +02:00
Petr Mrázek
69989ab54e GH-1274 update the licenses, the dirty way 2015-10-20 01:50:29 +02:00
Petr Mrázek
7ef1f88de7 NOISSUE fix non-zero exit code from minecraft not being a 'crash' 2015-10-18 02:35:47 +02:00
Petr Mrázek
432e812da1 GH-1275 create server-resource-packs folder on launch
This is a workaround for Minecraft bug MCL-3732
2015-10-12 17:55:10 +02:00
Petr Mrázek
d5aee5fd23 NOISSUE maybe less evil... 2015-10-11 20:06:46 +02:00
Petr Mrázek
457dd2e94e NOISSUE make the compiler more evil 2015-10-11 19:56:31 +02:00
Petr Mrázek
44db72ead5 GH-93 add an option to not copy saves on instance copy 2015-10-10 05:55:55 +02:00
Petr Mrázek
4fbcb3efb9 GH-1268 ... and catch the inevitable errors 2015-10-05 23:52:23 +02:00
Petr Mrázek
439c6b43a3 GH-1268 simplify and fix instance group file IO 2015-10-05 23:48:38 +02:00
Petr Mrázek
db926a546e NOISSUE there is too much ifdef involved... 2015-10-05 02:00:03 +02:00
Petr Mrázek
4a900a58d4 NOISSUE missed a few places... 2015-10-05 01:53:09 +02:00
Petr Mrázek
f93f867c3d NOISSUE dissolve util library 2015-10-05 01:47:27 +02:00
Petr Mrázek
7459eb627c GH-1266 fix ubuntu run script 2015-10-02 23:06:22 +02:00
Petr Mrázek
ae4216de61 GH-719 implement paste.ee API keys 2015-10-02 00:12:53 +02:00
Petr Mrázek
ac8ff88061 GH-729 use mod mmc_id as name when name is empty 2015-09-30 23:30:55 +02:00
Petr Mrázek
477a1a88c6 GH-1262 fix relative paths for java binaries 2015-09-30 22:52:55 +02:00
Petr Mrázek
cf0308c970 GH-1263 update build instructions 2015-09-30 22:00:31 +02:00
Petr Mrázek
e2fd299fc5 GH-253 implement launching instances from command line 2015-09-30 00:11:00 +02:00
Petr Mrázek
e993b1152d GH-1202 rebuild SSL certs on start on OSX - part 4 2015-09-29 01:42:45 +02:00
Petr Mrázek
877240524d GH-1202 rebuild SSL certs on start on OSX - part 3 2015-09-29 01:21:04 +02:00
Petr Mrázek
b7ff8a4c1c GH-1202 rebuild SSL certs on start on OSX - part 2 2015-09-29 01:06:26 +02:00
Petr Mrázek
22c0d5cf46 GH-1202 rebuild SSL certs on start on OSX 2015-09-29 00:49:54 +02:00
Petr Mrázek
12b14c3400 GH-1179 incorporate fixes for comments from linked changeset
Use SSL for downloads
Use XDG dirs for storage
Do not run from deploy function
2015-09-28 23:28:40 +02:00
Petr Mrázek
00994a425e GH-1238 add reddit button 2015-09-28 22:47:02 +02:00
Petr Mrázek
ccb5fc6f4a GH-1072 use crafatar for grabbing the user skin 2015-09-28 22:43:57 +02:00
Petr Mrázek
1cbe543b39 NOISSUE and even more: forgot to remove export from IconResourceHandler 2015-09-28 21:28:46 +02:00
Petr Mrázek
260a2cea59 NOISSUE more cmake tweakery 2015-09-28 21:20:27 +02:00
Petr Mrázek
143e24fa04 NOISSUE clean up some old cmake messes 2015-09-28 20:53:46 +02:00
Petr Mrázek
33c3850b40 NOISSUE add missing files 2015-09-27 22:31:52 +02:00
Petr Mrázek
271ad9e4fd GH-1072 split resource system to UI and logic parts 2015-09-26 13:45:29 +02:00
Petr Mrázek
cca6700134 NOISSUE fix all clang warnings 2015-09-26 04:04:09 +02:00
Petr Mrázek
0af04dc060 NOISSUE attempt to fix NBT lib options not applying 2015-09-26 01:22:37 +02:00
Petr Mrázek
e4df8165f7 NOISSUE and even more 2015-09-26 00:44:52 +02:00
Petr Mrázek
1ab26ef2ad NOISSUE fix travis not notifying IRC, again 2015-09-26 00:39:24 +02:00
Petr Mrázek
5d5bee4992 GH-1238 re-integrate nbt library 2015-09-23 01:21:19 +02:00
Petr Mrázek
e60a652b78 GH-1217 reset time played on zip pack import 2015-09-22 01:25:34 +02:00
Petr Mrázek
9ba1cd15e7 GH-1217 add simple instance play time tracking
Not reliable in the face of crashes... but what is?
2015-09-22 01:06:45 +02:00
Petr Mrázek
b107617112 GH-1121 select instance after creating it 2015-09-16 00:21:50 +02:00
Petr Mrázek
0a187d0ad3 GH-1227 protect world from corruption 2015-09-15 22:51:10 +02:00
Petr Mrázek
8d3f13c447 GH-1227 add world copy and rename 2015-09-14 23:49:32 +02:00
Petr Mrázek
dd8eacee1b GH-1227 renam GZip functions to not collide with zlib macros 2015-09-14 02:36:03 +02:00
Petr Mrázek
e38cc1d480 GH-1227 add GZip compress function and a unit test fo GZip 2015-09-14 02:25:47 +02:00
Petr Mrázek
cfd5976471 GH-1227 display some messages when MCEdit isn't setup or fails 2015-09-14 02:23:40 +02:00
Petr Mrázek
8ef07ec634 GH-1227 allow structured world zip import and drag and drop out of MultiMC 2015-09-13 04:21:26 +02:00
Petr Mrázek
2315f463a8 GH-1237 make mod info clickable and selectable 2015-09-12 03:08:36 +02:00
Petr Mrázek
38901ed21d NOISSUE use whole zip base filename for default instance name 2015-09-12 03:07:41 +02:00
Petr Mrázek
ec3472f21d GH-1232 try to export the primitive tag template instantiations explicitly 2015-09-10 08:39:41 +02:00
Petr Mrázek
bd96a25f7a Revert "GH-1232 attempt to fix reading NBT longs on OSX"
This reverts commit 877fc94f50.
2015-09-10 00:41:12 +02:00
Petr Mrázek
877fc94f50 GH-1232 attempt to fix reading NBT longs on OSX
EXPORT ALL THE THINGS
2015-09-10 00:16:33 +02:00
Petr Mrázek
db5816b0a2 GH-1227 fix zlib nonsense on Windows 2015-09-10 00:02:02 +02:00
Petr Mrázek
a1fd50e920 GH-1227: World import using drag and drop - zip files and folders 2015-09-09 23:53:33 +02:00
Petr Mrázek
51070a13f7 GH-1231 add libpng to packages 2015-09-08 22:09:08 +02:00
Petr Mrázek
36dbf1fb43 GH-1233 do not load worlds when not needed 2015-09-08 21:22:23 +02:00
Petr Mrázek
1ca9fc8961 NOISSUE catch more errors from nbt lib 2015-09-08 09:28:14 +02:00
Petr Mrázek
b8cdcdb96b GH-1047 build fixes 2015-09-06 23:46:38 +02:00
Petr Mrázek
38693e1d6c GH-1047 parse world files and integrate MCEdit with world page 2015-09-06 23:35:58 +02:00
Petr Mrázek
40b233448c Use default Yes/No buttons for confirmation 2015-09-06 16:00:07 +02:00
Petr Mrázek
7d8c71aad8 Just use an oxygen icon for the worlds folder... 2015-09-06 16:00:07 +02:00
Alex
498dc8fc03 Add confirmation dialog for world deletion 2015-09-06 16:00:07 +02:00
Petr Mrázek
c3480d6fe4 Icon 2015-09-06 16:00:07 +02:00
Alex
b5d5490714 Copyright fixes for new code 2015-09-06 16:00:07 +02:00
Alex
83434a9be5 Comment changes and general cleanup 2015-09-06 16:00:07 +02:00
Alex
583e5946f4 GH-1047 World management for instances. Removal only currently. 2015-09-06 16:00:07 +02:00
Petr Mrázek
16df6c16f3 Merge pull request #1225 from iambob314/patch-1
Fixed overwriting of wrapper command
2015-09-06 07:26:11 +02:00
iambob314
6148023ad6 Fixed overwriting of wrapper command
Fixed "WrapperCommand" setting being overwritten by the value of "PreLaunchCommand".
2015-09-05 19:39:13 -04:00
Petr Mrázek
6496c65285 SCRATCH do not export the pure template 2015-09-05 19:23:46 +02:00
Petr Mrázek
db5e55e026 SCRATCH NetAction fix 2015-09-05 19:10:03 +02:00
Petr Mrázek
23d0bd8edd NOISSUE make shared logic library ... shared 2015-09-05 18:46:57 +02:00
Petr Mrázek
cd108fd029 GH-1223 fix override settings
They now work more like passthrough settings, except not passing through set and reset
2015-09-04 02:10:29 +02:00
Petr Mrázek
151a0ca11e GH-1124 tell user which java path is wrong. 2015-08-24 09:38:05 +02:00
Petr Mrázek
bc917668ff GH-1133 fix bad java path detection some more 2015-08-24 00:56:45 +02:00
Petr Mrázek
961c1c61b8 GH-1206 fix deleting files during update
Backups weren't created properly which led to failure to update
2015-08-23 22:33:59 +02:00
Petr Mrázek
0d15247247 NOISSUE remoce the old WM_CLASS workaround 2015-08-23 16:05:53 +02:00
Petr Mrázek
b6ec2ac4b0 GH-1140 mark cached modpack dls as stale so it checks the server 2015-08-22 00:52:50 +02:00
Petr Mrázek
c5bb33c716 GH-1158 generate client uuid for yggdrasil auth 2015-08-22 00:42:40 +02:00
Petr Mrázek
40ed2654c7 GH-1148 New instance name is either version or modpack 'base name'
Default names now use the placeholder text in the new instance dialog.
2015-08-21 09:00:35 +02:00
Petr Mrázek
875c707358 NOISSUE fix librainbow some more 2015-08-21 07:40:39 +02:00
Mrazek, Petr
e5f7676622 NOISSUE fix librainbow 2015-08-20 15:27:51 +02:00
Mrazek, Petr
cc4d0a0a8e GH-1149 add irc notification to travis 2015-08-20 12:29:33 +02:00
Petr Mrázek
d0e88011dc GH-1197 finish color stuff 2015-08-20 01:49:03 +02:00
Petr Mrázek
6858f1dd62 GH-1197 add console log color adaptation
rainbow library was part of KDE - KGuiAddons
2015-08-19 23:52:53 +02:00
Petr Mrázek
9681f724e5 NOISSUE revamp the minecraft log exception detection 2015-08-19 23:50:36 +02:00
Petr Mrázek
5bc29b06a9 NOISSUE fix log-related legacy instance crash and show hidden log files 2015-08-19 02:04:56 +02:00
Petr Mrázek
96fdaebb5c GH-926 implement log cleaning functionality
Also adds gzip compressed log support
2015-08-18 08:51:12 +02:00
Petr Mrázek
4e3af265da GH-1164 make sure the censor filter never contains empty keys 2015-08-16 02:17:50 +02:00
Petr Mrázek
d7b3887fe1 Revert "NOISSUE redo the launcher part"
This reverts commit c1f7dda8fe.
2015-08-14 23:30:12 +02:00
Petr Mrázek
0adb572a07 NOISSUE improve account UI 2015-08-14 02:27:01 +02:00
Petr Mrázek
8ed10c5b81 NOISSUE fix build on windows - pid -> processId 2015-08-14 01:01:50 +02:00
Petr Mrázek
d8caab515a GH-1053 add back update progress dialog 2015-07-26 17:55:29 +02:00
Petr Mrázek
6310f6569c GH-1053 move guessLevel to instances 2015-07-22 09:01:04 +02:00
Petr Mrázek
2fc18921b0 GH-1053 add launch step creation to legacy instances 2015-07-21 09:21:59 +02:00
Petr Mrázek
61c5a67777 GH-1053 explode launch task into many small steps, each a Task 2015-07-21 02:38:15 +02:00
Petr Mrázek
8e7caf4e25 GH-1053 move launch related things and rename them 2015-07-10 01:11:06 +02:00
Petr Mrázek
5dd48e89f5 GH-1034 do jar modding separate from update 2015-07-10 00:06:05 +02:00
Petr Mrázek
5133b0f34f GH-1053 cleanup 2015-07-05 02:47:22 +02:00
Petr Mrázek
5f41886d76 GH-1053 split settings dialog creation to its own namespace 2015-07-05 02:29:41 +02:00
Petr Mrázek
7f1320390c GH-1053 move launch process UI to a separate class 2015-07-05 01:54:30 +02:00
Petr Mrázek
526a511f45 GH-1053 move instance update into the launch task (BaseLauncher) 2015-07-04 20:02:43 +02:00
Petr Mrázek
5628d3d379 SCRATCH squash MinecraftLauncher into BaseLauncher
needs to be split differently
needs to be squashed together with the logic from MainWindow
2015-06-30 07:16:20 +02:00
Petr Mrázek
f86a39c21c SCRATCH fix BaseLauncher 2015-06-30 07:16:20 +02:00
Petr Mrázek
34ddfc7ecc GH-1053 base process and launch refactor, part 1 2015-06-30 07:16:20 +02:00
Jan Dalheimer
d14a61b0df GH-1100 Fix issues with LD_* variables when restarting on updates 2015-06-29 20:22:04 +02:00
Petr Mrázek
bbba63eca5 GH-1096 make ubuntu package work on both 32bit and 64bit machines 2015-06-28 22:44:53 +02:00
Petr Mrázek
d403d12d6a GH-1096 simple debian/ubuntu wrapper package
Built with `fakeroot dpkg-deb --build multimc_1.0-1`
2015-06-28 11:25:32 +02:00
Petr Mrázek
b343434f99 GH-1068 update changelog 2015-06-13 02:22:12 +02:00
Petr Mrázek
f723721bd0 GH-1069 env hack/passthhrough for LD_PRELOAD and LD_LIBRARY_PATH 2015-06-12 09:40:41 +02:00
Petr Mrázek
b427a652ad GH-1009 use .sh instead of .pyc - .pyc doesn't work at all 2015-06-11 02:33:02 +02:00
Petr Mrázek
9684d3b0a0 GH-1008 implement log window max line count
Defaults to 100k lines
2015-06-11 01:50:20 +02:00
Petr Mrázek
1feb4bb387 GH-1009 add mcedit.pyc as a valid mcedit 'executable' 2015-06-10 23:51:05 +02:00
Petr Mrázek
dd97ea8029 GH-1060 ugly XP hack is ugly 2015-06-10 03:06:29 +02:00
Petr Mrázek
88f5c8d347 GH-1060 create and delete update dir 2015-06-10 00:46:45 +02:00
Petr Mrázek
15b7c3039a GH-1060 update tweaks
* download to multimc folder hierarchy
* use rename, not copy
* keep backup after update
* clean previous backup before update
* it's not 'copy', it's 'replace'
2015-06-09 23:30:28 +02:00
Petr Mrázek
22c5ced5dc GH-1060 add a lot of error checking and reporting to the inner updater 2015-06-09 20:58:19 +02:00
Petr Mrázek
64b70acac1 GH-1060 tweaks to new update mechanism - logging 2015-06-09 00:48:25 +02:00
Petr Mrázek
82e05661d2 GH-1060 implement very basic updater (only linux and maybe osx right now) 2015-06-09 00:03:42 +02:00
Petr Mrázek
166813cb91 GH-1060 remove some old updater bits and pieces 2015-06-09 00:03:42 +02:00
Petr Mrázek
38e42ad794 GH-1049 fix bad ifdefs that prevent linux-specific env blacklisting 2015-06-08 23:54:30 +02:00
Petr Mrázek
6d7bff2476 GH-1060 remove updater code 2015-06-07 21:10:18 +02:00
Petr Mrázek
977e11ef8d GH-1051 ignore CDPATH 2015-06-06 23:40:13 +02:00
Jan Dalheimer
634bdcdbcb GH-1033 Set up travis for C++14 setup
No clang, no OSX, nothing
2015-06-06 23:27:58 +02:00
Jan Dalheimer
1e51b62c88 NOISSUE Comment and bugfix the Resource system 2015-06-06 21:23:05 +02:00
Petr Mrázek
24db645167 NOISSUE sanitize Json
Removes magical parameter madness.
All require* can throw
All ensure* need a default value and never throw
2015-06-06 21:23:05 +02:00
Petr Mrázek
dde35a0eb8 NOISSUE remove StandardTask 2015-06-06 21:23:05 +02:00
Petr Mrázek
57b75dfcf7 NOISSUE document exceptions a bit more 2015-06-06 21:23:05 +02:00
Petr Mrázek
06a67fbd38 NOISSUE use FS a bit more 2015-06-06 21:23:05 +02:00
Jan Dalheimer
3a8b238052 NOISSUE Various changes from multiauth that are unrelated to it 2015-06-06 21:23:05 +02:00
Petr Mrázek
161dc66c2c GH-1052 try using c++1y on OSX 2015-06-06 21:00:37 +02:00
Petr Mrázek
678da0b639 GH-1052 switch over to C++14 everywhere 2015-06-06 19:56:51 +02:00
Jan Dalheimer
db69a3dacd NOISSUE Don't attempt to package the accessible plugins on Qt >= 5.4 2015-06-06 15:10:19 +02:00
Petr Mrázek
47f919173e NOISSUE begin working on 0.4.8 and update changelog 2015-06-02 01:11:31 +02:00
Alex
c1f7dda8fe NOISSUE redo the launcher part 2015-06-02 00:11:09 +02:00
900 changed files with 37565 additions and 60219 deletions

View File

@@ -10,7 +10,7 @@ NamespaceIndentation: None
BreakBeforeBraces: Allman
AllowShortIfStatementsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
ColumnLimit: 96
ColumnLimit: 160
MaxEmptyLinesToKeep: 1
Standard: Cpp11

1
.gitattributes vendored
View File

@@ -1 +1,2 @@
*.pem -crlf
**/testdata/** -text -diff

50
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,50 @@
<!--
Before submitting this issue, please make sure you have:
1. Filled out this form completely, the only optional field is "additional info".
- Use as many details as possible and state the problem clearly.
2. Proof-read your ENTIRE issue report.
- Grammar and spelling mistakes make issue reports harder to understand.
3. Made sure your problem is not caused by an issue in your own modpack.
- We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
4. Given the issue a descriptive title.
- A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
5. Place all information below the ---- of lines.
- It makes the issue look pretty
If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
-->
System Information
-----------------------------
MultiMC version:
Operating System:
Summary of the issue or suggestion:
----------------------------------------------
What should happen:
------------------------------
Steps to reproduce the issue (Add more if needed):
-------------------------------------------------------------
1.
2.
3.
Suspected cause:
---------------------------
Logs/Screenshots:
----------------------------
[//]: # (Please refer to https://github.com/MultiMC/MultiMC5/wiki/Log-Upload for instructions on how to attach your logs.)
Additional Info:
---------------------------

3
.gitmodules vendored Normal file
View File

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

View File

@@ -1,23 +1,39 @@
# General set up
language: cpp
cache: apt
# Build matrix set up
compiler:
- gcc
- clang
cache: apt
before_install:
- sudo apt-add-repository -y ppa:beineri/opt-qt532
- sudo apt-add-repository -y ppa:kalakris/cmake
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
# - clang
os:
- linux
# - osx
env:
- QT_VERSION=5.4.2
- QT_VERSION=5.5.1
# - QT_VERSION=5.6.2
matrix:
exclude:
# only use clang on OS X
- os: osx
compiler: gcc
# only use the qt available from homebrew
- os: osx
env: QT_VERSION=5.4.2
- os: osx
env: QT_VERSION=5.5.1
# - os: osx
# env: QT_VERSION=5.6
# Install dependencies
install:
- sudo apt-get install -y -qq cmake qt53base qt53svg qt53tools qt53x11extras qt53webkit
- sudo apt-get install -y -qq g++-4.8
- if [ "$CXX" = "g++" ]; then export CXX="g++-4.8" CC="gcc-4.8"; fi
- source travis/prepare.sh # installs qt and cmake. need to source because some env vars are set from there
# Actual work
before_script:
- mkdir build
- cd build
- cmake -DCMAKE_PREFIX_PATH=/opt/qt53/lib/cmake ..
- cmake -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH ..
script:
- make -j4
- make test ARGS="-V"
notifications:
email: false
- make -j4 && make test ARGS="-V"

164
BUILD.md
View File

@@ -2,7 +2,9 @@ Build Instructions
==================
# Contents
* [Note](#note)
* [Getting the source](#source)
* [Linux](#linux)
* [Windows](#windows)
* [OS X](#os-x)
@@ -14,60 +16,90 @@ That would be anything outside your home folder. Before runing `make install`, m
you set the install path to something you have write access to. Never build this under
an administrator/root level account. Don't use `sudo`. It won't work and it's not supposed to work.
# Getting the source
Clone the source code using git and grab all the submodules:
```
git clone git@github.com:MultiMC/MultiMC5.git
git submodule init
git submodule update
```
# Linux
Getting the project to build and run on Linux is easy if you use Ubuntu 13.10 (or 13.04) and Qt's IDE, Qt Creator.
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
## Dependencies
* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)")
* A copy of the MultiMC source (clone it with git)
* cmake
* build-essential
* zlib (for example, zlib1g-dev)
* java (for example, openjdk-7-jdk)
* GL headers (for example, libgl1-mesa-dev)
## Build dependencies
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
* Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution.
* cmake 3.1 or newer
* zlib (for example, `zlib1g-dev`)
* java (for example, `openjdk-8-jdk`)
* GL headers (for example, `libgl1-mesa-dev`)
## Getting set up
### Building from command line
You need a source folder, a build folder and an install folder.
### Installing dependencies
Just run `sudo apt-get install <dependency>` for each dependency (other than Qt and the MultiMC source) from above.
Let's say you want everything in `~/MultiMC/`:
### Installing Qt
1. Run the Qt installer
2. Choose a place to install Qt,
3. Choose the components you want to install
- You need Qt 5.1.1/gcc 64-bit ticked,
- You need Tools/Qt Creator ticked,
```
# make all the folders
mkdir ~/MultiMC && cd ~/MultiMC
mkdir build
mkdir install
# clone the complete source
git clone --recursive git@github.com:MultiMC/MultiMC5.git src
# configure the project
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install ../src
# build & install (use -j with the number of cores your CPU has)
make -j8 install
```
You can use IDEs like KDevelop or QtCreator to open the CMake project if you want to work on the code.
### Installing Qt using the installer (optional)
1. Run the Qt installer.
2. Choose a place to install Qt.
3. Choose the components you want to install.
- You need Qt 5.4.1/gcc 64-bit ticked.
- You need Tools/Qt Creator ticked.
- Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements,
5. Double check the install details and then click "Install"
4. Accept the license agreements.
5. Double check the install details and then click "Install".
- Installation can take a very long time, go grab a cup of tea or something and let it work.
### Loading the project
1. Open Qt Creator,
2. Choose File->Open File or Project,
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt,
4. Read the instructions that just popped up about a build location and choose one,
5. You should see "Run CMake" in the window,
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.1.1 GCC 64bit)",
- Hit the "Run CMake" button,
### Loading the project in Qt Creator (optional)
1. Open Qt Creator.
2. Choose `File->Open File or Project`.
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
4. Read the instructions that just popped up about a build location and choose one.
5. You should see "Run CMake" in the window.
- Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)".
- Hit the "Run CMake" button.
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
6. Cross your fingers and press the Run button (bottom left of Qt Creator)!
6. Cross your fingers and press the Run button (bottom left of Qt Creator).
- If the project builds successfully it will run and the MultiMC5 window will pop up.
*These build instructions worked for me (Drayshak) on a fresh Ubuntu 13.10 x64 install. If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
**If this doesn't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
# Windows
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using VC's build tools as it uses some C++11 features that aren't implemented in it at the time of writing.
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
## Dependencies
* Qt 5.1.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Windows")
* OpenSSL (http://slproweb.com/products/Win32OpenSSL.html) ("Win32 OpenSSL \<version\> Light")
- Microsoft Visual C++ 2008 Redist. is required for this, there's a link on the OpenSSL download page above next to the main download.
* CMake (http://www.cmake.org/cmake/resources/software.html) ("Windows (Win32 Installer)")
* A copy of the MultiMC source (clone it with git)
* [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
Put it somewhere on the `PATH`, so that it is accessible from the console.
## Getting set up
@@ -75,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
1. Run the Qt installer
2. Choose a place to install Qt (C:\Qt is the default),
3. Choose the components you want to install
- You need Qt 5.1.1/MinGW 4.8 (32 bit) ticked,
- You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked,
- You need Tools/Qt Creator ticked,
- Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements,
@@ -84,8 +116,8 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
### Installing OpenSSL
1. Run the OpenSSL installer,
2. It's best to choose the option to copy OpenSSL DLLs to the /bin directory
- If you do this you'll need to add that directory (the default being C:\OpenSSL-Win32\bin) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
2. It's best to choose the option to copy OpenSSL DLLs to the `/bin` directory
- If you do this you'll need to add that directory (the default being `C:\OpenSSL-Win32\bin`) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
### Installing CMake
1. Run the CMake installer,
@@ -100,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- Otherwise you can skip this step.
6. You should see "Run CMake" in the window,
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.1.1 MinGW 32bit)",
- Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)",
- Hit the "Run CMake" button,
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
@@ -108,26 +140,46 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- If the project builds successfully it will run and the MultiMC5 window will pop up,
- Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error.
*These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
### Compile from command line on Windows
1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
2. Once that is open, change into your user directory, and clone MultiMC by doing `git clone --recursive https://github.com/MultiMC/MultiMC5.git`, and change directory to the folder you cloned to.
3. Make a build directory, and change directory to the directory and do `cmake -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:\Path\that\makes\sense\for\you`. By default, it will install to C:\Program Files (x86), which you might not want, if you want a local installation. If you want to install it to that directory, make sure to run the command window as administrator.
3. Do `mingw32-make -jX`, where X is the number of cores your CPU has plus one.
4. Now to wait for it to compile. This could take some time. Hopefully it compiles properly.
5. Run the command `mingw32-make install`, and it should install MultiMC, to whatever the `-DCMAKE_INSTALL_PREFIX` was.
6. In most cases, whenever compiling, the OpenSSL dll's aren't put into the directory to where MultiMC installs, meaning you cannot log in. The best way to fix this is just to do `copy C:\OpenSSL-Win32\*.dll C:\Where\you\installed\MultiMC\to`. This should copy the required OpenSSL dll's to log in.
# OS X
### Install prerequisites:
1. install homebrew
2. brew install qt5
3. brew tap homebrew/versions
4. brew install gcc48
5. brew install cmake
* install homebrew
* then:
```
brew install qt5
brew tap homebrew/versions
brew install gcc48
brew install cmake
```
### Build
1. git clone https://github.com/MultiMC/MultiMC5.git
2. cd MultiMC5
3. mkdir build
4. cd build
5. export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
6. export CC=/usr/local/bin/gcc-4.8
7. export CXX=/usr/local/bin/g++-4.8
8. cmake ..
9. make
Pick an installation path - this is where the final `.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration.
```
git clone https://github.com/MultiMC/MultiMC5.git
git submodule init
git submodule update
cd MultiMC5
mkdir build
cd build
export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
export CC=/usr/local/bin/gcc-4.8
export CXX=/usr/local/bin/g++-4.8
cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/Users/YOU/some/path/that/makes/sense/
make
make install
```
*These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC (Esper/#MultiMC)!*
**These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.9)
cmake_minimum_required(VERSION 3.1)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD)
@@ -10,54 +10,44 @@ if(WIN32)
cmake_policy(SET CMP0020 OLD)
endif()
project(Megatron)
project(MultiMC)
enable_testing()
######## Set CMake options ########
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
######## Set module path ########
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
set(MMC_SRC "${PROJECT_SOURCE_DIR}")
set(MMC_BIN "${PROJECT_BINARY_DIR}")
# Output all executables and shared libs in the main build folder, not in subfolders.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
endif()
set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars)
######## Set compiler flags ########
include(UseCXX11)
include(Coverage)
set(CMAKE_CXX_FLAGS " -Wall ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_STANDARD_REQUIRED true)
set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
set(CMAKE_CXX_FLAGS " -Wall -D_GLIBCXX_USE_CXX11_ABI=0 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
# cmake code needed for the coverity scan upload
include(Coverity)
################################ 3rd Party Libs ################################
# Find the required Qt parts
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Concurrent REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Test REQUIRED)
find_package(Qt5Xml REQUIRED)
include_directories(
${Qt5Core_INCLUDE_DIRS}
${Qt5Widgets_INCLUDE_DIRS}
${Qt5Concurrent_INCLUDE_DIRS}
${Qt5Test_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${Qt5Xml_INCLUDE_DIRS}
)
find_package(Qt5Core)
find_package(Qt5Widgets)
find_package(Qt5Concurrent)
find_package(Qt5Network)
find_package(Qt5Test)
find_package(Qt5Xml)
# The Qt5 cmake files don't provide its install paths, so ask qmake.
include(QMakeQuery)
@@ -80,61 +70,43 @@ set_directory_properties(PROPERTIES EP_BASE External)
# Add quazip
add_definitions(-DQUAZIP_STATIC)
set(QUAZIP_VERSION "0.7.1")
if(NOT EXISTS ${CMAKE_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
file(DOWNLOAD http://downloads.sourceforge.net/project/quazip/quazip/${QUAZIP_VERSION}/quazip-${QUAZIP_VERSION}.tar.gz ${CMAKE_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz)
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_BINARY_DIR}/quazip-${QUAZIP_VERSION}.tar.gz
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_BINARY_DIR}/External/Install/QuaZIP/include/quazip")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/include/quazip")
if(UNIX)
set(QUAZIP_LIBRARIES -L"${CMAKE_BINARY_DIR}/External/Install/QuaZIP/lib" quazip z)
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip z)
else()
set(QUAZIP_LIBRARIES -L"${CMAKE_BINARY_DIR}/External/Install/QuaZIP/lib" quazip)
set(QUAZIP_LIBRARIES -L"${CMAKE_CURRENT_BINARY_DIR}/External/Install/QuaZIP/lib" quazip)
endif()
# Add the markdown parser thing
add_subdirectory(depends/hoedown)
include_directories(${HOEDOWN_INCLUDE_DIR})
option(NBT_BUILD_SHARED "Build NBT shared library" ON)
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++)
add_subdirectory(libraries/libnbtplusplus)
# Add the java launcher and checker
add_subdirectory(depends/launcher)
add_subdirectory(depends/javacheck)
# Add xz decompression
add_subdirectory(depends/xz-embedded)
include_directories(${XZ_INCLUDE_DIR})
# Add pack200 decompression
add_subdirectory(depends/pack200)
include_directories(${PACK200_INCLUDE_DIR})
######## MultiMC Libs ########
# Add the util library.
add_definitions(-DLIBUTIL_STATIC)
add_subdirectory(depends/util)
include_directories(${LIBUTIL_INCLUDE_DIR})
# Add the GUI -> Logic connection header
add_subdirectory(depends/LogicalGui)
include_directories(${LOGICALGUI_INCLUDE_DIR})
# Add the GUI -> Logic connection header
add_subdirectory(depends/iconfix)
include_directories(${ICONFIX_INCLUDE_DIR})
include(Coverity)
add_subdirectory(libraries/ganalytics) # google analytics library
add_subdirectory(libraries/systeminfo) # system information library
add_subdirectory(libraries/hoedown) # markdown parser
add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
############################### Built Artifacts ###############################
add_subdirectory(tests)
add_subdirectory(logic)
add_subdirectory(api/logic)
add_subdirectory(api/gui)
add_subdirectory(application)
add_subdirectory(mmc_updater)
add_subdirectory(wonkoclient)

205
COPYING.md Normal file
View File

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

5
LGPL_EXCEPTION.txt Normal file
View File

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

502
LICENSE.LPGLv21 Normal file
View File

@@ -0,0 +1,502 @@
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,17 +1,33 @@
![MultiMC](http://i.imgur.com/QJXbz.png)
MultiMC 5 [![Build Status](https://travis-ci.org/MultiMC/MultiMC5.svg?branch=develop)](https://travis-ci.org/MultiMC/MultiMC5) [![Translation Status](http://weblate.robotbrain.info/widgets/multimc/-/shields-badge.svg)](http://weblate.robotbrain.info/engage/multimc/?utm_source=widget) [![Stories in Ready](https://badge.waffle.io/MultiMC/MultiMC5.svg?label=ready&title=Ready)](http://waffle.io/MultiMC/MultiMC5)
MultiMC 5
=========
MultiMC is a custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once. It also allows you to easily install and remove mods by simply dragging and dropping. Here are the current [features](https://github.com/MultiMC/MultiMC5/wiki#features) of MultiMC.
## Building
Check [BUILD.md](BUILD.md) for build instructions.
## Contributing
The repository is currently managed by @peterix. If you'd like to contribute to the project please talk to us on IRC (Esper/#MultiMC) first! This helps us organise ideas and keep in contact with you, and we're unlikely to accept anything blindly.
## Development
The project uses C++ and Qt5 as the language and base framework. This might seem odd in the Minecraft community, but allows using 25MB of RAM, where other tools use an excessive amount of resources for no reason.
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted, but there are issues with it on Windows. If you have trouble setting it up, check [.clang-format](.clang-format) manually. We don't accept pull requests with poor formatting. If you have questions, talk to us on IRC (Esper/#MultiMC) _before_ submitting a pull request.
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [![Stories in Ready](https://badge.waffle.io/MultiMC/MultiMC5.svg?label=ready&title=Ready)](http://waffle.io/MultiMC/MultiMC5)
### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
The ci server is running at [ci.multimc.org](http://ci.multimc.org), where you can watch the builds happen in (or very close to) real time. It can also serve as a nice reference on how to set up the build environment on your end.
According to travis.ci, the builds are currently [![Build Status](https://travis-ci.org/MultiMC/MultiMC5.svg?branch=develop)](https://travis-ci.org/MultiMC/MultiMC5)
### Code formatting
We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted.
## Translations
Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5) or using our [translation server](http://translate.multimc.org). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
Currently, MultiMC is [![Translation Status](http://translate.multimc.org/widgets/multimc/-/shields-badge.svg)](http://translate.multimc.org/engage/multimc/?utm_source=widget)
## Forking/Redistributing
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
@@ -22,7 +38,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License
Copyright &copy; 2013-2015 MultiMC Contributors
Copyright &copy; 2013-2017 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).

28
api/gui/CMakeLists.txt Normal file
View File

@@ -0,0 +1,28 @@
project(MultiMC_gui LANGUAGES CXX)
set(GUI_SOURCES
DesktopServices.h
DesktopServices.cpp
# Icons
icons/MMCIcon.h
icons/MMCIcon.cpp
icons/IconList.h
icons/IconList.cpp
SkinUtils.cpp
SkinUtils.h
)
################################ COMPILE ################################
add_library(MultiMC_gui SHARED ${GUI_SOURCES})
set_target_properties(MultiMC_gui PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_gui)
# Link
target_link_libraries(MultiMC_gui iconfix MultiMC_logic)
qt5_use_modules(MultiMC_gui Gui)
# Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")

149
api/gui/DesktopServices.cpp Normal file
View File

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

37
api/gui/DesktopServices.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include <QUrl>
#include <QString>
#include "multimc_gui_export.h"
/**
* This wraps around QDesktopServices and adds workarounds where needed
* Use this instead of QDesktopServices!
*/
namespace DesktopServices
{
/**
* Open a file in whatever application is applicable
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &path);
/**
* Open a file in the specified application
*/
MULTIMC_GUI_EXPORT bool openFile(const QString &application, const QString &path, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Run an application
*/
MULTIMC_GUI_EXPORT bool run(const QString &application,const QStringList &args, const QString & workingDirectory = QString(), qint64 *pid = 0);
/**
* Open a directory
*/
MULTIMC_GUI_EXPORT bool openDirectory(const QString &path, bool ensureExists = false);
/**
* Open the URL, most likely in a browser. Maybe.
*/
MULTIMC_GUI_EXPORT bool openUrl(const QUrl &url);
};

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* limitations under the License.
*/
#include "minecraft/SkinUtils.h"
#include "SkinUtils.h"
#include "net/HttpMetaCache.h"
#include "Env.h"

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
#include <QPixmap>
#include "multimc_gui_export.h"
namespace SkinUtils
{
QPixmap getFaceFromCache(QString username, int height = 64, int width = 64);
QPixmap MULTIMC_GUI_EXPORT getFaceFromCache(QString id, int height = 64, int width = 64);
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
*/
#include "IconList.h"
#include <pathutils.h>
#include <FileSystem.h>
#include <QMap>
#include <QEventLoop>
#include <QMimeData>
@@ -25,15 +25,23 @@
#define MAX_SIZE 1024
IconList::IconList(QString builtinPath, QString path, QObject *parent) : QAbstractListModel(parent)
IconList::IconList(const QStringList &builtinPaths, QString path, QObject *parent) : QAbstractListModel(parent)
{
QSet<QString> builtinNames;
// add builtin icons
QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
for(auto & builtinPath: builtinPaths)
{
QString key = file_info.baseName();
addIcon(key, key, file_info.absoluteFilePath(), MMCIcon::Builtin);
QDir instance_icons(builtinPath);
auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name);
for (auto file_info : file_info_list)
{
builtinNames.insert(file_info.baseName());
}
}
for(auto & builtinName : builtinNames)
{
addThemeIcon(builtinName);
}
m_watcher.reset(new QFileSystemWatcher());
@@ -57,7 +65,7 @@ void IconList::directoryChanged(const QString &path)
startWatching();
}
if(!m_dir.exists())
if(!ensureFolderPathExists(m_dir.absolutePath()))
if(!FS::ensureFolderPathExists(m_dir.absolutePath()))
return;
m_dir.refresh();
auto new_list = m_dir.entryList(QDir::Files, QDir::Name);
@@ -70,9 +78,9 @@ void IconList::directoryChanged(const QString &path)
QList<QString> current_list;
for (auto &it : icons)
{
if (!it.has(MMCIcon::FileBased))
if (!it.has(IconType::FileBased))
continue;
current_list.push_back(it.m_images[MMCIcon::FileBased].filename);
current_list.push_back(it.m_images[IconType::FileBased].filename);
}
QSet<QString> current_set = current_list.toSet();
@@ -90,8 +98,8 @@ void IconList::directoryChanged(const QString &path)
int idx = getIconIndex(key);
if (idx == -1)
continue;
icons[idx].remove(MMCIcon::FileBased);
if (icons[idx].type() == MMCIcon::ToBeDeleted)
icons[idx].remove(IconType::FileBased);
if (icons[idx].type() == IconType::ToBeDeleted)
{
beginRemoveRows(QModelIndex(), idx, idx);
icons.remove(idx);
@@ -111,7 +119,7 @@ void IconList::directoryChanged(const QString &path)
qDebug() << "Adding " << add;
QFileInfo addfile(add);
QString key = addfile.baseName();
if (addIcon(key, QString(), addfile.filePath(), MMCIcon::FileBased))
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased))
{
m_watcher->addPath(add);
emit iconUpdated(key);
@@ -133,7 +141,7 @@ void IconList::fileChanged(const QString &path)
if (!icon.availableSizes().size())
return;
icons[idx].m_images[MMCIcon::FileBased].icon = icon;
icons[idx].m_images[IconType::FileBased].icon = icon;
dataChanged(index(idx), index(idx));
emit iconUpdated(key);
}
@@ -149,7 +157,7 @@ void IconList::SettingChanged(const Setting &setting, QVariant value)
void IconList::startWatching()
{
auto abs_path = m_dir.absolutePath();
ensureFolderPathExists(abs_path);
FS::ensureFolderPathExists(abs_path);
is_watching = m_watcher->addPath(abs_path);
if (is_watching)
{
@@ -243,14 +251,14 @@ int IconList::rowCount(const QModelIndex &parent) const
return icons.size();
}
void IconList::installIcons(QStringList iconFiles)
void IconList::installIcons(const QStringList &iconFiles)
{
for (QString file : iconFiles)
{
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = PathCombine(m_dir.dirName(), fileinfo.fileName());
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
@@ -261,17 +269,17 @@ void IconList::installIcons(QStringList iconFiles)
}
}
bool IconList::iconFileExists(QString key)
bool IconList::iconFileExists(const QString &key) const
{
auto iconEntry = icon(key);
if(!iconEntry)
{
return false;
}
return iconEntry->has(MMCIcon::FileBased);
return iconEntry->has(IconType::FileBased);
}
const MMCIcon *IconList::icon(QString key)
const MMCIcon *IconList::icon(const QString &key) const
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
@@ -279,20 +287,47 @@ const MMCIcon *IconList::icon(QString key)
return &icons[iconIdx];
}
bool IconList::deleteIcon(QString key)
bool IconList::deleteIcon(const QString &key)
{
int iconIdx = getIconIndex(key);
if (iconIdx == -1)
return false;
auto &iconEntry = icons[iconIdx];
if (iconEntry.has(MMCIcon::FileBased))
if (iconEntry.has(IconType::FileBased))
{
return QFile::remove(iconEntry.m_images[MMCIcon::FileBased].filename);
return QFile::remove(iconEntry.m_images[IconType::FileBased].filename);
}
return false;
}
bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type type)
bool IconList::addThemeIcon(const QString& key)
{
auto iter = name_index.find(key);
if (iter != name_index.end())
{
auto &oldOne = icons[*iter];
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
}
else
{
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
}
bool IconList::addIcon(const QString &key, const QString &name, const QString &path, const IconType type)
{
// replace the icon even? is the input valid?
QIcon icon(path);
@@ -323,6 +358,14 @@ bool IconList::addIcon(QString key, QString name, QString path, MMCIcon::Type ty
}
}
void IconList::saveIcon(const QString &key, const QString &path, const char * format) const
{
auto icon = getIcon(key);
auto pixmap = icon.pixmap(128, 128);
pixmap.save(path, format);
}
void IconList::reindex()
{
name_index.clear();
@@ -334,7 +377,7 @@ void IconList::reindex()
}
}
QIcon IconList::getIcon(QString key)
QIcon IconList::getIcon(const QString &key) const
{
int icon_index = getIconIndex(key);
@@ -349,15 +392,12 @@ QIcon IconList::getIcon(QString key)
return QIcon();
}
QIcon IconList::getBigIcon(QString key)
QIcon IconList::getBigIcon(const QString &key) const
{
int icon_index = getIconIndex(key);
if (icon_index == -1)
key = "infinity";
// Fallback for icons that don't exist.
icon_index = getIconIndex(key);
icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
if (icon_index == -1)
return QIcon();
@@ -366,12 +406,9 @@ QIcon IconList::getBigIcon(QString key)
return QIcon(bigone);
}
int IconList::getIconIndex(QString key)
int IconList::getIconIndex(const QString &key) const
{
if (key == "default")
key = "infinity";
auto iter = name_index.find(key);
auto iter = name_index.find(key == "default" ? "infinity" : key);
if (iter != name_index.end())
return *iter;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,36 +24,40 @@
#include "MMCIcon.h"
#include "settings/Setting.h"
#include "Env.h" // there is a global icon list inside Env.
#include <icons/IIconList.h>
#include "multimc_gui_export.h"
class QFileSystemWatcher;
class IconList : public QAbstractListModel
class MULTIMC_GUI_EXPORT IconList : public QAbstractListModel, public IIconList
{
Q_OBJECT
public:
explicit IconList(QString builtinPath, QString path, QObject *parent = 0);
explicit IconList(const QStringList &builtinPaths, QString path, QObject *parent = 0);
virtual ~IconList() {};
QIcon getIcon(QString key);
QIcon getBigIcon(QString key);
int getIconIndex(QString key);
QIcon getIcon(const QString &key) const;
QIcon getBigIcon(const QString &key) const;
int getIconIndex(const QString &key) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool addIcon(QString key, QString name, QString path, MMCIcon::Type type);
bool deleteIcon(QString key);
bool iconFileExists(QString key);
bool addThemeIcon(const QString &key);
bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) override;
void saveIcon(const QString &key, const QString &path, const char * format) const override;
bool deleteIcon(const QString &key) override;
bool iconFileExists(const QString &key) const override;
virtual QStringList mimeTypes() const;
virtual Qt::DropActions supportedDropActions() const;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
const QModelIndex &parent);
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
virtual QStringList mimeTypes() const override;
virtual Qt::DropActions supportedDropActions() const override;
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void installIcons(QStringList iconFiles);
void installIcons(const QStringList &iconFiles) override;
const MMCIcon * icon(QString key);
const MMCIcon * icon(const QString &key) const;
void startWatching();
void stopWatching();

104
api/gui/icons/MMCIcon.cpp Normal file
View File

@@ -0,0 +1,104 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MMCIcon.h"
#include <QFileInfo>
#include <xdgicon.h>
IconType operator--(IconType &t, int)
{
IconType temp = t;
switch (t)
{
case IconType::Builtin:
t = IconType::ToBeDeleted;
break;
case IconType::Transient:
t = IconType::Builtin;
break;
case IconType::FileBased:
t = IconType::Transient;
break;
default:
{
}
}
return temp;
}
IconType MMCIcon::type() const
{
return m_current_type;
}
QString MMCIcon::name() const
{
if (m_name.size())
return m_name;
return m_key;
}
bool MMCIcon::has(IconType _type) const
{
return m_images[_type].present();
}
QIcon MMCIcon::icon() const
{
if (m_current_type == IconType::ToBeDeleted)
return QIcon();
auto & icon = m_images[m_current_type].icon;
if(!icon.isNull())
return icon;
// FIXME: inject this.
return XdgIcon::fromTheme(m_images[m_current_type].key);
}
void MMCIcon::remove(IconType rm_type)
{
m_images[rm_type].filename = QString();
m_images[rm_type].icon = QIcon();
for (auto iter = rm_type; iter != IconType::ToBeDeleted; iter--)
{
if (m_images[iter].present())
{
m_current_type = iter;
return;
}
}
m_current_type = IconType::ToBeDeleted;
}
void MMCIcon::replace(IconType new_type, QIcon icon, QString path)
{
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
{
m_current_type = new_type;
}
m_images[new_type].icon = icon;
m_images[new_type].filename = path;
m_images[new_type].key = QString();
}
void MMCIcon::replace(IconType new_type, const QString& key)
{
if (new_type > m_current_type || m_current_type == IconType::ToBeDeleted)
{
m_current_type = new_type;
}
m_images[new_type].icon = QIcon();
m_images[new_type].filename = QString();
m_images[new_type].key = key;
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,36 +17,33 @@
#include <QString>
#include <QDateTime>
#include <QIcon>
struct MMCImage
#include <icons/IIconList.h>
#include "multimc_gui_export.h"
struct MULTIMC_GUI_EXPORT MMCImage
{
QIcon icon;
QString key;
QString filename;
QDateTime changed;
bool present() const
{
return !icon.isNull();
return !icon.isNull() || !key.isEmpty();
}
};
struct MMCIcon
struct MULTIMC_GUI_EXPORT MMCIcon
{
enum Type : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
QString m_key;
QString m_name;
MMCImage m_images[ICONS_TOTAL];
Type m_current_type = ToBeDeleted;
IconType m_current_type = ToBeDeleted;
Type type() const;
IconType type() const;
QString name() const;
bool has(Type _type) const;
bool has(IconType _type) const;
QIcon icon() const;
void remove(Type rm_type);
void replace(Type new_type, QIcon icon, QString path = QString());
void remove(IconType rm_type);
void replace(IconType new_type, QIcon icon, QString path = QString());
void replace(IconType new_type, const QString &key);
};

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#include <QFile>
#include "BaseInstaller.h"
#include "minecraft/OneSixInstance.h"
#include "minecraft/onesix/OneSixInstance.h"
BaseInstaller::BaseInstaller()
{

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,17 @@
#include <memory>
#include "multimc_logic_export.h"
class OneSixInstance;
class QDir;
class QString;
class QObject;
class Task;
struct BaseVersion;
class BaseVersion;
typedef std::shared_ptr<BaseVersion> BaseVersionPtr;
class BaseInstaller
class MULTIMC_LOGIC_EXPORT BaseInstaller
{
public:
BaseInstaller();

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,15 +17,15 @@
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "pathutils.h"
#include <cmdutils.h>
#include "minecraft/MinecraftVersionList.h"
#include "icons/IconList.h"
#include "FileSystem.h"
#include "Commandline.h"
BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
: QObject()
@@ -35,21 +35,58 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("name", "Unnamed Instance");
m_settings->registerSetting("iconKey", "default");
connect(ENV.icons().get(), SIGNAL(iconUpdated(QString)), SLOT(iconUpdated(QString)));
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
// Custom Commands
m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"));
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"));
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"));
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
m_settings->registerOverride(globalSettings->getSetting("PreLaunchCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("WrapperCommand"), commandSetting);
m_settings->registerOverride(globalSettings->getSetting("PostExitCommand"), commandSetting);
// Console
m_settings->registerSetting("OverrideConsole", false);
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"));
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"));
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"));
auto consoleSetting = m_settings->registerSetting("OverrideConsole", false);
m_settings->registerOverride(globalSettings->getSetting("ShowConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("AutoCloseConsole"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("ShowConsoleOnError"), consoleSetting);
m_settings->registerOverride(globalSettings->getSetting("LogPrePostOutput"), consoleSetting);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleMaxLines"), nullptr);
m_settings->registerPassthrough(globalSettings->getSetting("ConsoleOverflowStop"), nullptr);
}
QString BaseInstance::getPreLaunchCommand()
{
return settings()->get("PreLaunchCommand").toString();
}
QString BaseInstance::getWrapperCommand()
{
return settings()->get("WrapperCommand").toString();
}
QString BaseInstance::getPostExitCommand()
{
return settings()->get("PostExitCommand").toString();
}
int BaseInstance::getConsoleMaxLines() const
{
auto lineSetting = settings()->getSetting("ConsoleMaxLines");
bool conversionOk = false;
int maxLines = lineSetting->get().toInt(&conversionOk);
if(!conversionOk)
{
maxLines = lineSetting->defValue().toInt();
qWarning() << "ConsoleMaxLines has nonsensical value, defaulting to" << maxLines;
}
return maxLines;
}
bool BaseInstance::shouldStopOnConsoleOverflow() const
{
return settings()->get("ConsoleOverflowStop").toBool();
}
void BaseInstance::iconUpdated(QString key)
@@ -60,10 +97,32 @@ void BaseInstance::iconUpdated(QString key)
}
}
void BaseInstance::invalidate()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been invalidated.";
}
void BaseInstance::nuke()
{
deletePath(instanceRoot());
emit nuked(this);
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
FS::deletePath(instanceRoot());
}
void BaseInstance::changeStatus(BaseInstance::Status newStatus)
{
Status status = currentStatus();
if(status != newStatus)
{
m_status = newStatus;
emit statusChanged(status, newStatus);
}
}
BaseInstance::Status BaseInstance::currentStatus() const
{
return m_status;
}
QString BaseInstance::id() const
@@ -78,7 +137,40 @@ bool BaseInstance::isRunning() const
void BaseInstance::setRunning(bool running)
{
if(running == m_isRunning)
return;
m_isRunning = running;
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
}
else
{
qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
emit propertiesChanged(this);
}
emit runningStatusChanged(running);
}
int64_t BaseInstance::totalTimePlayed() const
{
qint64 current = settings()->get("totalTimePlayed").toLongLong();
if(m_isRunning)
{
QDateTime timeNow = QDateTime::currentDateTime();
return current + m_timeStarted.secsTo(timeNow);
}
return current;
}
void BaseInstance::resetTimePlayed()
{
settings()->reset("totalTimePlayed");
}
QString BaseInstance::instanceType() const
@@ -101,44 +193,9 @@ SettingsObjectPtr BaseInstance::settings() const
return m_settings;
}
BaseInstance::InstanceFlags BaseInstance::flags() const
{
return m_flags;
}
void BaseInstance::setFlags(const InstanceFlags &flags)
{
if (flags != m_flags)
{
m_flags = flags;
emit flagsChanged();
emit propertiesChanged(this);
}
}
void BaseInstance::setFlag(const BaseInstance::InstanceFlag flag)
{
// nothing to set?
if(flag & m_flags)
return;
m_flags |= flag;
emit flagsChanged();
emit propertiesChanged(this);
}
void BaseInstance::unsetFlag(const BaseInstance::InstanceFlag flag)
{
// nothing to unset?
if(!(flag & m_flags))
return;
m_flags &= ~flag;
emit flagsChanged();
emit propertiesChanged(this);
}
bool BaseInstance::canLaunch() const
{
return !(flags() & VersionBrokenFlag);
return (!hasVersionBroken() && !isRunning());
}
bool BaseInstance::reload()
@@ -225,5 +282,26 @@ QString BaseInstance::windowTitle() const
QStringList BaseInstance::extraArguments() const
{
return Util::Commandline::splitArgs(settings()->get("JvmArgs").toString());
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
return m_launchProcess;
}
void BaseInstance::setProvider(BaseInstanceProvider* provider)
{
// only once.
assert(!m_provider);
if(m_provider)
{
qWarning() << "Provider set more than once for instance" << id();
}
m_provider = provider;
}
BaseInstanceProvider* BaseInstance::provider() const
{
return m_provider;
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,21 +14,29 @@
*/
#pragma once
#include <cassert>
#include <QObject>
#include "QObjectPtr.h"
#include <QDateTime>
#include <QSet>
#include <QProcess>
#include "settings/SettingsObject.h"
#include "settings/INIFile.h"
#include "BaseVersionList.h"
#include "auth/MojangAccount.h"
#include "minecraft/auth/MojangAccount.h"
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
class QDir;
class Task;
class BaseProcess;
class LaunchTask;
class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
@@ -41,18 +49,25 @@ typedef std::shared_ptr<BaseInstance> InstancePtr;
* To create a new instance type, create a new class inheriting from this class
* and implement the pure virtual functions.
*/
class 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
protected:
/// no-touchy!
BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
public: /* types */
enum class Status
{
Present,
Gone // either nuked or invalidated
};
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {};
virtual void copy(const QDir &newDir) {}
virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
virtual void init() = 0;
@@ -60,12 +75,25 @@ public:
/// responsible of cleaning up the husk
void nuke();
/***
* the instance has been invalidated - it is no longer tracked by MultiMC for some reason,
* but it has not necessarily been deleted.
*
* Happens when the instance folder changes to some other location, or the instance is removed by external means.
*/
void invalidate();
/// The instance's ID. The ID SHALL be determined by MMC internally. The ID IS guaranteed to
/// be unique.
virtual QString id() const;
void setRunning(bool running);
bool isRunning() const;
int64_t totalTimePlayed() const;
void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance
QString instanceType() const;
@@ -89,6 +117,16 @@ public:
void setGroupInitial(QString val);
void setGroupPost(QString val);
QString getPreLaunchCommand();
QString getPostExitCommand();
QString getWrapperCommand();
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
{
return level;
};
virtual QStringList extraArguments() const;
virtual QString intendedVersionId() const = 0;
@@ -135,35 +173,102 @@ public:
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
virtual std::shared_ptr<Task> doUpdate() = 0;
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
/// returns a valid process, ready for launch with the given account.
virtual BaseProcess *prepareForLaunch(AuthSessionPtr account) = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
/// do any necessary cleanups after the instance finishes. also runs before
/// 'prepareForLaunch'
virtual void cleanupAfterRun() = 0;
/// returns the current launch task (if any)
std::shared_ptr<LaunchTask> getLaunchTask();
/*!
* Returns a task that should be done right before launch
* This task should do any extra preparations needed
*/
virtual std::shared_ptr<Task> createJarModdingTask() = 0;
/*!
* Create envrironment variables for running the instance
*/
virtual QProcessEnvironment createEnvironment() = 0;
/*!
* Returns a matcher that can maps relative paths within the instance to whether they are 'log files'
*/
virtual IPathMatcher::Ptr getLogFileMatcher() = 0;
/*!
* Returns the root folder to use for looking up log files
*/
virtual QString getLogFileRoot() = 0;
virtual QString getStatusbarDescription() = 0;
/// FIXME: this really should be elsewhere...
virtual QString instanceConfigFolder() const = 0;
enum InstanceFlag
/// get variables this instance exports
virtual QMap<QString, QString> getVariables() const = 0;
virtual QString typeName() const = 0;
bool hasVersionBroken() const
{
VersionBrokenFlag = 0x01,
UpdateAvailable = 0x02
};
Q_DECLARE_FLAGS(InstanceFlags, InstanceFlag);
InstanceFlags flags() const;
void setFlags(const InstanceFlags &flags);
void setFlag(const InstanceFlag flag);
void unsetFlag(const InstanceFlag flag);
return m_hasBrokenVersion;
}
void setVersionBroken(bool value)
{
if(m_hasBrokenVersion != value)
{
m_hasBrokenVersion = value;
emit propertiesChanged(this);
}
}
bool hasUpdateAvailable() const
{
return m_hasUpdate;
}
void setUpdateAvailable(bool value)
{
if(m_hasUpdate != value)
{
m_hasUpdate = value;
emit propertiesChanged(this);
}
}
bool hasCrashed() const
{
return m_crashed;
}
void setCrashed(bool value)
{
if(m_crashed != value)
{
m_crashed = value;
emit propertiesChanged(this);
}
}
bool canLaunch() const;
virtual bool canExport() const = 0;
virtual bool reload();
/**
* 'print' a verbose desription of the instance into a QStringList
*/
virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
Status currentStatus() const;
int getConsoleMaxLines() const;
bool shouldStopOnConsoleOverflow() const;
protected:
void changeStatus(Status newStatus);
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@@ -173,24 +278,33 @@ signals:
* \brief Signal emitted when groups are affected in any way
*/
void groupChanged();
/*!
* \brief The instance just got nuked. Hurray!
*/
void nuked(BaseInstance *inst);
void flagsChanged();
void launchTaskChanged(std::shared_ptr<LaunchTask>);
void runningStatusChanged(bool running);
void statusChanged(Status from, Status to);
protected slots:
void iconUpdated(QString key);
protected:
protected: /* data */
QString m_rootDir;
QString m_group;
SettingsObjectPtr m_settings;
InstanceFlags m_flags;
// InstanceFlags m_flags;
bool m_isRunning = false;
std::shared_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted;
BaseInstanceProvider * m_provider = nullptr;
private: /* data */
Status m_status = Status::Present;
bool m_crashed = false;
bool m_hasUpdate = false;
bool m_hasBrokenVersion = false;
};
Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)

View File

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

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,9 @@
/*!
* An abstract base class for versions.
*/
struct BaseVersion
class BaseVersion
{
public:
virtual ~BaseVersion() {}
/*!
* A string used to identify this version in config files.

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ QVariant BaseVersionList::data(const QModelIndex &index, int role) const
}
}
BaseVersionList::RoleList BaseVersionList::providesRoles()
BaseVersionList::RoleList BaseVersionList::providesRoles() const
{
return {VersionPointerRole, VersionRole, VersionIdRole, TypeRole};
}
@@ -87,3 +87,18 @@ int BaseVersionList::columnCount(const QModelIndex &parent) const
{
return 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
roles.insert(VersionRole, "version");
roles.insert(VersionIdRole, "versionId");
roles.insert(ParentGameVersionRole, "parentGameVersion");
roles.insert(RecommendedRole, "recommended");
roles.insert(LatestRole, "latest");
roles.insert(TypeRole, "type");
roles.insert(BranchRole, "branch");
roles.insert(PathRole, "path");
roles.insert(ArchitectureRole, "architecture");
return roles;
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +20,8 @@
#include <QAbstractListModel>
#include "BaseVersion.h"
class Task;
#include "tasks/Task.h"
#include "multimc_logic_export.h"
/*!
* \brief Class that each instance type's version list derives from.
@@ -35,7 +35,7 @@ class Task;
* all have a default implementation, but they can be overridden by plugins to
* change the behavior of the list.
*/
class BaseVersionList : public QAbstractListModel
class MULTIMC_LOGIC_EXPORT BaseVersionList : public QAbstractListModel
{
Q_OBJECT
public:
@@ -50,9 +50,10 @@ public:
TypeRole,
BranchRole,
PathRole,
ArchitectureRole
ArchitectureRole,
SortRole
};
typedef QList<ModelRoles> RoleList;
typedef QList<int> RoleList;
explicit BaseVersionList(QObject *parent = 0);
@@ -78,9 +79,10 @@ public:
virtual QVariant data(const QModelIndex &index, int role) const;
virtual int rowCount(const QModelIndex &parent) const;
virtual int columnCount(const QModelIndex &parent) const;
virtual QHash<int, QByteArray> roleNames() const override;
//! which roles are provided by this version list?
virtual RoleList providesRoles();
virtual RoleList providesRoles() const;
/*!
* \brief Finds a version by its descriptor.
@@ -104,7 +106,7 @@ public:
/*!
* Sorts the version list.
*/
virtual void sort() = 0;
virtual void sortVersions() = 0;
protected
slots:

498
api/logic/CMakeLists.txt Normal file
View File

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

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -15,14 +15,12 @@
* limitations under the License.
*/
#include "include/cmdutils.h"
#include "Commandline.h"
/**
* @file libutil/src/cmdutils.cpp
*/
namespace Util
{
namespace Commandline
{
@@ -482,5 +480,4 @@ void Parser::getPrefix(QString &opt, QString &flag)
ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString())
{
}
}
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -25,15 +25,13 @@
#include <QHash>
#include <QStringList>
#include "libutil_config.h"
#include "multimc_logic_export.h"
/**
* @file libutil/include/cmdutils.h
* @brief commandline parsing and processing utilities
*/
namespace Util
{
namespace Commandline
{
@@ -42,7 +40,7 @@ namespace Commandline
* @param args the argument string
* @return a QStringList containing all arguments
*/
LIBUTIL_EXPORT QStringList splitArgs(QString args);
MULTIMC_LOGIC_EXPORT QStringList splitArgs(QString args);
/**
* @brief The FlagStyle enum
@@ -85,7 +83,7 @@ enum Enum
/**
* @brief The ParsingError class
*/
class LIBUTIL_EXPORT ParsingError : public std::runtime_error
class MULTIMC_LOGIC_EXPORT ParsingError : public std::runtime_error
{
public:
ParsingError(const QString &what);
@@ -94,7 +92,7 @@ public:
/**
* @brief The Parser class
*/
class LIBUTIL_EXPORT Parser
class MULTIMC_LOGIC_EXPORT Parser
{
public:
/**
@@ -252,4 +250,3 @@ private:
void getPrefix(QString &opt, QString &flag);
};
}
}

View File

@@ -1,6 +1,5 @@
#include "Env.h"
#include "net/HttpMetaCache.h"
#include "icons/IconList.h"
#include "BaseVersion.h"
#include "BaseVersionList.h"
#include <QDir>
@@ -8,104 +7,71 @@
#include <QNetworkAccessManager>
#include <QDebug>
#include "tasks/Task.h"
#include "wonko/WonkoIndex.h"
#include <QDebug>
class Env::Private
{
public:
QNetworkAccessManager m_qnam;
shared_qobject_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IIconList> m_iconlist;
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
shared_qobject_ptr<WonkoIndex> m_wonkoIndex;
QString m_wonkoRootUrl;
};
static Env * instance;
/*
* The *NEW* global rat nest of an object. Handle with care.
*/
Env::Env()
{
m_qnam = std::make_shared<QNetworkAccessManager>();
d = new Private();
}
void Env::destroy()
Env::~Env()
{
m_metacache.reset();
m_qnam.reset();
m_icons.reset();
m_versionLists.clear();
delete d;
}
Env& Env::Env::getInstance()
{
static Env instance;
return instance;
if(!instance)
{
instance = new Env();
}
return *instance;
}
std::shared_ptr< HttpMetaCache > Env::metacache()
void Env::dispose()
{
Q_ASSERT(m_metacache != nullptr);
return m_metacache;
delete instance;
instance = nullptr;
}
std::shared_ptr< QNetworkAccessManager > Env::qnam()
shared_qobject_ptr< HttpMetaCache > Env::metacache()
{
return m_qnam;
return d->m_metacache;
}
std::shared_ptr<IconList> Env::icons()
QNetworkAccessManager& Env::qnam() const
{
Q_ASSERT(m_icons != nullptr);
return m_icons;
return d->m_qnam;
}
/*
class NullVersion : public BaseVersion
{
Q_OBJECT
public:
virtual QString name()
{
return "null";
}
virtual QString descriptor()
{
return "null";
}
virtual QString typeString() const
{
return "Null";
}
};
class NullTask: public Task
std::shared_ptr<IIconList> Env::icons()
{
Q_OBJECT
public:
virtual void executeTask()
{
emitFailed(tr("Nothing to do."));
}
};
return d->m_iconlist;
}
class NullVersionList: public BaseVersionList
void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
{
Q_OBJECT
public:
virtual const BaseVersionPtr at(int i) const
{
return std::make_shared<NullVersion>();
}
virtual int count() const
{
return 0;
};
virtual Task* getLoadTask()
{
return new NullTask;
}
virtual bool isLoaded()
{
return false;
}
virtual void sort()
{
}
virtual void updateListData(QList< BaseVersionPtr >)
{
}
};
*/
d->m_iconlist = iconlist;
}
BaseVersionPtr Env::getVersion(QString component, QString version)
{
@@ -119,8 +85,8 @@ BaseVersionPtr Env::getVersion(QString component, QString version)
std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
{
auto iter = m_versionLists.find(component);
if(iter != m_versionLists.end())
auto iter = d->m_versionLists.find(component);
if(iter != d->m_versionLists.end())
{
return *iter;
}
@@ -130,12 +96,22 @@ std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
{
m_versionLists[name] = 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(QString rootPath, QString staticDataPath)
void Env::initHttpMetaCache()
{
auto &m_metacache = d->m_metacache;
m_metacache.reset(new HttpMetaCache("metacache"));
m_metacache->addBase("asset_indexes", QDir("assets/indexes").absolutePath());
m_metacache->addBase("asset_objects", QDir("assets/objects").absolutePath());
@@ -146,8 +122,10 @@ void Env::initHttpMetaCache(QString rootPath, QString staticDataPath)
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir(rootPath).absolutePath());
m_metacache->addBase("translations", QDir(staticDataPath + "/translations").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
m_metacache->addBase("icons", QDir("cache/icons").absolutePath());
m_metacache->addBase("wonko", QDir("cache/wonko").absolutePath());
m_metacache->Load();
}
@@ -177,8 +155,7 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
qDebug() << "Detecting proxy settings...";
QNetworkProxy proxy = QNetworkProxy::applicationProxy();
if (m_qnam.get())
m_qnam->setProxy(proxy);
d->m_qnam.setProxy(proxy);
QString proxyDesc;
if (proxy.type() == QNetworkProxy::NoProxy)
{
@@ -214,4 +191,14 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
qDebug() << proxyDesc;
}
QString Env::wonkoRootUrl() const
{
return d->m_wonkoRootUrl;
}
void Env::setWonkoRootUrl(const QString& url)
{
d->m_wonkoRootUrl = url;
}
#include "Env.moc"

View File

@@ -1,39 +1,44 @@
#pragma once
#include <memory>
#include "icons/IIconList.h"
#include <QString>
#include <QMap>
class IconList;
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
class QNetworkAccessManager;
class HttpMetaCache;
class BaseVersionList;
class BaseVersion;
class WonkoIndex;
#if defined(ENV)
#undef ENV
#endif
#define ENV (Env::getInstance())
class Env
class MULTIMC_LOGIC_EXPORT Env
{
friend class MultiMC;
private:
class Private;
Env();
~Env();
static void dispose();
public:
static Env& getInstance();
// call when Qt stuff is being torn down
void destroy();
QNetworkAccessManager &qnam() const;
std::shared_ptr<QNetworkAccessManager> qnam();
shared_qobject_ptr<HttpMetaCache> metacache();
std::shared_ptr<HttpMetaCache> metacache();
std::shared_ptr<IconList> icons();
std::shared_ptr<IIconList> icons();
/// init the cache. FIXME: possible future hook point
void initHttpMetaCache(QString rootPath, QString staticDataPath);
void initHttpMetaCache();
/// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
@@ -45,9 +50,14 @@ public:
std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
void registerIconList(std::shared_ptr<IIconList> iconlist);
shared_qobject_ptr<WonkoIndex> wonkoIndex();
QString wonkoRootUrl() const;
void setWonkoRootUrl(const QString &url);
protected:
std::shared_ptr<QNetworkAccessManager> m_qnam;
std::shared_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IconList> m_icons;
QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
Private * d;
};

34
api/logic/Exception.h Normal file
View File

@@ -0,0 +1,34 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QString>
#include <QDebug>
#include <exception>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT Exception : public std::exception
{
public:
Exception(const QString &message) : std::exception(), m_message(message)
{
qCritical() << "Exception:" << message;
}
Exception(const Exception &other)
: std::exception(), m_message(other.cause())
{
}
virtual ~Exception() noexcept {}
const char *what() const noexcept
{
return m_message.toLatin1().constData();
}
QString cause() const
{
return m_message;
}
private:
QString m_message;
};

456
api/logic/FileSystem.cpp Normal file
View File

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

127
api/logic/FileSystem.h Normal file
View File

@@ -0,0 +1,127 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
#include <QDir>
#include <QFlags>
namespace FS
{
class MULTIMC_LOGIC_EXPORT FileSystemException : public ::Exception
{
public:
FileSystemException(const QString &message) : Exception(message) {}
};
/**
* write data to a file safely
*/
MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data);
/**
* read data from a file safely\
*/
MULTIMC_LOGIC_EXPORT QByteArray read(const QString &filename);
/**
* Update the last changed timestamp of an existing file
*/
MULTIMC_LOGIC_EXPORT bool updateTimestamp(const QString & filename);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a file name and is ignored!
*/
MULTIMC_LOGIC_EXPORT bool ensureFilePathExists(QString filenamepath);
/**
* Creates all the folders in a path for the specified path
* last segment of the path is treated as a folder name and is created!
*/
MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
class MULTIMC_LOGIC_EXPORT copy
{
public:
copy(const QString & src, const QString & dst)
{
m_src = src;
m_dst = dst;
}
copy & followSymlinks(const bool follow)
{
m_followSymlinks = follow;
return *this;
}
copy & blacklist(const IPathMatcher * filter)
{
m_blacklist = filter;
return *this;
}
bool operator()()
{
return operator()(QString());
}
private:
bool operator()(const QString &offset);
private:
bool m_followSymlinks = true;
const IPathMatcher * m_blacklist = nullptr;
QDir m_src;
QDir m_dst;
};
/**
* Delete a folder recursively
*/
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
/**
* Resolve an executable
*
* Will resolve:
* single executable (by name)
* relative path
* absolute path
*
* @return absolute path to executable or null string
*/
MULTIMC_LOGIC_EXPORT QString ResolveExecutable(QString path);
/**
* Normalize path
*
* Any paths inside the current directory will be normalized to relative paths (to current)
* Other paths will be made absolute
*
* Returns false if the path logic somehow filed (and normalizedPath in invalid)
*/
MULTIMC_LOGIC_EXPORT QString NormalizePath(QString path);
MULTIMC_LOGIC_EXPORT QString RemoveInvalidFilenameChars(QString string, QChar replaceWith = '-');
MULTIMC_LOGIC_EXPORT QString DirNameFromString(QString string, QString inDir = ".");
/// Checks if the a given Path contains "!"
MULTIMC_LOGIC_EXPORT bool checkProblemticPathJava(QDir folder);
// Get the Directory representing the User's Desktop
MULTIMC_LOGIC_EXPORT QString getDesktopDir();
// Create a shortcut at *location*, pointing to *dest* called with the arguments *args*
// call it *name* and assign it the icon *icon*
// return true if operation succeeded
MULTIMC_LOGIC_EXPORT bool createShortCut(QString location, QString dest, QStringList args, QString name, QString iconLocation);
}

View File

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

View File

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

View File

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

115
api/logic/GZip.cpp Normal file
View File

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

12
api/logic/GZip.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <QByteArray>
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT GZip
{
public:
static bool unzip(const QByteArray &compressedBytes, QByteArray &uncompressedBytes);
static bool zip(const QByteArray &uncompressedBytes, QByteArray &compressedBytes);
};

57
api/logic/GZip_test.cpp Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,164 @@
#include "InstanceImportTask.h"
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "FileSystem.h"
#include "Env.h"
#include "MMCZip.h"
#include "NullInstance.h"
#include "settings/INISettingsObject.h"
#include "icons/IIconList.h"
#include <QtConcurrentRun>
InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
const QString &instName, const QString &instIcon, const QString &instGroup)
{
m_globalSettings = settings;
m_sourceUrl = sourceUrl;
m_target = target;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
}
void InstanceImportTask::executeTask()
{
InstancePtr newInstance;
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
extractAndTweak();
}
else
{
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = ENV.metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download")));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath();
auto job = m_filesNetJob.get();
connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed);
m_filesNetJob->start();
}
}
void InstanceImportTask::downloadSucceeded()
{
extractAndTweak();
}
void InstanceImportTask::downloadFailed(QString reason)
{
emitFailed(reason);
}
void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
{
setProgress(current / 2, total);
}
static QFileInfo findRecursive(const QString &dir, const QString &name)
{
for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
{
if (info.isFile() && info.fileName() == name)
{
return info;
}
else if (info.isDir())
{
const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
if (res.isFile() && res.exists())
{
return res;
}
}
}
return QFileInfo();
}
void InstanceImportTask::extractAndTweak()
{
setStatus(tr("Extracting modpack"));
m_stagingPath = m_target->getStagedInstancePath();
QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture);
}
void InstanceImportTask::extractFinished()
{
if (m_extractFuture.result().isEmpty())
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Failed to extract modpack"));
return;
}
QDir extractDir(m_stagingPath);
const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
if (!instanceCfgFile.isFile() || !instanceCfgFile.exists())
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Archive does not contain instance.cfg"));
return;
}
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
auto instanceSettings = std::make_shared<INISettingsObject>(instanceCfgFile.absoluteFilePath());
instanceSettings->registerSetting("InstanceType", "Legacy");
QString actualDir = instanceCfgFile.absolutePath();
NullInstance instance(m_globalSettings, instanceSettings, actualDir);
// reset time played on import... because packs.
instance.resetTimePlayed();
// set a new nice name
instance.setName(m_instName);
// if the icon was specified by user, use that. otherwise pull icon from the pack
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
}
else
{
m_instIcon = instance.iconKey();
auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
if (QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
if (iconList->iconFileExists(m_instIcon))
{
iconList->deleteIcon(m_instIcon);
}
iconList->installIcons({importIconPath});
}
}
if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup))
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Unable to commit instance"));
return;
}
emitSucceeded();
}
void InstanceImportTask::extractAborted()
{
m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance import has been aborted."));
return;
}

View File

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

342
api/logic/InstanceList.cpp Normal file
View File

@@ -0,0 +1,342 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <QDir>
#include <QSet>
#include <QFile>
#include <QThread>
#include <QTextStream>
#include <QXmlStreamReader>
#include <QDebug>
#include "InstanceList.h"
#include "BaseInstance.h"
#include "FolderInstanceProvider.h"
InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
: QAbstractListModel(parent), m_instDir(instDir)
{
m_globalSettings = globalSettings;
resumeWatch();
}
InstanceList::~InstanceList()
{
}
int InstanceList::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_instances.count();
}
QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(parent);
if (row < 0 || row >= m_instances.size())
return QModelIndex();
return createIndex(row, column, (void *)m_instances.at(row).get());
}
QVariant InstanceList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
switch (role)
{
case InstancePointerRole:
{
QVariant v = qVariantFromValue((void *)pdata);
return v;
}
case InstanceIDRole:
{
return pdata->id();
}
case Qt::DisplayRole:
{
return pdata->name();
}
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
}
case Qt::DecorationRole:
{
return pdata->iconKey();
}
// HACK: see GroupView.h in gui!
case GroupRole:
{
return pdata->group();
}
default:
break;
}
return QVariant();
}
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{
Qt::ItemFlags f;
if (index.isValid())
{
f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
}
return f;
}
QStringList InstanceList::getGroups()
{
return m_groups.toList();
}
void InstanceList::deleteGroup(const QString& name)
{
for(auto & instance: m_instances)
{
auto instGroupName = instance->group();
if(instGroupName == name)
{
instance->setGroupPost(QString());
}
}
}
static QMap<InstanceId, InstanceLocator> getIdMapping(const QList<InstancePtr> &list)
{
QMap<InstanceId, InstanceLocator> out;
int i = 0;
for(auto & item: list)
{
auto id = item->id();
if(out.contains(id))
{
qWarning() << "Duplicate ID" << id << "in instance list";
}
out[id] = std::make_pair(item, i);
i++;
}
return out;
}
InstanceList::InstListError InstanceList::loadList(bool complete)
{
auto existingIds = getIdMapping(m_instances);
QList<InstancePtr> newList;
auto processIds = [&](BaseInstanceProvider * provider, QList<InstanceId> ids)
{
for(auto & id: ids)
{
if(existingIds.contains(id))
{
auto instPair = existingIds[id];
/*
auto & instPtr = instPair.first;
auto & instIdx = instPair.second;
*/
existingIds.remove(id);
qDebug() << "Should keep and soft-reload" << id;
}
else
{
InstancePtr instPtr = provider->loadInstance(id);
if(instPtr)
{
newList.append(instPtr);
}
}
}
};
if(complete)
{
for(auto & item: m_providers)
{
processIds(item.get(), item->discoverInstances());
}
}
else
{
for (auto & item: m_updatedProviders)
{
processIds(item, item->discoverInstances());
}
}
// TODO: looks like a general algorithm with a few specifics inserted. Do something about it.
if(!existingIds.isEmpty())
{
// get the list of removed instances and sort it by their original index, from last to first
auto deadList = existingIds.values();
auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool
{
return a.second > b.second;
};
std::sort(deadList.begin(), deadList.end(), orderSortPredicate);
// remove the contiguous ranges of rows
int front_bookmark = -1;
int back_bookmark = -1;
int currentItem = -1;
auto removeNow = [&]()
{
beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark);
m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1);
endRemoveRows();
front_bookmark = -1;
back_bookmark = currentItem;
};
for(auto & removedItem: deadList)
{
auto instPtr = removedItem.first;
if(!complete && !m_updatedProviders.contains(instPtr->provider()))
{
continue;
}
instPtr->invalidate();
currentItem = removedItem.second;
if(back_bookmark == -1)
{
// no bookmark yet
back_bookmark = currentItem;
}
else if(currentItem == front_bookmark - 1)
{
// part of contiguous sequence, continue
}
else
{
// seam between previous and current item
removeNow();
}
front_bookmark = currentItem;
}
if(back_bookmark != -1)
{
removeNow();
}
}
if(newList.size())
{
add(newList);
}
m_updatedProviders.clear();
return NoError;
}
void InstanceList::add(const QList<InstancePtr> &t)
{
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
m_instances.append(t);
for(auto & ptr : t)
{
connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged);
}
endInsertRows();
}
void InstanceList::resumeWatch()
{
if(m_watchLevel > 0)
{
qWarning() << "Bad suspend level resume in instance list";
return;
}
m_watchLevel++;
if(m_watchLevel > 0 && !m_updatedProviders.isEmpty())
{
loadList();
}
}
void InstanceList::suspendWatch()
{
m_watchLevel --;
}
void InstanceList::providerUpdated()
{
auto provider = dynamic_cast<BaseInstanceProvider *>(QObject::sender());
if(!provider)
{
qWarning() << "InstanceList::providerUpdated triggered by a non-provider";
return;
}
m_updatedProviders.insert(provider);
if(m_watchLevel == 1)
{
loadList();
}
}
void InstanceList::groupsPublished(QSet<QString> newGroups)
{
m_groups.unite(newGroups);
}
void InstanceList::addInstanceProvider(BaseInstanceProvider* provider)
{
connect(provider, &BaseInstanceProvider::instancesChanged, this, &InstanceList::providerUpdated);
connect(provider, &BaseInstanceProvider::groupsChanged, this, &InstanceList::groupsPublished);
m_providers.append(provider);
}
InstancePtr InstanceList::getInstanceById(QString instId) const
{
if(instId.isEmpty())
return InstancePtr();
for(auto & inst: m_instances)
{
if (inst->id() == instId)
{
return inst;
}
}
return InstancePtr();
}
QModelIndex InstanceList::getInstanceIndexById(const QString &id) const
{
return index(getInstIndex(getInstanceById(id).get()));
}
int InstanceList::getInstIndex(BaseInstance *inst) const
{
int count = m_instances.count();
for (int i = 0; i < count; i++)
{
if (inst == m_instances[i].get())
{
return i;
}
}
return -1;
}
void InstanceList::propertiesChanged(BaseInstance *inst)
{
int i = getInstIndex(inst);
if (i != -1)
{
emit dataChanged(index(i), index(i));
}
}
#include "InstanceList.moc"

108
api/logic/InstanceList.h Normal file
View File

@@ -0,0 +1,108 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QObject>
#include <QAbstractListModel>
#include <QSet>
#include <QList>
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
#include "multimc_logic_export.h"
#include "QObjectPtr.h"
class QFileSystemWatcher;
class BaseInstance;
class QDir;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{
Q_OBJECT
public:
explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
virtual ~InstanceList();
public:
QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
enum AdditionalRoles
{
GroupRole = Qt::UserRole,
InstancePointerRole = 0x34B1CB48, ///< Return pointer to real instance
InstanceIDRole = 0x34B1CB49 ///< Return id if the instance
};
/*!
* \brief Error codes returned by functions in the InstanceList class.
* NoError Indicates that no error occurred.
* UnknownError indicates that an unspecified error occurred.
*/
enum InstListError
{
NoError = 0,
UnknownError
};
InstancePtr at(int i) const
{
return m_instances.at(i);
}
int count() const
{
return m_instances.count();
}
InstListError loadList(bool complete = false);
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
void addInstanceProvider(BaseInstanceProvider * provider);
InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
void deleteGroup(const QString & name);
signals:
void dataIsInvalid();
private slots:
void propertiesChanged(BaseInstance *inst);
void groupsPublished(QSet<QString>);
void providerUpdated();
private:
int getInstIndex(BaseInstance *inst) const;
void suspendWatch();
void resumeWatch();
void add(const QList<InstancePtr> &list);
protected:
int m_watchLevel = 0;
QSet<BaseInstanceProvider *> m_updatedProviders;
QString m_instDir;
QList<InstancePtr> m_instances;
QSet<QString> m_groups;
SettingsObjectPtr m_globalSettings;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
};

272
api/logic/Json.cpp Normal file
View File

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

249
api/logic/Json.h Normal file
View File

@@ -0,0 +1,249 @@
// Licensed under the Apache-2.0 license. See README.md for details.
#pragma once
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDateTime>
#include <QUrl>
#include <QDir>
#include <QUuid>
#include <QVariant>
#include <memory>
#include "Exception.h"
namespace Json
{
class MULTIMC_LOGIC_EXPORT JsonException : public ::Exception
{
public:
JsonException(const QString &message) : Exception(message) {}
};
/// @throw FileSystemException
MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename);
MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonObject &obj);
MULTIMC_LOGIC_EXPORT QByteArray toBinary(const QJsonArray &array);
MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonObject &obj);
MULTIMC_LOGIC_EXPORT QByteArray toText(const QJsonArray &array);
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QByteArray &data, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonDocument requireDocument(const QString &filename, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonObject requireObject(const QJsonDocument &doc, const QString &what = "Document");
/// @throw JsonException
MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QString &what = "Document");
/////////////////// WRITING ////////////////////
void writeString(QJsonObject & to, const QString &key, const QString &value);
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
template<typename T>
QJsonValue toJson(const T &t)
{
return QJsonValue(t);
}
template<>
QJsonValue toJson<QUrl>(const QUrl &url);
template<>
QJsonValue toJson<QByteArray>(const QByteArray &data);
template<>
QJsonValue toJson<QDateTime>(const QDateTime &datetime);
template<>
QJsonValue toJson<QDir>(const QDir &dir);
template<>
QJsonValue toJson<QUuid>(const QUuid &uuid);
template<>
QJsonValue toJson<QVariant>(const QVariant &variant);
template<typename T>
QJsonArray toJsonArray(const QList<T> &container)
{
QJsonArray array;
for (const T item : container)
{
array.append(toJson<T>(item));
}
return array;
}
////////////////// READING ////////////////////
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonValue &value, const QString &what = "Value");
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT double requireIsType<double>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT bool requireIsType<bool>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT int requireIsType<int>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonObject requireIsType<QJsonObject>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonArray requireIsType<QJsonArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QJsonValue requireIsType<QJsonValue>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QByteArray requireIsType<QByteArray>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QDateTime requireIsType<QDateTime>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QVariant requireIsType<QVariant>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QString requireIsType<QString>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QUuid requireIsType<QUuid>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QDir requireIsType<QDir>(const QJsonValue &value, const QString &what);
/// @throw JsonException
template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value, const QString &what);
// the following functions are higher level functions, that make use of the above functions for
// type conversion
template <typename T>
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{
if (value.isUndefined() || value.isNull())
{
return default_;
}
try
{
return requireIsType<T>(value, what);
}
catch (JsonException &)
{
return default_;
}
}
/// @throw JsonException
template <typename T>
T requireIsType(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return requireIsType<T>(parent.value(key), localWhat);
}
template <typename T>
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
return default_;
}
return ensureIsType<T>(parent.value(key), default_, localWhat);
}
template <typename T>
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = requireArray(doc);
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, "Document"));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, what));
}
return out;
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{
if (value.isUndefined())
{
return default_;
}
return ensureIsArrayOf<T>(value, what);
}
/// @throw JsonException
template <typename T>
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return ensureIsArrayOf<T>(parent.value(key), localWhat);
}
template <typename T>
QVector<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QVector<T> &default_ = QVector<T>(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
return default_;
}
return ensureIsArrayOf<T>(parent.value(key), default_, localWhat);
}
// this macro part could be replaced by variadic functions that just pass on their arguments, but that wouldn't work well with IDE helpers
#define JSON_HELPERFUNCTIONS(NAME, TYPE) \
inline TYPE require##NAME(const QJsonValue &value, const QString &what = "Value") \
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
inline TYPE require##NAME(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__") \
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
}
JSON_HELPERFUNCTIONS(Array, QJsonArray)
JSON_HELPERFUNCTIONS(Object, QJsonObject)
JSON_HELPERFUNCTIONS(JsonValue, QJsonValue)
JSON_HELPERFUNCTIONS(String, QString)
JSON_HELPERFUNCTIONS(Boolean, bool)
JSON_HELPERFUNCTIONS(Double, double)
JSON_HELPERFUNCTIONS(Integer, int)
JSON_HELPERFUNCTIONS(DateTime, QDateTime)
JSON_HELPERFUNCTIONS(Url, QUrl)
JSON_HELPERFUNCTIONS(ByteArray, QByteArray)
JSON_HELPERFUNCTIONS(Dir, QDir)
JSON_HELPERFUNCTIONS(Uuid, QUuid)
JSON_HELPERFUNCTIONS(Variant, QVariant)
#undef JSON_HELPERFUNCTIONS
}
using JSONValidationError = Json::JsonException;

176
api/logic/LoggedProcess.cpp Normal file
View File

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

80
api/logic/LoggedProcess.h Normal file
View File

@@ -0,0 +1,80 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include "MessageLevel.h"
#include "multimc_logic_export.h"
/*
* This is a basic process.
* It has line-based logging support and hides some of the nasty bits.
*/
class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
{
Q_OBJECT
public:
enum State
{
NotRunning,
Starting,
FailedToStart,
Running,
Finished,
Crashed,
Aborted
};
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess();
State state() const;
int exitCode() const;
qint64 processId() const;
void setDetachable(bool detachable);
signals:
void log(QStringList lines, MessageLevel::Enum level);
void stateChanged(LoggedProcess::State state);
public slots:
/**
* @brief kill the process - equivalent to kill -9
*/
void kill();
private slots:
void on_stdErr();
void on_stdOut();
void on_exit(int exit_code, QProcess::ExitStatus status);
void on_error(QProcess::ProcessError error);
void on_stateChange(QProcess::ProcessState);
private:
void changeState(LoggedProcess::State state);
private:
QString m_err_leftover;
QString m_out_leftover;
bool m_killed = false;
State m_state = NotRunning;
int m_exit_code = 0;
bool m_is_aborting = false;
bool m_is_detachable = false;
};

10
api/logic/MMCStrings.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <QString>
#include "multimc_logic_export.h"
namespace Strings
{
int MULTIMC_LOGIC_EXPORT naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
}

View File

@@ -23,10 +23,11 @@ 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 <pathutils.h>
#include <quazip.h>
#include <JlCompress.h>
#include <quazipdir.h>
#include "MMCZip.h"
#include "FileSystem.h"
#include <QDebug>
@@ -110,7 +111,7 @@ bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QStr
if(!blacklist || !blacklist->covers(internalDirName))
{
QuaZipFile dirZipFile(zip);
auto dirPrefix = PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
auto dirPrefix = FS::PathCombine(prefix, origDirectory.relativeFilePath(dir)) + "/";
if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(dirPrefix, dir), 0, 0, 0))
{
return false;
@@ -152,7 +153,7 @@ bool MMCZip::compressSubDir(QuaZip* zip, QString dir, QString origDir, QSet<QStr
}
if(prefix.size())
{
filename = PathCombine(prefix, filename);
filename = FS::PathCombine(prefix, filename);
}
added.insert(filename);
if (!compressFile(zip,file.absoluteFilePath(),filename))
@@ -338,3 +339,153 @@ bool MMCZip::compressDir(QString zipFile, QString dir, QString prefix, const Sep
return true;
}
QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
return root;
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
QString result = findFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
}
}
return QString();
}
bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
{
if(fileName == what)
{
result.append(root);
return true;
}
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
findFilesInZip(zip, what, result, root + fileName);
}
return !result.isEmpty();
}
bool removeFile(QStringList listFile)
{
bool ret = true;
for (int i = 0; i < listFile.count(); i++)
{
ret &= QFile::remove(listFile.at(i));
}
return ret;
}
bool MMCZip::extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest)
{
if(!zip)
return false;
if (zip->getMode() != QuaZip::mdUnzip)
return false;
if (!fileName.isEmpty())
zip->setCurrentFile(fileName);
QuaZipFile inFile(zip);
if (!inFile.open(QIODevice::ReadOnly) || inFile.getZipError() != UNZ_OK)
return false;
// Controllo esistenza cartella file risultato
QDir curDir;
if (fileDest.endsWith('/'))
{
if (!curDir.mkpath(fileDest))
{
return false;
}
}
else
{
if (!curDir.mkpath(QFileInfo(fileDest).absolutePath()))
{
return false;
}
}
QuaZipFileInfo64 info;
if (!zip->getCurrentFileInfo(&info))
return false;
QFile::Permissions srcPerm = info.getPermissions();
if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir())
{
if (srcPerm != 0)
{
QFile(fileDest).setPermissions(srcPerm);
}
return true;
}
QFile outFile;
outFile.setFileName(fileDest);
if (!outFile.open(QIODevice::WriteOnly))
return false;
if (!copyData(inFile, outFile) || inFile.getZipError() != UNZ_OK)
{
outFile.close();
removeFile(QStringList(fileDest));
return false;
}
outFile.close();
inFile.close();
if (inFile.getZipError() != UNZ_OK)
{
removeFile(QStringList(fileDest));
return false;
}
if (srcPerm != 0)
{
outFile.setPermissions(srcPerm);
}
return true;
}
QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{
QDir directory(target);
QStringList extracted;
if (!zip->goToFirstFile())
{
return QStringList();
}
do
{
QString name = zip->getCurrentFileName();
if(!name.startsWith(subdir))
{
continue;
}
name.remove(0, subdir.size());
QString absFilePath = directory.absoluteFilePath(name);
if(name.isEmpty())
{
absFilePath += "/";
}
if (!extractFile(zip, "", absFilePath))
{
removeFile(extracted);
return QStringList();
}
extracted.append(absFilePath);
} while (zip->goToNextFile());
return extracted;
}

88
api/logic/MMCZip.h Normal file
View File

@@ -0,0 +1,88 @@
#pragma once
#include <QString>
#include <QFileInfo>
#include <QSet>
#include "minecraft/Mod.h"
#include "SeparatorPrefixTree.h"
#include <functional>
#include "multimc_logic_export.h"
class QuaZip;
namespace MMCZip
{
/**
* Compress a subdirectory.
* \param parentZip Opened zip containing the parent directory.
* \param dir The full path to the directory to pack.
* \param parentDir The full path to the directory corresponding to the root of the ZIP.
* \param recursive Whether to pack sub-directories as well or only files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressSubDir(QuaZip *zip, QString dir, QString origDir, QSet<QString> &added,
QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/**
* Compress a whole directory.
* \param fileCompressed The name of the archive.
* \param dir The directory to compress.
* \param recursive Whether to pack the subdirectories as well, or just regular files.
* \return true if success, false otherwise.
*/
bool MULTIMC_LOGIC_EXPORT compressDir(QString zipFile, QString dir, QString prefix = QString(), const SeparatorPrefixTree <'/'> * blacklist = nullptr);
/// filter function for @mergeZipFiles - passthrough
bool MULTIMC_LOGIC_EXPORT noFilter(QString key);
/// filter function for @mergeZipFiles - ignores METAINF
bool MULTIMC_LOGIC_EXPORT metaInfFilter(QString key);
/**
* Merge two zip files, using a filter function
*/
bool MULTIMC_LOGIC_EXPORT mergeZipFiles(QuaZip *into, QFileInfo from, QSet<QString> &contained, std::function<bool(QString)> filter);
/**
* take a source jar, add mods to it, resulting in target jar
*/
bool MULTIMC_LOGIC_EXPORT createModdedJar(QString sourceJarPath, QString targetJarPath, const QList<Mod>& mods);
/**
* Extract a whole archive.
*
* \param fileCompressed The name of the archive.
* \param dir The directory to extract to, the current directory if
* left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir = QString());
/**
* Find a single file in archive by file name (not path)
*
* \return the path prefix where the file is
*/
QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
/**
* Find a multiple files of the same name in archive by file name
* If a file is found in a path, no deeper paths are searched
*
* \return true if anything was found
*/
bool MULTIMC_LOGIC_EXPORT findFilesInZip(QuaZip * zip, const QString & what, QStringList & result, const QString &root = QString());
/**
* Extract a single file to a destination
*
* \return true if it succeeds
*/
bool MULTIMC_LOGIC_EXPORT extractFile(QuaZip *zip, const QString &fileName, const QString &fileDest);
/**
* Extract a subdirectory from an archive
*/
QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
}

View File

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

28
api/logic/MessageLevel.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once
#include <QString>
/**
* @brief the MessageLevel Enum
* defines what level a log message is
*/
namespace MessageLevel
{
enum Enum
{
Unknown, /**< No idea what this is or where it came from */
StdOut, /**< Undetermined stderr messages */
StdErr, /**< Undetermined stdout messages */
MultiMC, /**< MultiMC Messages */
Debug, /**< Debug Messages */
Info, /**< Info Messages */
Message, /**< Standard Messages */
Warning, /**< Warnings */
Error, /**< Errors */
Fatal, /**< Fatal Errors */
};
MessageLevel::Enum getLevel(const QString &levelName);
/* Get message level from a line. Line is modified if it was successful. */
MessageLevel::Enum fromLine(QString &line);
}

93
api/logic/NullInstance.h Normal file
View File

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

82
api/logic/QObjectPtr.h Normal file
View File

@@ -0,0 +1,82 @@
#pragma once
#include <memory>
#include <QObject>
namespace details
{
struct DeleteQObjectLater
{
void operator()(QObject *obj) const
{
obj->deleteLater();
}
};
}
/**
* A unique pointer class with unique pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template<typename T> using unique_qobject_ptr = std::unique_ptr<T, details::DeleteQObjectLater>;
/**
* A shared pointer class with shared pointer semantics intended for derivates of QObject
* Calls deleteLater() instead of destroying the contained object immediately
*/
template <typename T>
class shared_qobject_ptr
{
public:
shared_qobject_ptr(){}
shared_qobject_ptr(T * wrap)
{
reset(wrap);
}
shared_qobject_ptr(const shared_qobject_ptr<T>& other)
{
m_ptr = other.m_ptr;
}
template<typename Derived>
shared_qobject_ptr(const shared_qobject_ptr<Derived> &other)
{
m_ptr = other.unwrap();
}
public:
void reset(T * wrap)
{
using namespace std::placeholders;
m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1));
}
void reset(const shared_qobject_ptr<T> &other)
{
m_ptr = other.m_ptr;
}
void reset()
{
m_ptr.reset();
}
T * get() const
{
return m_ptr.get();
}
T * operator->() const
{
return m_ptr.get();
}
T & operator*() const
{
return *m_ptr.get();
}
operator bool() const
{
return m_ptr.get() != nullptr;
}
const std::shared_ptr <T> unwrap() const
{
return m_ptr;
}
private:
std::shared_ptr <T> m_ptr;
};

View File

@@ -4,7 +4,7 @@
#include <QDebug>
RecursiveFileSystemWatcher::RecursiveFileSystemWatcher(QObject *parent)
: QObject(parent), m_exp(".*"), m_watcher(new QFileSystemWatcher(this))
: QObject(parent), m_watcher(new QFileSystemWatcher(this))
{
connect(m_watcher, &QFileSystemWatcher::fileChanged, this,
&RecursiveFileSystemWatcher::fileChange);
@@ -82,16 +82,20 @@ void RecursiveFileSystemWatcher::addFilesToWatcherRecursive(const QDir &dir)
QStringList RecursiveFileSystemWatcher::scanRecursive(const QDir &directory)
{
QStringList ret;
QRegularExpression exp(m_exp);
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot))
if(!m_matcher)
{
return {};
}
for (const QString &dir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden))
{
ret.append(scanRecursive(directory.absoluteFilePath(dir)));
}
for (const QString &file : directory.entryList(QDir::Files))
for (const QString &file : directory.entryList(QDir::Files | QDir::Hidden))
{
if (exp.match(file).hasMatch())
auto relPath = m_root.relativeFilePath(directory.absoluteFilePath(file));
if (m_matcher->matches(relPath))
{
ret.append(m_root.relativeFilePath(directory.absoluteFilePath(file)));
ret.append(relPath);
}
}
return ret;

View File

@@ -2,24 +2,38 @@
#include <QFileSystemWatcher>
#include <QDir>
#include "pathmatcher/IPathMatcher.h"
class RecursiveFileSystemWatcher : public QObject
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT RecursiveFileSystemWatcher : public QObject
{
Q_OBJECT
public:
RecursiveFileSystemWatcher(QObject *parent);
void setRootDir(const QDir &root);
QDir rootDir() const { return m_root; }
QDir rootDir() const
{
return m_root;
}
// WARNING: setting this to true may be bad for performance
void setWatchFiles(const bool watchFiles);
bool watchFiles() const { return m_watchFiles; }
bool watchFiles() const
{
return m_watchFiles;
}
void setFileExpression(const QString &exp) { m_exp = exp; }
QString fileExpression() const { return m_exp; }
void setMatcher(IPathMatcher::Ptr matcher)
{
m_matcher = matcher;
}
QStringList files() const { return m_files; }
QStringList files() const
{
return m_files;
}
signals:
void filesChanged();
@@ -33,7 +47,7 @@ private:
QDir m_root;
bool m_watchFiles = false;
bool m_isEnabled = false;
QString m_exp;
IPathMatcher::Ptr m_matcher;
QFileSystemWatcher *m_watcher;

58
api/logic/Usable.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <cstddef>
#include <memory>
class Usable;
/**
* Base class for things that can be used by multiple other things and we want to track the use count.
*
* @see UseLock
*/
class Usable
{
friend class UseLock;
public:
std::size_t useCount()
{
return m_useCount;
}
bool isInUse()
{
return m_useCount > 0;
}
protected:
virtual void decrementUses()
{
m_useCount--;
}
virtual void incrementUses()
{
m_useCount++;
}
private:
std::size_t m_useCount = 0;
};
/**
* Lock class to use for keeping track of uses of other things derived from Usable
*
* @see Usable
*/
class UseLock
{
public:
UseLock(std::shared_ptr<Usable> usable)
: m_usable(usable)
{
// this doesn't use shared pointer use count, because that wouldn't be correct. this count is separate.
m_usable->incrementUses();
}
~UseLock()
{
m_usable->decrementUses();
}
private:
std::shared_ptr<Usable> m_usable;
};

View File

@@ -1,16 +1,16 @@
#include "include/modutils.h"
#include "Version.h"
#include <QStringList>
#include <QUrl>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
Util::Version::Version(const QString &str) : m_string(str)
Version::Version(const QString &str) : m_string(str)
{
parse();
}
bool Util::Version::operator<(const Version &other) const
bool Version::operator<(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
@@ -26,11 +26,11 @@ bool Util::Version::operator<(const Version &other) const
return false;
}
bool Util::Version::operator<=(const Util::Version &other) const
bool Version::operator<=(const Version &other) const
{
return *this < other || *this == other;
}
bool Util::Version::operator>(const Version &other) const
bool Version::operator>(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
@@ -46,11 +46,11 @@ bool Util::Version::operator>(const Version &other) const
return false;
}
bool Util::Version::operator>=(const Version &other) const
bool Version::operator>=(const Version &other) const
{
return *this > other || *this == other;
}
bool Util::Version::operator==(const Version &other) const
bool Version::operator==(const Version &other) const
{
const int size = qMax(m_sections.size(), other.m_sections.size());
for (int i = 0; i < size; ++i)
@@ -66,12 +66,12 @@ bool Util::Version::operator==(const Version &other) const
return true;
}
bool Util::Version::operator!=(const Version &other) const
bool Version::operator!=(const Version &other) const
{
return !operator==(other);
}
void Util::Version::parse()
void Version::parse()
{
m_sections.clear();
@@ -83,11 +83,11 @@ void Util::Version::parse()
}
}
bool Util::versionIsInInterval(const QString &version, const QString &interval)
bool versionIsInInterval(const QString &version, const QString &interval)
{
return versionIsInInterval(Util::Version(version), interval);
return versionIsInInterval(Version(version), interval);
}
bool Util::versionIsInInterval(const Version &version, const QString &interval)
bool versionIsInInterval(const Version &version, const QString &interval)
{
if (interval.isEmpty() || version.toString() == interval)
{
@@ -108,7 +108,7 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
// check if in range (bottom)
if (!bottom.isEmpty())
{
const auto bottomVersion = Util::Version(bottom);
const auto bottomVersion = Version(bottom);
if ((start == '[') && !(version >= bottomVersion))
{
return false;
@@ -122,7 +122,7 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
// check if in range (top)
if (!top.isEmpty())
{
const auto topVersion = Util::Version(top);
const auto topVersion = Version(top);
if ((end == ']') && !(version <= topVersion))
{
return false;
@@ -138,4 +138,3 @@ bool Util::versionIsInInterval(const Version &version, const QString &interval)
return false;
}

View File

@@ -3,13 +3,11 @@
#include <QString>
#include <QList>
#include "libutil_config.h"
#include "multimc_logic_export.h"
class QUrl;
namespace Util
{
struct Version
struct MULTIMC_LOGIC_EXPORT Version
{
Version(const QString &str);
Version() {}
@@ -107,7 +105,6 @@ private:
void parse();
};
LIBUTIL_EXPORT bool versionIsInInterval(const QString &version, const QString &interval);
LIBUTIL_EXPORT bool versionIsInInterval(const Version &version, const QString &interval);
}
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-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@
#include <QTest>
#include "modutils.h"
#include "TestUtil.h"
#include <Version.h>
class ModUtilsTest : public QObject
{
@@ -95,59 +95,29 @@ private slots:
QFETCH(QString, interval);
QFETCH(bool, result);
QCOMPARE(Util::versionIsInInterval(version, interval), result);
QCOMPARE(versionIsInInterval(version, interval), result);
}
void test_versionCompareLessThan_data()
void test_versionCompare_data()
{
setupVersions();
}
void test_versionCompareLessThan()
void test_versionCompare()
{
QFETCH(QString, first);
QFETCH(QString, second);
QFETCH(bool, lessThan);
QFETCH(bool, equal);
const auto v1 = Util::Version(first);
const auto v2 = Util::Version(second);
const auto v1 = Version(first);
const auto v2 = Version(second);
QCOMPARE(v1 < v2, lessThan);
}
void test_versionCompareGreaterThan_data()
{
setupVersions();
}
void test_versionCompareGreaterThan()
{
QFETCH(QString, first);
QFETCH(QString, second);
QFETCH(bool, lessThan);
QFETCH(bool, equal);
const auto v1 = Util::Version(first);
const auto v2 = Util::Version(second);
QCOMPARE(v1 > v2, !lessThan && !equal);
}
void test_versionCompareEqual_data()
{
setupVersions();
}
void test_versionCompareEqual()
{
QFETCH(QString, first);
QFETCH(QString, second);
QFETCH(bool, lessThan);
QFETCH(bool, equal);
const auto v1 = Util::Version(first);
const auto v2 = Util::Version(second);
QCOMPARE(v1 == v2, equal);
}
};
QTEST_GUILESS_MAIN(ModUtilsTest)
#include "tst_modutils.moc"
#include "Version_test.moc"

View File

@@ -0,0 +1,7 @@
#include "IIconList.h"
// blargh
IIconList::~IIconList()
{
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <QString>
#include <QStringList>
#include "multimc_logic_export.h"
enum IconType : unsigned
{
Builtin,
Transient,
FileBased,
ICONS_TOTAL,
ToBeDeleted
};
class MULTIMC_LOGIC_EXPORT IIconList
{
public:
virtual ~IIconList();
virtual bool addIcon(const QString &key, const QString &name, const QString &path, const IconType type) = 0;
virtual bool deleteIcon(const QString &key) = 0;
virtual void saveIcon(const QString &key, const QString &path, const char * format) const = 0;
virtual bool iconFileExists(const QString &key) const = 0;
virtual void installIcons(const QStringList &iconFiles) = 0;
};

View File

@@ -1,10 +1,9 @@
#include "JavaChecker.h"
#include <pathutils.h>
#include <cmdutils.h>
#include <FileSystem.h>
#include <Commandline.h>
#include <QFile>
#include <QProcess>
#include <QMap>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QDebug>
@@ -14,14 +13,14 @@ JavaChecker::JavaChecker(QObject *parent) : QObject(parent)
void JavaChecker::performCheck()
{
QString checkerJar = PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
QString checkerJar = FS::PathCombine(QCoreApplication::applicationDirPath(), "jars", "JavaCheck.jar");
QStringList args;
process.reset(new QProcess());
if(m_args.size())
{
auto extraArgs = Util::Commandline::splitArgs(m_args);
auto extraArgs = Commandline::splitArgs(m_args);
args.append(extraArgs);
}
if(m_minMem != 0)
@@ -81,13 +80,14 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.id = m_id;
}
result.errorLog = m_stderr;
result.outLog = m_stdout;
qDebug() << "STDOUT" << m_stdout;
qWarning() << "STDERR" << m_stderr;
qDebug() << "Java checker finished with status " << status << " exit code " << exitcode;
if (status == QProcess::CrashExit || exitcode == 1)
{
qDebug() << "Java checker failed!";
result.validity = JavaCheckResult::Validity::Errored;
emit checkFinished(result);
return;
}
@@ -113,7 +113,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
if(!results.contains("os.arch") || !results.contains("java.version") || !success)
{
qDebug() << "Java checker failed - couldn't extract required information.";
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
emit checkFinished(result);
return;
}
@@ -123,7 +123,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
result.valid = true;
result.validity = JavaCheckResult::Validity::Valid;
result.is_64bit = is_64;
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;

View File

@@ -3,24 +3,33 @@
#include <QTimer>
#include <memory>
#include "multimc_logic_export.h"
#include "JavaVersion.h"
class JavaChecker;
struct JavaCheckResult
struct MULTIMC_LOGIC_EXPORT JavaCheckResult
{
QString path;
QString mojangPlatform;
QString realPlatform;
QString javaVersion;
JavaVersion javaVersion;
QString outLog;
QString errorLog;
bool valid = false;
bool is_64bit = false;
int id;
enum class Validity
{
Errored,
ReturnedInvalidData,
Valid
} validity = Validity::Errored;
};
typedef std::shared_ptr<QProcess> QProcessPtr;
typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
class JavaChecker : public QObject
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{
Q_OBJECT
public:

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
*/
#include "JavaCheckerJob.h"
#include "pathutils.h"
#include <QDebug>

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
#pragma once
#include <QtNetwork>
#include <QLabel>
#include "JavaChecker.h"
#include "tasks/Task.h"
@@ -36,7 +35,7 @@ public:
// if this is already running, the action needs to be started right away!
if (isRunning())
{
emit progress(current_progress, total_progress);
setProgress(current_progress, total_progress);
connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
base->performCheck();
@@ -59,7 +58,7 @@ public:
{
return javacheckers.size();
}
virtual bool isRunning() const
virtual bool isRunning() const override
{
return m_running;
}
@@ -68,9 +67,6 @@ signals:
void started();
void finished(QList<JavaCheckResult>);
public slots:
virtual void abort() {};
private slots:
void partFinished(JavaCheckResult result);

View File

@@ -0,0 +1,28 @@
#include "JavaInstall.h"
#include <MMCStrings.h>
bool JavaInstall::operator<(const JavaInstall &rhs)
{
auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if(archCompare != 0)
return archCompare < 0;
if(id < rhs.id)
{
return true;
}
if(id > rhs.id)
{
return false;
}
return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
}
bool JavaInstall::operator==(const JavaInstall &rhs)
{
return arch == rhs.arch && id == rhs.id && path == rhs.path;
}
bool JavaInstall::operator>(const JavaInstall &rhs)
{
return (!operator<(rhs)) && (!operator==(rhs));
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "BaseVersion.h"
#include "JavaVersion.h"
struct JavaInstall : public BaseVersion
{
JavaInstall(){}
JavaInstall(QString id, QString arch, QString path)
: id(id), arch(arch), path(path)
{
}
virtual QString descriptor()
{
return id.toString();
}
virtual QString name()
{
return id.toString();
}
virtual QString typeString() const
{
return arch;
}
bool operator<(const JavaInstall & rhs);
bool operator==(const JavaInstall & rhs);
bool operator>(const JavaInstall & rhs);
JavaVersion id;
QString arch;
QString path;
bool recommended = false;
};
typedef std::shared_ptr<JavaInstall> JavaInstallPtr;

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,37 +19,37 @@
#include <QDebug>
#include "java/JavaVersionList.h"
#include "java/JavaInstallList.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaUtils.h"
#include "MMCStrings.h"
#include "minecraft/VersionFilterData.h"
JavaVersionList::JavaVersionList(QObject *parent) : BaseVersionList(parent)
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
{
}
Task *JavaVersionList::getLoadTask()
Task *JavaInstallList::getLoadTask()
{
return new JavaListLoadTask(this);
}
const BaseVersionPtr JavaVersionList::at(int i) const
const BaseVersionPtr JavaInstallList::at(int i) const
{
return m_vlist.at(i);
}
bool JavaVersionList::isLoaded()
bool JavaInstallList::isLoaded()
{
return m_loaded;
}
int JavaVersionList::count() const
int JavaInstallList::count() const
{
return m_vlist.count();
}
QVariant JavaVersionList::data(const QModelIndex &index, int role) const
QVariant JavaInstallList::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -57,7 +57,7 @@ QVariant JavaVersionList::data(const QModelIndex &index, int role) const
if (index.row() > count())
return QVariant();
auto version = std::dynamic_pointer_cast<JavaVersion>(m_vlist[index.row()]);
auto version = std::dynamic_pointer_cast<JavaInstall>(m_vlist[index.row()]);
switch (role)
{
case VersionPointerRole:
@@ -65,7 +65,7 @@ QVariant JavaVersionList::data(const QModelIndex &index, int role) const
case VersionIdRole:
return version->descriptor();
case VersionRole:
return version->id;
return version->id.toString();
case RecommendedRole:
return version->recommended;
case PathRole:
@@ -77,37 +77,21 @@ QVariant JavaVersionList::data(const QModelIndex &index, int role) const
}
}
BaseVersionList::RoleList JavaVersionList::providesRoles()
BaseVersionList::RoleList JavaInstallList::providesRoles() const
{
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
}
void JavaVersionList::updateListData(QList<BaseVersionPtr> versions)
void JavaInstallList::updateListData(QList<BaseVersionPtr> versions)
{
beginResetModel();
m_vlist = versions;
m_loaded = true;
// manual testing fakery
/*
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_33", "64", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_44", "64", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_55", "64", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.7.0_44", "64", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.8.0_44", "64", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_33", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_44", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.6.0_55", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.7.0_44", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.8.0_44", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.9.0_1231", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.9.0_1", "32", "/foo/bar/baz"));
m_vlist.push_back(std::make_shared<JavaVersion>("1.9.0_1", "64", "/foo/bar/baz"));
*/
sort();
sortVersions();
if(m_vlist.size())
{
auto best = std::dynamic_pointer_cast<JavaVersion>(m_vlist[0]);
auto best = std::dynamic_pointer_cast<JavaInstall>(m_vlist[0]);
best->recommended = true;
}
endResetModel();
@@ -115,35 +99,19 @@ void JavaVersionList::updateListData(QList<BaseVersionPtr> versions)
bool sortJavas(BaseVersionPtr left, BaseVersionPtr right)
{
auto rleft = std::dynamic_pointer_cast<JavaVersion>(left);
auto rright = std::dynamic_pointer_cast<JavaVersion>(right);
// prefer higher arch
auto archCompare = Strings::naturalCompare(rleft->arch, rright->arch, Qt::CaseInsensitive);
if(archCompare != 0)
return archCompare > 0;
// dirty hack - 1.9 and above is too new
auto labove19 = Strings::naturalCompare(rleft->name(), g_VersionFilterData.discouragedJavaVersion, Qt::CaseInsensitive) >= 0;
auto rabove19 = Strings::naturalCompare(rright->name(), g_VersionFilterData.discouragedJavaVersion, Qt::CaseInsensitive) >= 0;
if(labove19 == rabove19)
{
// prefer higher versions in general
auto nameCompare = Strings::naturalCompare(rleft->name(), rright->name(), Qt::CaseInsensitive);
if(nameCompare != 0)
return nameCompare > 0;
// if all else is equal, sort by path
return Strings::naturalCompare(rleft->path, rright->path, Qt::CaseInsensitive) < 0;
}
return labove19 < rabove19;
auto rleft = std::dynamic_pointer_cast<JavaInstall>(left);
auto rright = std::dynamic_pointer_cast<JavaInstall>(right);
return (*rleft) > (*rright);
}
void JavaVersionList::sort()
void JavaInstallList::sortVersions()
{
beginResetModel();
std::sort(m_vlist.begin(), m_vlist.end(), sortJavas);
endResetModel();
}
JavaListLoadTask::JavaListLoadTask(JavaVersionList *vlist) : Task()
JavaListLoadTask::JavaListLoadTask(JavaInstallList *vlist) : Task()
{
m_list = vlist;
m_currentRecommended = NULL;
@@ -162,7 +130,7 @@ void JavaListLoadTask::executeTask()
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
connect(m_job.get(), SIGNAL(progress(qint64,qint64)), this, SLOT(checkerProgress(qint64, qint64)));
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: ";
int id = 0;
@@ -181,29 +149,23 @@ void JavaListLoadTask::executeTask()
m_job->start();
}
void JavaListLoadTask::checkerProgress(qint64 current, qint64 total)
{
float progress = (current * 100.0) / total;
this->setProgress((int) progress);
}
void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
{
QList<JavaVersionPtr> candidates;
QList<JavaInstallPtr> candidates;
qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
{
if(result.valid)
if(result.validity == JavaCheckResult::Validity::Valid)
{
JavaVersionPtr javaVersion(new JavaVersion());
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = result.javaVersion;
javaVersion->arch = result.mojangPlatform;
javaVersion->path = result.path;
candidates.append(javaVersion);
qDebug() << " " << javaVersion->id << javaVersion->arch << javaVersion->path;
qDebug() << " " << javaVersion->id.toString() << javaVersion->arch << javaVersion->path;
}
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,57 +20,31 @@
#include "BaseVersionList.h"
#include "tasks/Task.h"
#include "java/JavaCheckerJob.h"
#include "JavaCheckerJob.h"
#include "JavaInstall.h"
#include "multimc_logic_export.h"
class JavaListLoadTask;
struct JavaVersion : public BaseVersion
{
JavaVersion(){}
JavaVersion(QString id, QString arch, QString path)
: id(id), arch(arch), path(path)
{
}
virtual QString descriptor()
{
return id;
}
virtual QString name()
{
return id;
}
virtual QString typeString() const
{
return arch;
}
QString id;
QString arch;
QString path;
bool recommended = false;
};
typedef std::shared_ptr<JavaVersion> JavaVersionPtr;
class JavaVersionList : public BaseVersionList
class MULTIMC_LOGIC_EXPORT JavaInstallList : public BaseVersionList
{
Q_OBJECT
public:
explicit JavaVersionList(QObject *parent = 0);
explicit JavaInstallList(QObject *parent = 0);
virtual Task *getLoadTask() override;
virtual bool isLoaded() override;
virtual const BaseVersionPtr at(int i) const override;
virtual int count() const override;
virtual void sort() override;
virtual void sortVersions() override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual RoleList providesRoles() override;
virtual RoleList providesRoles() const override;
public slots:
virtual void updateListData(QList<BaseVersionPtr> versions);
virtual void updateListData(QList<BaseVersionPtr> versions) override;
protected:
QList<BaseVersionPtr> m_vlist;
@@ -83,16 +57,15 @@ class JavaListLoadTask : public Task
Q_OBJECT
public:
explicit JavaListLoadTask(JavaVersionList *vlist);
explicit JavaListLoadTask(JavaInstallList *vlist);
~JavaListLoadTask();
virtual void executeTask();
public slots:
void javaCheckerFinished(QList<JavaCheckResult> results);
void checkerProgress(qint64 current, qint64 total);
protected:
std::shared_ptr<JavaCheckerJob> m_job;
JavaVersionList *m_list;
JavaVersion *m_currentRecommended;
JavaInstallList *m_list;
JavaInstall *m_currentRecommended;
};

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,20 +19,20 @@
#include <QStringList>
#include <settings/Setting.h>
#include <pathutils.h>
#include <QDebug>
#include "java/JavaUtils.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaVersionList.h"
#include "java/JavaInstallList.h"
#include "FileSystem.h"
JavaUtils::JavaUtils()
{
}
JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
{
JavaVersionPtr javaVersion(new JavaVersion());
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = id;
javaVersion->arch = arch;
@@ -41,21 +41,25 @@ JavaVersionPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
return javaVersion;
}
JavaVersionPtr JavaUtils::GetDefaultJava()
JavaInstallPtr JavaUtils::GetDefaultJava()
{
JavaVersionPtr javaVersion(new JavaVersion());
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = "java";
javaVersion->arch = "unknown";
#if defined(Q_OS_WIN32)
javaVersion->path = "javaw";
#else
javaVersion->path = "java";
#endif
return javaVersion;
}
#if WINDOWS
QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
#if defined(Q_OS_WIN32)
QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
{
QList<JavaVersionPtr> javas;
QList<JavaInstallPtr> javas;
QString archType = "unknown";
if (keyType == KEY_WOW64_64KEY)
@@ -115,12 +119,12 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
&valueSz);
// Now, we construct the version object and add it to the list.
JavaVersionPtr javaVersion(new JavaVersion());
JavaInstallPtr javaVersion(new JavaInstall());
javaVersion->id = subKeyName;
javaVersion->arch = archType;
javaVersion->path =
QDir(PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
QDir(FS::PathCombine(value, "bin")).absoluteFilePath("javaw.exe");
javas.append(javaVersion);
}
@@ -138,29 +142,31 @@ QList<JavaVersionPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
QList<QString> JavaUtils::FindJavaPaths()
{
QList<JavaVersionPtr> java_candidates;
QList<JavaInstallPtr> java_candidates;
QList<JavaVersionPtr> JRE64s = this->FindJavaFromRegistryKey(
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaVersionPtr> JDK64s = this->FindJavaFromRegistryKey(
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
QList<JavaVersionPtr> JRE32s = this->FindJavaFromRegistryKey(
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
QList<JavaVersionPtr> JDK32s = this->FindJavaFromRegistryKey(
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
java_candidates.append(JRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK64s);
java_candidates.append(JRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK32s);
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
QList<QString> candidates;
for(JavaVersionPtr java_candidate : java_candidates)
for(JavaInstallPtr java_candidate : java_candidates)
{
if(!candidates.contains(java_candidate->path))
{
@@ -171,7 +177,7 @@ QList<QString> JavaUtils::FindJavaPaths()
return candidates;
}
#elif OSX
#elif defined(Q_OS_MAC)
QList<QString> JavaUtils::FindJavaPaths()
{
QList<QString> javas;
@@ -194,16 +200,43 @@ QList<QString> JavaUtils::FindJavaPaths()
return javas;
}
#elif LINUX
#elif defined(Q_OS_LINUX)
QList<QString> JavaUtils::FindJavaPaths()
{
qDebug() << "Linux Java detection incomplete - defaulting to \"java\"";
QList<QString> javas;
javas.append(this->GetDefaultJava()->path);
javas.append("/opt/java/bin/java");
javas.append("/usr/bin/java");
auto scanJavaDir = [&](const QString & dirPath)
{
QDir dir(dirPath);
if(!dir.exists())
return;
auto entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks);
for(auto & entry: entries)
{
QString prefix;
if(entry.isAbsolute())
{
prefix = entry.absoluteFilePath();
}
else
{
prefix = entry.filePath();
}
javas.append(FS::PathCombine(prefix, "jre/bin/java"));
javas.append(FS::PathCombine(prefix, "bin/java"));
}
};
// oracle RPMs
scanJavaDir("/usr/java");
// general locations used by distro packaging
scanJavaDir("/usr/lib/jvm");
scanJavaDir("/usr/lib32/jvm");
// javas stored in MultiMC's folder
scanJavaDir("java");
return javas;
}
#else

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,28 +16,28 @@
#pragma once
#include <QStringList>
#include <QWidget>
#include <osutils.h>
#include "JavaCheckerJob.h"
#include "JavaChecker.h"
#include "JavaVersionList.h"
#include "JavaInstallList.h"
#if WINDOWS
#ifdef Q_OS_WIN
#include <windows.h>
#endif
class JavaUtils : public QObject
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{
Q_OBJECT
public:
JavaUtils();
JavaVersionPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
JavaInstallPtr MakeJavaPtr(QString path, QString id = "unknown", QString arch = "unknown");
QList<QString> FindJavaPaths();
JavaVersionPtr GetDefaultJava();
JavaInstallPtr GetDefaultJava();
#if WINDOWS
QList<JavaVersionPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
#ifdef Q_OS_WIN
QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
#endif
};

View File

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

View File

@@ -0,0 +1,42 @@
#pragma once
#include "multimc_logic_export.h"
#include <QString>
class MULTIMC_LOGIC_EXPORT JavaVersion
{
friend class JavaVersionTest;
public:
JavaVersion() {};
JavaVersion(const QString & rhs);
JavaVersion & operator=(const QString & rhs);
bool operator<(const JavaVersion & rhs);
bool operator==(const JavaVersion & rhs);
bool operator>(const JavaVersion & rhs);
bool requiresPermGen();
QString toString();
int major()
{
return m_major;
}
int minor()
{
return m_minor;
}
int security()
{
return m_security;
}
private:
QString m_string;
int m_major = 0;
int m_minor = 0;
int m_security = 0;
bool m_parseable = false;
QString m_prerelease;
};

View File

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

View File

@@ -0,0 +1,136 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "CheckJava.h"
#include <launch/LaunchTask.h>
#include <FileSystem.h>
#include <QStandardPaths>
#include <QFileInfo>
#include <sys.h>
void CheckJava::executeTask()
{
auto instance = m_parent->instance();
auto settings = instance->settings();
m_javaPath = FS::ResolveExecutable(settings->get("JavaPath").toString());
bool perInstance = settings->get("OverrideJava").toBool() || settings->get("OverrideJavaLocation").toBool();
auto realJavaPath = QStandardPaths::findExecutable(m_javaPath);
if (realJavaPath.isEmpty())
{
if (perInstance)
{
emit logLine(
tr("The java binary \"%1\" couldn't be found. Please fix the java path "
"override in the instance's settings or disable it.").arg(m_javaPath),
MessageLevel::Warning);
}
else
{
emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
"the settings.").arg(m_javaPath),
MessageLevel::Warning);
}
emitFailed(tr("Java path is not valid."));
return;
}
else
{
emit logLine("Java path is:\n" + m_javaPath + "\n\n", MessageLevel::MultiMC);
}
QFileInfo javaInfo(realJavaPath);
qlonglong javaUnixTime = javaInfo.lastModified().toMSecsSinceEpoch();
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
{
m_JavaChecker = std::make_shared<JavaChecker>();
emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
return;
}
else
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
printJavaInfo(verString, archString);
}
emitSucceeded();
}
void CheckJava::checkJavaFinished(JavaCheckResult result)
{
switch (result.validity)
{
case JavaCheckResult::Validity::Errored:
{
// Error message displayed if java can't start
emit logLine(tr("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
printSystemInfo(false, false);
emitFailed(tr("Could not start java!"));
return;
}
case JavaCheckResult::Validity::ReturnedInvalidData:
{
emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
printSystemInfo(false, false);
emitSucceeded();
return;
}
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
return;
}
}
}
void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
{
emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
printSystemInfo(true, architecture == "64");
}
void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
{
auto cpu64 = Sys::isCPU64bit();
auto system64 = Sys::isSystem64bit();
if(cpu64 != system64)
{
emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
}
if(javaIsKnown)
{
if(javaIs64bit != system64)
{
emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
}
}
}

View File

@@ -0,0 +1,45 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
class CheckJava: public LaunchStep
{
Q_OBJECT
public:
explicit CheckJava(LaunchTask *parent) :LaunchStep(parent){};
virtual ~CheckJava() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
private slots:
void checkJavaFinished(JavaCheckResult result);
private:
void printJavaInfo(const QString & version, const QString & architecture);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private:
QString m_javaPath;
qlonglong m_javaUnixTime;
JavaCheckerPtr m_JavaChecker;
};

View File

@@ -0,0 +1,27 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "LaunchStep.h"
#include "LaunchTask.h"
void LaunchStep::bind(LaunchTask *parent)
{
m_parent = parent;
connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch);
connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine);
connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines);
connect(this, &LaunchStep::finished, parent, &LaunchTask::onStepFinished);
connect(this, &LaunchStep::progressReportingRequest, parent, &LaunchTask::onProgressReportingRequested);
}

View File

@@ -0,0 +1,50 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "tasks/Task.h"
#include "MessageLevel.h"
#include <QStringList>
class LaunchTask;
class LaunchStep: public Task
{
Q_OBJECT
public: /* methods */
explicit LaunchStep(LaunchTask *parent):Task(nullptr), m_parent(parent)
{
bind(parent);
};
virtual ~LaunchStep() {};
protected: /* methods */
virtual void bind(LaunchTask *parent);
signals:
void logLines(QStringList lines, MessageLevel::Enum level);
void logLine(QString line, MessageLevel::Enum level);
void readyForLaunch();
void progressReportingRequest();
public slots:
virtual void proceed() {};
// called in the opposite order than the Task launch(), used to clean up or otherwise undo things after the launch ends
virtual void finalize() {};
protected: /* data */
LaunchTask *m_parent;
};

View File

@@ -0,0 +1,280 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "launch/LaunchTask.h"
#include "MessageLevel.h"
#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <QDebug>
#include <QDir>
#include <QEventLoop>
#include <QRegularExpression>
#include <QCoreApplication>
#include <QStandardPaths>
#include <assert.h>
void LaunchTask::init()
{
m_instance->setRunning(true);
}
std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
{
std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init();
return proc;
}
LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{
}
void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
{
m_steps.append(step);
}
void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
{
m_steps.prepend(step);
}
void LaunchTask::executeTask()
{
m_instance->setCrashed(false);
if(!m_steps.size())
{
state = LaunchTask::Finished;
emitSucceeded();
}
state = LaunchTask::Running;
onStepFinished();
}
void LaunchTask::onReadyForLaunch()
{
state = LaunchTask::Waiting;
emit readyForLaunch();
}
void LaunchTask::onStepFinished()
{
// initial -> just start the first step
if(currentStep == -1)
{
currentStep ++;
m_steps[currentStep]->start();
return;
}
auto step = m_steps[currentStep];
if(step->successful())
{
// end?
if(currentStep == m_steps.size() - 1)
{
finalizeSteps(true, QString());
}
else
{
currentStep ++;
step = m_steps[currentStep];
step->start();
}
}
else
{
finalizeSteps(false, step->failReason());
}
}
void LaunchTask::finalizeSteps(bool successful, const QString& error)
{
for(auto step = currentStep; step >= 0; step--)
{
m_steps[step]->finalize();
}
if(successful)
{
emitSucceeded();
}
else
{
emitFailed(error);
}
}
void LaunchTask::onProgressReportingRequested()
{
state = LaunchTask::Waiting;
emit requestProgress(m_steps[currentStep].get());
}
void LaunchTask::setCensorFilter(QMap<QString, QString> filter)
{
m_censorFilter = filter;
}
QString LaunchTask::censorPrivateInfo(QString in)
{
auto iter = m_censorFilter.begin();
while (iter != m_censorFilter.end())
{
in.replace(iter.key(), iter.value());
iter++;
}
return in;
}
void LaunchTask::proceed()
{
if(state != LaunchTask::Waiting)
{
return;
}
m_steps[currentStep]->proceed();
}
bool LaunchTask::canAbort() const
{
switch(state)
{
case LaunchTask::Aborted:
case LaunchTask::Failed:
case LaunchTask::Finished:
return false;
case LaunchTask::NotStarted:
return true;
case LaunchTask::Running:
case LaunchTask::Waiting:
{
auto step = m_steps[currentStep];
return step->canAbort();
}
}
return false;
}
bool LaunchTask::abort()
{
switch(state)
{
case LaunchTask::Aborted:
case LaunchTask::Failed:
case LaunchTask::Finished:
return true;
case LaunchTask::NotStarted:
{
state = LaunchTask::Aborted;
emitFailed("Aborted");
return true;
}
case LaunchTask::Running:
case LaunchTask::Waiting:
{
auto step = m_steps[currentStep];
if(!step->canAbort())
{
return false;
}
if(step->abort())
{
state = LaunchTask::Aborted;
return true;
}
}
default:
break;
}
return false;
}
shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
{
if(!m_logModel)
{
m_logModel.reset(new LogModel());
m_logModel->setMaxLines(m_instance->getConsoleMaxLines());
m_logModel->setStopOnOverflow(m_instance->shouldStopOnConsoleOverflow());
// FIXME: should this really be here?
m_logModel->setOverflowMessage(tr("MultiMC stopped watching the game log because the log length surpassed %1 lines.\n"
"You may have to fix your mods because the game is still logging to files and"
" likely wasting harddrive space at an alarming rate!").arg(m_logModel->getMaxLines()));
}
return m_logModel;
}
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
{
for (auto & line: lines)
{
onLogLine(line, defaultLevel);
}
}
void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
{
// if the launcher part set a log level, use it
auto innerLevel = MessageLevel::fromLine(line);
if(innerLevel != MessageLevel::Unknown)
{
level = innerLevel;
}
// If the level is still undetermined, guess level
if (level == MessageLevel::StdErr || level == MessageLevel::StdOut || level == MessageLevel::Unknown)
{
level = m_instance->guessLevel(line, level);
}
// censor private user info
line = censorPrivateInfo(line);
auto &model = *getLogModel();
model.append(level, line);
}
void LaunchTask::emitSucceeded()
{
m_instance->setRunning(false);
Task::emitSucceeded();
}
void LaunchTask::emitFailed(QString reason)
{
m_instance->setRunning(false);
m_instance->setCrashed(true);
Task::emitFailed(reason);
}
QString LaunchTask::substituteVariables(const QString &cmd) const
{
QString out = cmd;
auto variables = m_instance->getVariables();
for (auto it = variables.begin(); it != variables.end(); ++it)
{
out.replace("$" + it.key(), it.value());
}
auto env = QProcessEnvironment::systemEnvironment();
for (auto var : env.keys())
{
out.replace("$" + var, env.value(var));
}
return out;
}

View File

@@ -0,0 +1,125 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QProcess>
#include <QObjectPtr.h>
#include "LogModel.h"
#include "BaseInstance.h"
#include "MessageLevel.h"
#include "LoggedProcess.h"
#include "LaunchStep.h"
#include "multimc_logic_export.h"
class MULTIMC_LOGIC_EXPORT LaunchTask: public Task
{
Q_OBJECT
protected:
explicit LaunchTask(InstancePtr instance);
void init();
public:
enum State
{
NotStarted,
Running,
Waiting,
Failed,
Aborted,
Finished
};
public: /* methods */
static std::shared_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask() {};
void appendStep(std::shared_ptr<LaunchStep> step);
void prependStep(std::shared_ptr<LaunchStep> step);
void setCensorFilter(QMap<QString, QString> filter);
InstancePtr instance()
{
return m_instance;
}
void setPid(qint64 pid)
{
m_pid = pid;
}
qint64 pid()
{
return m_pid;
}
/**
* @brief prepare the process for launch (for multi-stage launch)
*/
virtual void executeTask() override;
/**
* @brief launch the armed instance
*/
void proceed();
/**
* @brief abort launch
*/
bool abort() override;
bool canAbort() const override;
shared_qobject_ptr<LogModel> getLogModel();
public:
QString substituteVariables(const QString &cmd) const;
QString censorPrivateInfo(QString in);
protected: /* methods */
virtual void emitFailed(QString reason) override;
virtual void emitSucceeded() override;
signals:
/**
* @brief emitted when the launch preparations are done
*/
void readyForLaunch();
void requestProgress(Task *task);
void requestLogging();
public slots:
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onReadyForLaunch();
void onStepFinished();
void onProgressReportingRequested();
private: /*methods */
void finalizeSteps(bool successful, const QString & error);
protected: /* data */
InstancePtr m_instance;
shared_qobject_ptr<LogModel> m_logModel;
QList <std::shared_ptr<LaunchStep>> m_steps;
QMap<QString, QString> m_censorFilter;
int currentStep = -1;
State state = NotStarted;
qint64 m_pid = -1;
};

View File

@@ -0,0 +1,149 @@
#include "LogModel.h"
LogModel::LogModel(QObject *parent):QAbstractListModel(parent)
{
m_content.resize(m_maxLines);
}
int LogModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_numLines;
}
QVariant LogModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_numLines)
return QVariant();
auto row = index.row();
auto realRow = (row + m_firstLine) % m_maxLines;
if (role == Qt::DisplayRole || role == Qt::EditRole)
{
return m_content[realRow].line;
}
if(role == LevelRole)
{
return m_content[realRow].level;
}
return QVariant();
}
void LogModel::append(MessageLevel::Enum level, QString line)
{
if(m_suspended)
{
return;
}
int lineNum = (m_firstLine + m_numLines) % m_maxLines;
// overflow
if(m_numLines == m_maxLines)
{
if(m_stopOnOverflow)
{
// nothing more to do, the buffer is full
return;
}
beginRemoveRows(QModelIndex(), 0, 0);
m_firstLine = (m_firstLine + 1) % m_maxLines;
m_numLines --;
endRemoveRows();
}
else if (m_numLines == m_maxLines - 1 && m_stopOnOverflow)
{
level = MessageLevel::Fatal;
line = m_overflowMessage;
}
beginInsertRows(QModelIndex(), m_numLines, m_numLines);
m_numLines ++;
m_content[lineNum].level = level;
m_content[lineNum].line = line;
endInsertRows();
}
void LogModel::suspend(bool suspend)
{
m_suspended = suspend;
}
void LogModel::clear()
{
beginResetModel();
m_firstLine = 0;
m_numLines = 0;
endResetModel();
}
QString LogModel::toPlainText()
{
QString out;
out.reserve(m_numLines * 80);
for(int i = 0; i < m_numLines; i++)
{
QString & line = m_content[(m_firstLine + i) % m_maxLines].line;
out.append(line + '\n');
}
out.squeeze();
return out;
}
void LogModel::setMaxLines(int maxLines)
{
// no-op
if(maxLines == m_maxLines)
{
return;
}
// if it all still fits in the buffer, just resize it
if(m_firstLine + m_numLines < m_maxLines)
{
m_maxLines = maxLines;
m_content.resize(maxLines);
return;
}
// otherwise, we need to reorganize the data because it crosses the wrap boundary
QVector<entry> newContent;
newContent.resize(maxLines);
if(m_numLines <= maxLines)
{
// if it all fits in the new buffer, just copy it over
for(int i = 0; i < m_numLines; i++)
{
newContent[i] = m_content[(m_firstLine + i) % m_maxLines];
}
m_content.swap(newContent);
}
else
{
// if it doesn't fit, part of the data needs to be thrown away (the oldest log messages)
int lead = m_numLines - maxLines;
beginRemoveRows(QModelIndex(), 0, lead - 1);
for(int i = 0; i < maxLines; i++)
{
newContent[i] = m_content[(m_firstLine + lead + i) % m_maxLines];
}
m_numLines = m_maxLines;
m_content.swap(newContent);
endRemoveRows();
}
m_firstLine = 0;
m_maxLines = maxLines;
}
int LogModel::getMaxLines()
{
return m_maxLines;
}
void LogModel::setStopOnOverflow(bool stop)
{
m_stopOnOverflow = stop;
}
void LogModel::setOverflowMessage(const QString& overflowMessage)
{
m_overflowMessage = overflowMessage;
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include <QAbstractListModel>
#include <QString>
#include "MessageLevel.h"
#include <multimc_logic_export.h>
class MULTIMC_LOGIC_EXPORT LogModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit LogModel(QObject *parent = 0);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
void append(MessageLevel::Enum, QString line);
void clear();
void suspend(bool suspend);
QString toPlainText();
int getMaxLines();
void setMaxLines(int maxLines);
void setStopOnOverflow(bool stop);
void setOverflowMessage(const QString & overflowMessage);
enum Roles
{
LevelRole = Qt::UserRole
};
private /* types */:
struct entry
{
MessageLevel::Enum level;
QString line;
};
private: /* data */
QVector <entry> m_content;
int m_maxLines = 1000;
// first line in the circular buffer
int m_firstLine = 0;
// number of lines occupied in the circular buffer
int m_numLines = 0;
bool m_stopOnOverflow = false;
QString m_overflowMessage = "OVERFLOW";
bool m_suspended = false;
private:
Q_DISABLE_COPY(LogModel)
};

View File

@@ -0,0 +1,84 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PostLaunchCommand.h"
#include <launch/LaunchTask.h>
PostLaunchCommand::PostLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
{
auto instance = m_parent->instance();
m_command = instance->getPostExitCommand();
m_process.setProcessEnvironment(instance->createEnvironment());
connect(&m_process, &LoggedProcess::log, this, &PostLaunchCommand::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &PostLaunchCommand::on_state);
}
void PostLaunchCommand::executeTask()
{
QString postlaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Post-Launch command: %1").arg(postlaunch_cmd), MessageLevel::MultiMC);
m_process.start(postlaunch_cmd);
}
void PostLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [&]()
{
return tr("Post-Launch command failed with code %1.\n\n").arg(m_process.exitCode());
};
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
return;
}
case LoggedProcess::Finished:
{
if(m_process.exitCode() != 0)
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
}
else
{
emit logLine(tr("Post-Launch command ran successfully.\n\n"), MessageLevel::MultiMC);
emitSucceeded();
}
}
default:
break;
}
}
void PostLaunchCommand::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
bool PostLaunchCommand::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@@ -0,0 +1,39 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <LoggedProcess.h>
class PostLaunchCommand: public LaunchStep
{
Q_OBJECT
public:
explicit PostLaunchCommand(LaunchTask *parent);
virtual void executeTask();
virtual bool abort();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
};

View File

@@ -0,0 +1,85 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PreLaunchCommand.h"
#include <launch/LaunchTask.h>
PreLaunchCommand::PreLaunchCommand(LaunchTask *parent) : LaunchStep(parent)
{
auto instance = m_parent->instance();
m_command = instance->getPreLaunchCommand();
m_process.setProcessEnvironment(instance->createEnvironment());
connect(&m_process, &LoggedProcess::log, this, &PreLaunchCommand::logLines);
connect(&m_process, &LoggedProcess::stateChanged, this, &PreLaunchCommand::on_state);
}
void PreLaunchCommand::executeTask()
{
//FIXME: where to put this?
QString prelaunch_cmd = m_parent->substituteVariables(m_command);
emit logLine(tr("Running Pre-Launch command: %1").arg(prelaunch_cmd), MessageLevel::MultiMC);
m_process.start(prelaunch_cmd);
}
void PreLaunchCommand::on_state(LoggedProcess::State state)
{
auto getError = [&]()
{
return tr("Pre-Launch command failed with code %1.\n\n").arg(m_process.exitCode());
};
switch(state)
{
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
case LoggedProcess::FailedToStart:
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
return;
}
case LoggedProcess::Finished:
{
if(m_process.exitCode() != 0)
{
auto error = getError();
emit logLine(error, MessageLevel::Fatal);
emitFailed(error);
}
else
{
emit logLine(tr("Pre-Launch command ran successfully.\n\n"), MessageLevel::MultiMC);
emitSucceeded();
}
}
default:
break;
}
}
void PreLaunchCommand::setWorkingDirectory(const QString &wd)
{
m_process.setWorkingDirectory(wd);
}
bool PreLaunchCommand::abort()
{
auto state = m_process.state();
if (state == LoggedProcess::Running || state == LoggedProcess::Starting)
{
m_process.kill();
}
return true;
}

View File

@@ -0,0 +1,39 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "launch/LaunchStep.h"
#include "LoggedProcess.h"
class PreLaunchCommand: public LaunchStep
{
Q_OBJECT
public:
explicit PreLaunchCommand(LaunchTask *parent);
virtual void executeTask();
virtual bool abort();
virtual bool canAbort() const
{
return true;
}
void setWorkingDirectory(const QString &wd);
private slots:
void on_state(LoggedProcess::State state);
private:
LoggedProcess m_process;
QString m_command;
};

View File

@@ -0,0 +1,29 @@
#include "TextPrint.h"
TextPrint::TextPrint(LaunchTask * parent, const QStringList &lines, MessageLevel::Enum level) : LaunchStep(parent)
{
m_lines = lines;
m_level = level;
}
TextPrint::TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level) : LaunchStep(parent)
{
m_lines.append(line);
m_level = level;
}
void TextPrint::executeTask()
{
emit logLines(m_lines, m_level);
emitSucceeded();
}
bool TextPrint::canAbort() const
{
return true;
}
bool TextPrint::abort()
{
emitFailed("Aborted.");
return true;
}

View File

@@ -0,0 +1,43 @@
/* Copyright 2013-2017 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <launch/LaunchStep.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
#include "multimc_logic_export.h"
/*
* FIXME: maybe do not export
*/
class MULTIMC_LOGIC_EXPORT TextPrint: public LaunchStep
{
Q_OBJECT
public:
explicit TextPrint(LaunchTask *parent, const QStringList &lines, MessageLevel::Enum level);
explicit TextPrint(LaunchTask *parent, const QString &line, MessageLevel::Enum level);
virtual ~TextPrint(){};
virtual void executeTask();
virtual bool canAbort() const;
virtual bool abort();
private:
QStringList m_lines;
MessageLevel::Enum m_level;
};

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