Compare commits

..

240 Commits

Author SHA1 Message Date
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
iarspider
fbec48080b NOISSUE Do not ask to overwrite existing file twice when exporting instances 2016-01-20 21:11:58 +03:00
746 changed files with 23594 additions and 13399 deletions

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:
---------------------------

2
.gitmodules vendored
View File

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

View File

@@ -1,9 +1,6 @@
# General set up
language: cpp
cache: apt
notifications:
irc: "irc.esper.net#MultiMC"
email: false
# Build matrix set up
compiler:
@@ -13,34 +10,21 @@ os:
- linux
# - osx
env:
- QT_VERSION=5.0.2
- QT_VERSION=5.1.1
- QT_VERSION=5.2.1
- QT_VERSION=5.3.2
- QT_VERSION=5.4.2
- QT_VERSION=5.5.1 # latest
- 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.0.2
- os: osx
env: QT_VERSION=5.1.1
- os: osx
env: QT_VERSION=5.2.1
- os: osx
env: QT_VERSION=5.3.2
- os: osx
env: QT_VERSION=5.4.2
- os: osx
env: QT_VERSION=5.5.1
allow_failures:
- env: QT_VERSION=5.0.2
- env: QT_VERSION=5.1.1
- env: QT_VERSION=5.2.1
# - os: osx
# env: QT_VERSION=5.6
# Install dependencies
install:

View File

@@ -33,24 +33,45 @@ Getting the project to build and run on Linux is easy if you use any modern and
## Build dependencies
* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
* Qt 5.5.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager
* 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`)
### Installing Qt using the installer
### Building from command line
You need a source folder, a build folder and an install folder.
Let's say you want everything in `~/MultiMC/`:
```
# 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.5.1/gcc 64-bit ticked.
- 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".
- Installation can take a very long time, go grab a cup of tea or something and let it work.
### Loading the project in Qt Creator
### 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.
@@ -70,7 +91,7 @@ Getting the project to build and run on Linux is easy if you use any modern and
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.5.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [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.
@@ -120,6 +141,14 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- 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](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
@@ -135,6 +164,9 @@ brew install cmake
```
### Build
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
@@ -145,8 +177,9 @@ 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 ..
cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/Users/YOU/some/path/that/makes/sense/
make
make install
```
**These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**

View File

@@ -17,19 +17,13 @@ enable_testing()
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
######## Set module path ########
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
# Only used for test coverage
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 ########
@@ -37,13 +31,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED true)
set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
include(Coverage)
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 ################################
@@ -76,43 +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_subdirectory(depends/hoedown) # markdown parser
add_subdirectory(depends/launcher) # java based launcher part for Minecraft
add_subdirectory(depends/javacheck) # java compatibility checker
add_subdirectory(depends/xz-embedded) # xz compression
add_subdirectory(depends/pack200) # java pack200 compression
add_subdirectory(depends/rainbow) # Qt extension for colors
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.
add_subdirectory(depends/libnbtplusplus)
set(NBT_NAME MultiMC_nbt++)
add_subdirectory(libraries/libnbtplusplus)
######## MultiMC Libs ########
add_subdirectory(depends/LogicalGui) # GUI -> Logic connection
add_subdirectory(depends/iconfix) # fork of Qt's QIcon loader
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(wonkoclient)

View File

@@ -1,6 +1,6 @@
#MultiMC
Copyright 2012-2014 MultiMC Contributors
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

View File

@@ -38,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}")

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,9 +17,9 @@
#include <QPixmap>
#include "multimc_logic_export.h"
#include "multimc_gui_export.h"
namespace SkinUtils
{
QPixmap MULTIMC_LOGIC_EXPORT getFaceFromCache(QString id, 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.
@@ -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());
@@ -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);
}
@@ -243,7 +251,7 @@ 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)
{
@@ -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,38 +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_logic_export.h"
#include "multimc_gui_export.h"
class QFileSystemWatcher;
class MULTIMC_LOGIC_EXPORT 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,39 +17,33 @@
#include <QString>
#include <QDateTime>
#include <QIcon>
#include <icons/IIconList.h>
#include "multimc_logic_export.h"
#include "multimc_gui_export.h"
struct MULTIMC_LOGIC_EXPORT MMCImage
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 MULTIMC_LOGIC_EXPORT 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.

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,13 +17,13 @@
#include <QFileInfo>
#include <QDir>
#include <QDebug>
#include "settings/INISettingsObject.h"
#include "settings/Setting.h"
#include "settings/OverrideSetting.h"
#include "minecraft/MinecraftVersionList.h"
#include "icons/IconList.h"
#include "FileSystem.h"
#include "Commandline.h"
@@ -35,7 +35,6 @@ 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);
@@ -50,6 +49,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
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);
}
@@ -76,10 +76,32 @@ void BaseInstance::iconUpdated(QString key)
}
}
void BaseInstance::invalidate()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been invalidated.";
}
void BaseInstance::nuke()
{
changeStatus(Status::Gone);
qDebug() << "Instance" << id() << "has been deleted by MultiMC.";
FS::deletePath(instanceRoot());
emit nuked(this);
}
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
@@ -94,18 +116,24 @@ bool BaseInstance::isRunning() const
void BaseInstance::setRunning(bool running)
{
if(running && !m_isRunning)
if(running == m_isRunning)
return;
m_isRunning = running;
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
}
else if(!running && m_isRunning)
else
{
qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
emit propertiesChanged(this);
}
m_isRunning = running;
emit runningStatusChanged(running);
}
int64_t BaseInstance::totalTimePlayed() const
@@ -144,44 +172,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()
@@ -270,3 +263,24 @@ QStringList BaseInstance::extraArguments() const
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
return m_launchProcess;
}
void BaseInstance::setProvider(BaseInstanceProvider* provider)
{
// only once.
assert(!m_provider);
if(m_provider)
{
qWarning() << "Provider set more than once for instance" << id();
}
m_provider = provider;
}
BaseInstanceProvider* BaseInstance::provider() const
{
return m_provider;
}

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,8 +14,10 @@
*/
#pragma once
#include <cassert>
#include <QObject>
#include "QObjectPtr.h"
#include <QDateTime>
#include <QSet>
#include <QProcess>
@@ -24,8 +26,8 @@
#include "settings/INIFile.h"
#include "BaseVersionList.h"
#include "auth/MojangAccount.h"
#include "launch/MessageLevel.h"
#include "minecraft/auth/MojangAccount.h"
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
#include "multimc_logic_export.h"
@@ -34,6 +36,7 @@ class QDir;
class Task;
class LaunchTask;
class BaseInstance;
class BaseInstanceProvider;
// pointer for lazy people
typedef std::shared_ptr<BaseInstance> InstancePtr;
@@ -53,6 +56,13 @@ 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() {};
@@ -65,6 +75,14 @@ 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;
@@ -74,6 +92,9 @@ public:
int64_t totalTimePlayed() const;
void resetTimePlayed();
void setProvider(BaseInstanceProvider * provider);
BaseInstanceProvider * provider() const;
/// get the type of this instance
QString instanceType() const;
@@ -152,11 +173,14 @@ public:
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
virtual std::shared_ptr<Task> createUpdateTask() = 0;
virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
/// returns the current launch task (if any)
std::shared_ptr<LaunchTask> getLaunchTask();
/*!
* Returns a task that should be done right before launch
* This task should do any extra preparations needed
@@ -178,12 +202,6 @@ public:
*/
virtual QString getLogFileRoot() = 0;
/*!
* does any necessary cleanups after the instance finishes. also runs before\
* TODO: turn into a task that can run asynchronously
*/
virtual void cleanupAfterRun() = 0;
virtual QString getStatusbarDescription() = 0;
/// FIXME: this really should be elsewhere...
@@ -194,21 +212,60 @@ public:
virtual QString typeName() const = 0;
enum InstanceFlag
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;
protected:
void changeStatus(Status newStatus);
signals:
/*!
* \brief Signal emitted when properties relevant to the instance view change
@@ -218,25 +275,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.

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.
@@ -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.

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>
*

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*

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()
{
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());
@@ -149,6 +125,7 @@ void Env::initHttpMetaCache()
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();
}
@@ -178,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)
{
@@ -215,4 +191,14 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
qDebug() << proxyDesc;
}
#include "Env.moc"
QString Env::wonkoRootUrl() const
{
return d->m_wonkoRootUrl;
}
void Env::setWonkoRootUrl(const QString& url)
{
d->m_wonkoRootUrl = url;
}
#include "Env.moc"

View File

@@ -1,16 +1,19 @@
#pragma once
#include <memory>
#include "icons/IIconList.h"
#include <QString>
#include <QMap>
#include "multimc_logic_export.h"
class IconList;
#include "QObjectPtr.h"
class QNetworkAccessManager;
class HttpMetaCache;
class BaseVersionList;
class BaseVersion;
class WonkoIndex;
#if defined(ENV)
#undef ENV
@@ -21,18 +24,18 @@ 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();
@@ -47,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;
};

View File

@@ -60,6 +60,25 @@ QByteArray read(const QString &filename)
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);
@@ -131,6 +150,7 @@ bool copy::operator()(const QString &offset)
}
if(!operator()(inner_offset))
{
qWarning() << "Failed to copy" << inner_offset;
return false;
}
}

View File

@@ -28,6 +28,11 @@ MULTIMC_LOGIC_EXPORT void write(const QString &filename, const QByteArray &data)
*/
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!
@@ -43,7 +48,6 @@ MULTIMC_LOGIC_EXPORT bool ensureFolderPathExists(QString filenamepath);
class MULTIMC_LOGIC_EXPORT copy
{
public:
copy(const copy&) = delete;
copy(const QString & src, const QString & dst)
{
m_src = src;

View File

@@ -1,5 +1,6 @@
#include <QTest>
#include <QTemporaryDir>
#include <QStandardPaths>
#include "TestUtil.h"
#include "FileSystem.h"
@@ -80,7 +81,7 @@ slots:
void test_copy()
{
QString folder = QFINDTESTDATA("tests/data/test_folder");
QString folder = QFINDTESTDATA("data/test_folder");
auto f = [&folder]()
{
QTemporaryDir tempDir;
@@ -110,8 +111,54 @@ slots:
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 "tst_FileSystem.moc"
#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;
};

View File

@@ -54,4 +54,4 @@ slots:
QTEST_GUILESS_MAIN(GZipTest)
#include "tst_GZip.moc"
#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(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;
};

View File

@@ -23,16 +23,16 @@ public:
};
/// @throw FileSystemException
void write(const QJsonDocument &doc, const QString &filename);
MULTIMC_LOGIC_EXPORT void write(const QJsonDocument &doc, const QString &filename);
/// @throw FileSystemException
void write(const QJsonObject &object, const QString &filename);
MULTIMC_LOGIC_EXPORT void write(const QJsonObject &object, const QString &filename);
/// @throw FileSystemException
void write(const QJsonArray &array, const QString &filename);
MULTIMC_LOGIC_EXPORT void write(const QJsonArray &array, const QString &filename);
QByteArray toBinary(const QJsonObject &obj);
QByteArray toBinary(const QJsonArray &array);
QByteArray toText(const QJsonObject &obj);
QByteArray toText(const QJsonArray &array);
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");
@@ -48,33 +48,6 @@ MULTIMC_LOGIC_EXPORT QJsonArray requireArray(const QJsonDocument &doc, const QSt
void writeString(QJsonObject & to, const QString &key, const QString &value);
void writeStringList(QJsonObject & to, const QString &key, const QStringList &values);
template <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<std::shared_ptr<T>> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value->toJson());
}
to.insert(key, array);
}
}
template <typename T>
void writeObjectList(QJsonObject & to, QString key, QList<T> values)
{
if (!values.isEmpty())
{
QJsonArray array;
for (auto value: values)
{
array.append(value.toJson());
}
to.insert(key, array);
}
}
template<typename T>
QJsonValue toJson(const T &t)
{
@@ -140,9 +113,9 @@ template<> MULTIMC_LOGIC_EXPORT QUrl requireIsType<QUrl>(const QJsonValue &value
// 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_, const QString &what = "Value")
T ensureIsType(const QJsonValue &value, const T default_ = T(), const QString &what = "Value")
{
if (value.isUndefined())
if (value.isUndefined() || value.isNull())
{
return default_;
}
@@ -169,7 +142,7 @@ T requireIsType(const QJsonObject &parent, const QString &key, const QString &wh
}
template <typename T>
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_, const QString &what = "__placeholder__")
T ensureIsType(const QJsonObject &parent, const QString &key, const T default_ = T(), const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
@@ -180,10 +153,10 @@ T ensureIsType(const QJsonObject &parent, const QString &key, const T default_,
}
template <typename T>
QList<T> requireIsArrayOf(const QJsonDocument &doc)
QVector<T> requireIsArrayOf(const QJsonDocument &doc)
{
const QJsonArray array = requireArray(doc);
QList<T> out;
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(requireIsType<T>(val, "Document"));
@@ -192,19 +165,19 @@ QList<T> requireIsArrayOf(const QJsonDocument &doc)
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QString &what = "Value")
{
const QJsonArray array = requireIsType<QJsonArray>(value, what);
QList<T> out;
const QJsonArray array = ensureIsType<QJsonArray>(value, QJsonArray(), what);
QVector<T> out;
for (const QJsonValue val : array)
{
out.append(ensureIsType<T>(val, what));
out.append(requireIsType<T>(val, what));
}
return out;
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonValue &value, const QList<T> default_, const QString &what = "Value")
QVector<T> ensureIsArrayOf(const QJsonValue &value, const QVector<T> default_, const QString &what = "Value")
{
if (value.isUndefined())
{
@@ -215,19 +188,19 @@ QList<T> ensureIsArrayOf(const QJsonValue &value, const QList<T> default_, const
/// @throw JsonException
template <typename T>
QList<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
QVector<T> requireIsArrayOf(const QJsonObject &parent, const QString &key, const QString &what = "__placeholder__")
{
const QString localWhat = QString(what).replace("__placeholder__", '\'' + key + '\'');
if (!parent.contains(key))
{
throw JsonException(localWhat + "s parent does not contain " + localWhat);
}
return requireIsArrayOf<T>(parent.value(key), localWhat);
return ensureIsArrayOf<T>(parent.value(key), localWhat);
}
template <typename T>
QList<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
const QList<T> &default_, const QString &what = "__placeholder__")
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))
@@ -243,7 +216,7 @@ QList<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
{ \
return requireIsType<TYPE>(value, what); \
} \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_, const QString &what = "Value") \
inline TYPE ensure##NAME(const QJsonValue &value, const TYPE default_ = TYPE(), const QString &what = "Value") \
{ \
return ensureIsType<TYPE>(value, default_, what); \
} \
@@ -251,7 +224,7 @@ QList<T> ensureIsArrayOf(const QJsonObject &parent, const QString &key,
{ \
return requireIsType<TYPE>(parent, key, what); \
} \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_, const QString &what = "__placeholder") \
inline TYPE ensure##NAME(const QJsonObject &parent, const QString &key, const TYPE default_ = TYPE(), const QString &what = "__placeholder") \
{ \
return ensureIsType<TYPE>(parent, key, default_, what); \
}

View File

@@ -12,6 +12,14 @@ LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent)
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);
@@ -161,3 +169,8 @@ qint64 LoggedProcess::processId() const
return pid();
#endif
}
void LoggedProcess::setDetachable(bool detachable)
{
m_is_detachable = detachable;
}

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,12 +17,13 @@
#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 LoggedProcess : public QProcess
class MULTIMC_LOGIC_EXPORT LoggedProcess : public QProcess
{
Q_OBJECT
public:
@@ -38,13 +39,15 @@ public:
};
public:
explicit LoggedProcess(QObject* parent = 0);
virtual ~LoggedProcess() {};
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);
@@ -73,4 +76,5 @@ private:
State m_state = NotRunning;
int m_exit_code = 0;
bool m_is_aborting = false;
bool m_is_detachable = false;
};

View File

@@ -7,16 +7,13 @@ public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir)
{
setFlag(BaseInstance::VersionBrokenFlag);
setVersionBroken(true);
}
virtual ~NullInstance() {};
virtual bool setIntendedVersionId(QString) override
{
return false;
}
virtual void cleanupAfterRun() override
{
}
virtual QString currentVersionId() const override
{
return "Null";
@@ -48,7 +45,7 @@ public:
{
return nullptr;
}
virtual std::shared_ptr< Task > createUpdateTask() override
virtual shared_qobject_ptr< Task > createUpdateTask() override
{
return nullptr;
}
@@ -83,4 +80,14 @@ public:
{
return "Null";
}
bool canExport() const override
{
return false;
}
QStringList verboseDescription(AuthSessionPtr session) override
{
QStringList out;
out << "Null instance - placeholder.";
return out;
}
};

View File

@@ -48,6 +48,10 @@ public:
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();

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,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.
@@ -120,4 +120,4 @@ private slots:
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

@@ -4,7 +4,6 @@
#include <QFile>
#include <QProcess>
#include <QMap>
#include <QTemporaryFile>
#include <QCoreApplication>
#include <QDebug>
@@ -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

@@ -15,10 +15,16 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
QString mojangPlatform;
QString realPlatform;
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;

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.

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"

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.
@@ -77,7 +77,7 @@ QVariant JavaInstallList::data(const QModelIndex &index, int role) const
}
}
BaseVersionList::RoleList JavaInstallList::providesRoles()
BaseVersionList::RoleList JavaInstallList::providesRoles() const
{
return {VersionPointerRole, VersionIdRole, VersionRole, RecommendedRole, PathRole, ArchitectureRole};
}
@@ -156,7 +156,7 @@ void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
{
if(result.valid)
if(result.validity == JavaCheckResult::Validity::Valid)
{
JavaInstallPtr javaVersion(new JavaInstall());

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.
@@ -41,7 +41,7 @@ public:
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) override;

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.
@@ -47,7 +47,11 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
javaVersion->id = "java";
javaVersion->arch = "unknown";
#if defined(Q_OS_WIN32)
javaVersion->path = "javaw";
#else
javaVersion->path = "java";
#endif
return javaVersion;
}
@@ -150,10 +154,12 @@ QList<QString> JavaUtils::FindJavaPaths()
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);
@@ -201,9 +207,36 @@ QList<QString> JavaUtils::FindJavaPaths()
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,7 +16,6 @@
#pragma once
#include <QStringList>
#include <QWidget>
#include "JavaCheckerJob.h"
#include "JavaChecker.h"

View File

@@ -6,7 +6,7 @@
JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
{
string = javaVersionString;
m_string = javaVersionString;
auto getCapturedInteger = [](const QRegularExpressionMatch & match, const QString &what) -> int
{
@@ -28,12 +28,12 @@ JavaVersion & JavaVersion::operator=(const QString & javaVersionString)
pattern = QRegularExpression("(?<major>[0-9]+)([.](?<minor>[0-9]+))?([.](?<security>[0-9]+))?(-(?<prerelease>[a-zA-Z0-9]+))?");
}
auto match = pattern.match(string);
parseable = match.hasMatch();
major = getCapturedInteger(match, "major");
minor = getCapturedInteger(match, "minor");
security = getCapturedInteger(match, "security");
prerelease = match.captured("prerelease");
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;
}
@@ -44,38 +44,38 @@ JavaVersion::JavaVersion(const QString &rhs)
QString JavaVersion::toString()
{
return string;
return m_string;
}
bool JavaVersion::requiresPermGen()
{
if(parseable)
if(m_parseable)
{
return major < 8;
return m_major < 8;
}
return true;
}
bool JavaVersion::operator<(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
if(m_parseable && rhs.m_parseable)
{
if(major < rhs.major)
if(m_major < rhs.m_major)
return true;
if(major > rhs.major)
if(m_major > rhs.m_major)
return false;
if(minor < rhs.minor)
if(m_minor < rhs.m_minor)
return true;
if(minor > rhs.minor)
if(m_minor > rhs.m_minor)
return false;
if(security < rhs.security)
if(m_security < rhs.m_security)
return true;
if(security > rhs.security)
if(m_security > rhs.m_security)
return false;
// everything else being equal, consider prerelease status
bool thisPre = !prerelease.isEmpty();
bool rhsPre = !rhs.prerelease.isEmpty();
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
@@ -89,21 +89,21 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
return Strings::naturalCompare(prerelease, rhs.prerelease, Qt::CaseSensitive) < 0;
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(string, rhs.string, Qt::CaseSensitive) < 0;
else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
}
bool JavaVersion::operator==(const JavaVersion &rhs)
{
if(parseable && rhs.parseable)
if(m_parseable && rhs.m_parseable)
{
return major == rhs.major && minor == rhs.minor && security == rhs.security && prerelease == rhs.prerelease;
return m_major == rhs.m_major && m_minor == rhs.m_minor && m_security == rhs.m_security && m_prerelease == rhs.m_prerelease;
}
return string == rhs.string;
return m_string == rhs.m_string;
}
bool JavaVersion::operator>(const JavaVersion &rhs)

View File

@@ -20,11 +20,23 @@ public:
QString toString();
int major()
{
return m_major;
}
int minor()
{
return m_minor;
}
int security()
{
return m_security;
}
private:
QString string;
int major = 0;
int minor = 0;
int security = 0;
bool parseable = false;
QString prerelease;
QString m_string;
int m_major = 0;
int m_minor = 0;
int m_security = 0;
bool m_parseable = false;
QString m_prerelease;
};

View File

@@ -34,12 +34,12 @@ slots:
QFETCH(QString, prerelease);
JavaVersion test(string);
QCOMPARE(test.string, string);
QCOMPARE(test.m_string, string);
QCOMPARE(test.toString(), string);
QCOMPARE(test.major, major);
QCOMPARE(test.minor, minor);
QCOMPARE(test.security, security);
QCOMPARE(test.prerelease, prerelease);
QCOMPARE(test.m_major, major);
QCOMPARE(test.m_minor, minor);
QCOMPARE(test.m_security, security);
QCOMPARE(test.m_prerelease, prerelease);
}
void test_Sort_data()
@@ -113,8 +113,4 @@ slots:
QTEST_GUILESS_MAIN(JavaVersionTest)
#include "tst_JavaVersion.moc"
// manual testing fakery
#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

@@ -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 @@
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
class CheckJava: public LaunchStep
@@ -34,6 +34,10 @@ public:
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;

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.

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.
@@ -42,6 +42,8 @@ signals:
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

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -56,6 +56,7 @@ void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
void LaunchTask::executeTask()
{
m_instance->setCrashed(false);
if(!m_steps.size())
{
state = LaunchTask::Finished;
@@ -87,7 +88,7 @@ void LaunchTask::onStepFinished()
// end?
if(currentStep == m_steps.size() - 1)
{
emitSucceeded();
finalizeSteps(true, QString());
}
else
{
@@ -98,7 +99,23 @@ void LaunchTask::onStepFinished()
}
else
{
emitFailed(step->failReason());
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);
}
}
@@ -133,6 +150,26 @@ void LaunchTask::proceed()
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)
@@ -167,6 +204,15 @@ bool LaunchTask::abort()
return false;
}
shared_qobject_ptr<LogModel> LaunchTask::getLogModel()
{
if(!m_logModel)
{
m_logModel.reset(new LogModel());
}
return m_logModel;
}
void LaunchTask::onLogLines(const QStringList &lines, MessageLevel::Enum defaultLevel)
{
for (auto & line: lines)
@@ -193,20 +239,20 @@ void LaunchTask::onLogLine(QString line, MessageLevel::Enum level)
// censor private user info
line = censorPrivateInfo(line);
emit log(line, level);
auto &model = *getLogModel();
model.append(level, line);
}
void LaunchTask::emitSucceeded()
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
Task::emitSucceeded();
}
void LaunchTask::emitFailed(QString reason)
{
m_instance->cleanupAfterRun();
m_instance->setRunning(false);
m_instance->setCrashed(true);
Task::emitFailed(reason);
}

View File

@@ -1,4 +1,4 @@
/* Copyright 2013-2015 MultiMC Contributors
/* Copyright 2013-2017 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -17,6 +17,8 @@
#pragma once
#include <QProcess>
#include <QObjectPtr.h>
#include "LogModel.h"
#include "BaseInstance.h"
#include "MessageLevel.h"
#include "LoggedProcess.h"
@@ -78,7 +80,11 @@ public: /* methods */
/**
* @brief abort launch
*/
virtual bool abort() override;
bool abort() override;
bool canAbort() const override;
shared_qobject_ptr<LogModel> getLogModel();
public:
QString substituteVariables(const QString &cmd) const;
@@ -98,13 +104,6 @@ signals:
void requestLogging();
/**
* @brief emitted when we want to log something
* @param text the text to log
* @param level the level to log at
*/
void log(QString text, MessageLevel::Enum level = MessageLevel::MultiMC);
public slots:
void onLogLines(const QStringList& lines, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
void onLogLine(QString line, MessageLevel::Enum defaultLevel = MessageLevel::MultiMC);
@@ -112,8 +111,12 @@ public slots:
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;

View File

@@ -0,0 +1,144 @@
#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 < 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;
}
void LogModel::setStopOnOverflow(bool stop)
{
m_stopOnOverflow = stop;
}
void LogModel::setOverflowMessage(const QString& overflowMessage)
{
m_overflowMessage = overflowMessage;
}

View File

@@ -0,0 +1,53 @@
#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();
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

@@ -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.

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 @@
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>
class PostLaunchCommand: public LaunchStep
{

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.

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 @@
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include "launch/LaunchStep.h"
#include "LoggedProcess.h"
class PreLaunchCommand: public LaunchStep
{

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 @@
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
#include "multimc_logic_export.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.
@@ -18,7 +18,12 @@
void Update::executeTask()
{
m_updateTask = m_parent->instance()->createUpdateTask();
if(m_aborted)
{
emitFailed(tr("Task aborted."));
return;
}
m_updateTask.reset(m_parent->instance()->createUpdateTask());
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
@@ -39,12 +44,37 @@ void Update::updateFinished()
{
if(m_updateTask->successful())
{
m_updateTask.reset();
emitSucceeded();
}
else
{
QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
m_updateTask.reset();
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
}
}
bool Update::canAbort() const
{
if(m_updateTask)
{
return m_updateTask->canAbort();
}
return true;
}
bool Update::abort()
{
m_aborted = true;
if(m_updateTask)
{
if(m_updateTask->canAbort())
{
return m_updateTask->abort();
}
}
return true;
}

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,8 @@
#pragma once
#include <launch/LaunchStep.h>
#include <launch/LoggedProcess.h>
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
@@ -27,15 +28,16 @@ public:
explicit Update(LaunchTask *parent):LaunchStep(parent) {};
virtual ~Update() {};
virtual void executeTask();
virtual bool canAbort() const
{
return false;
}
virtual void proceed();
void executeTask() override;
bool canAbort() const override;
void proceed() override;
public slots:
bool abort() override;
private slots:
void updateFinished();
private:
std::shared_ptr<Task> m_updateTask;
shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false;
};

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,6 +13,7 @@
* limitations under the License.
*/
#include <QFileInfo>
#include <QDir>
#include <QDirIterator>
#include <QCryptographicHash>
@@ -23,7 +24,10 @@
#include <QDebug>
#include "AssetsUtils.h"
#include <FileSystem.h>
#include "FileSystem.h"
#include "net/Download.h"
#include "net/ChecksumValidator.h"
namespace AssetsUtils
{
@@ -32,7 +36,7 @@ namespace AssetsUtils
* Returns true on success, with index populated
* index is undefined otherwise
*/
bool loadAssetsIndexJson(QString path, AssetsIndex *index)
bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
{
/*
{
@@ -56,6 +60,7 @@ bool loadAssetsIndexJson(QString path, AssetsIndex *index)
qCritical() << "Failed to read assets index file" << path;
return false;
}
index->id = assetsId;
// Read the file and close it.
QByteArray jsonData = file.readAll();
@@ -143,7 +148,7 @@ QDir reconstructAssets(QString assetsId)
<< objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(indexPath, &index);
bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
if (loadAssetsIndex && index.isVirtual)
{
@@ -182,3 +187,51 @@ QDir reconstructAssets(QString assetsId)
}
}
NetActionPtr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size))
{
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
if(hash.size())
{
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
}
objectDL->m_total_progress = size;
return objectDL;
}
return nullptr;
}
QString AssetObject::getLocalPath()
{
return "assets/objects/" + getRelPath();
}
QUrl AssetObject::getUrl()
{
return QUrl("http://resources.download.minecraft.net/" + getRelPath());
}
QString AssetObject::getRelPath()
{
return hash.left(2) + "/" + hash;
}
NetJobPtr AssetsIndex::getDownloadJob()
{
auto job = new NetJob(QObject::tr("Assets for %1").arg(id));
for (auto &object : objects.values())
{
auto dl = object.getDownloadAction();
if(dl)
{
job->addNetAction(dl);
}
}
if(job->size())
return job;
return nullptr;
}

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